invoist 1.0.1

Official Rust client for the Invoist API: extract invoices, read a household's bills, verify webhooks.
Documentation
use reqwest::Method;
use serde::Deserialize;

use crate::client::{Client, Request};
use crate::error::Result;
use crate::files::FileDownload;
use crate::models::{Bill, BillDetail, BillRecord, BillsStats, BillsSummary, HeldEmail};
use crate::params::{BillsStatsParams, BillsSummaryParams, ListBillsParams};

/// A household's bills (Invoist Personal), from [`Client::bills`].
///
/// These calls answer only a household's key or assistant, which holds
/// `bills:read` (and `bills:write` when the household allowed marking bills
/// paid). A business key gets a 403 with code `household_feature`.
///
/// ```no_run
/// # async fn run(client: invoist::Client) -> Result<(), invoist::Error> {
/// use invoist::ListBillsParams;
///
/// let unpaid = client
///     .bills()
///     .list(ListBillsParams { paid: Some(false), ..Default::default() })
///     .await?;
/// for bill in &unpaid {
///     println!("{:?} {:?} due {:?}", bill.vendor_name, bill.total_cents, bill.due_date);
/// }
/// # Ok(())
/// # }
/// ```
#[derive(Debug, Clone, Copy)]
pub struct Bills<'a> {
    client: &'a Client,
}

/// Emails forwarded to a household that wait for approval, from
/// [`Client::held_emails`].
#[derive(Debug, Clone, Copy)]
pub struct HeldEmails<'a> {
    client: &'a Client,
}

impl Client {
    /// A household's bills.
    pub fn bills(&self) -> Bills<'_> {
        Bills { client: self }
    }

    /// A household's emails waiting for approval.
    pub fn held_emails(&self) -> HeldEmails<'_> {
        HeldEmails { client: self }
    }
}

#[derive(Deserialize)]
struct RecordAnswer {
    bill: BillRecord,
}

impl Bills<'_> {
    /// Lists the household's bills as the app does: unpaid ones soonest-due
    /// first, with a reminder or collection letter folded into its bill.
    pub async fn list(&self, params: ListBillsParams) -> Result<Vec<Bill>> {
        #[derive(Deserialize)]
        struct Page {
            bills: Vec<Bill>,
        }
        let req = Request::get(&["v1", "bills"])?.query(params.query());
        Ok(self.client.json::<Page>(req).await?.bills)
    }

    /// Everything about one bill: what was read from it, its open warnings,
    /// the household's record, and its letters.
    pub async fn get(&self, id: &str) -> Result<BillDetail> {
        self.client.json(Request::get(&["v1", "bills", id])?).await
    }

    /// Downloads a bill's original file, exactly as added.
    pub async fn get_file(&self, id: &str) -> Result<FileDownload> {
        self.client
            .download(Request::get(&["v1", "bills", id, "file"])?)
            .await
    }

    /// What there is to pay: how many bills are unpaid and overdue, and what
    /// falls due soon, per currency.
    pub async fn summary(&self, params: BillsSummaryParams) -> Result<BillsSummary> {
        let req = Request::get(&["v1", "bills", "summary"])?.query(params.query());
        self.client.json(req).await
    }

    /// A month at a glance: totals, the twelve months up to it, and the
    /// senders.
    pub async fn stats(&self, params: BillsStatsParams) -> Result<BillsStats> {
        let req = Request::get(&["v1", "bills", "stats"])?.query(params.query());
        self.client.json(req).await
    }

    /// Marks a bill, and its letters, paid by the person who created the
    /// key. Needs `bills:write`.
    pub async fn mark_paid(&self, id: &str) -> Result<BillRecord> {
        let req = Request::new(Method::POST, &["v1", "bills", id, "paid"])?;
        Ok(self.client.json::<RecordAnswer>(req).await?.bill)
    }

    /// Marks a bill as not paid. Needs `bills:write`.
    pub async fn mark_unpaid(&self, id: &str) -> Result<BillRecord> {
        let req = Request::new(Method::DELETE, &["v1", "bills", id, "paid"])?;
        Ok(self.client.json::<RecordAnswer>(req).await?.bill)
    }
}

impl HeldEmails<'_> {
    /// The emails waiting for a person to approve their sender, or for a
    /// plan, oldest first. Approving a sender is done in the app.
    pub async fn list(&self) -> Result<Vec<HeldEmail>> {
        #[derive(Deserialize)]
        struct Page {
            held_emails: Vec<HeldEmail>,
        }
        let req = Request::get(&["v1", "held-emails"])?;
        Ok(self.client.json::<Page>(req).await?.held_emails)
    }
}