wave-api 0.1.0

Typed Rust client for the Wave Accounting GraphQL API
Documentation
use serde_json::{json, Map, Value};

use crate::client::WaveClient;
use crate::error::WaveError;
use crate::models::*;
use crate::options::*;
use crate::pagination::{Connection, Page};
use crate::queries::{account, business, constants, customer, invoice, product, sales_tax, user, vendor};

/// Insert a key into a JSON object only if the value is not None.
fn insert_opt<T: serde::Serialize>(map: &mut Map<String, Value>, key: &str, val: &Option<T>) {
    if let Some(v) = val {
        if let Ok(json_val) = serde_json::to_value(v) {
            map.insert(key.to_string(), json_val);
        }
    }
}

impl WaveClient {
    // ── User ──

    /// Get the currently authenticated user.
    pub async fn get_user(&self) -> Result<User, WaveError> {
        let data = self.execute(user::GET_USER, json!({})).await?;
        let user: User = serde_json::from_value(data["user"].clone())?;
        Ok(user)
    }

    // ── Businesses ──

    /// List all businesses accessible to the authenticated user.
    pub async fn list_businesses(
        &self,
        opts: ListBusinessesOptions,
    ) -> Result<Page<Business>, WaveError> {
        let mut vars = Map::new();
        insert_opt(&mut vars, "page", &opts.page);
        insert_opt(&mut vars, "pageSize", &opts.page_size);
        let data = self.execute(business::LIST_BUSINESSES, Value::Object(vars)).await?;
        let conn: Connection<Business> = serde_json::from_value(data["businesses"].clone())?;
        Ok(conn.into_page())
    }

    /// Get a single business by ID.
    pub async fn get_business(&self, id: &str) -> Result<Business, WaveError> {
        let data = self
            .execute(business::GET_BUSINESS, json!({ "id": id }))
            .await?;
        let biz: Business = serde_json::from_value(data["business"].clone())?;
        Ok(biz)
    }

    // ── Customers ──

    /// List customers for a business.
    pub async fn list_customers(
        &self,
        business_id: &str,
        opts: ListCustomersOptions,
    ) -> Result<Page<Customer>, WaveError> {
        let sort = if opts.sort.is_empty() {
            vec![serde_json::to_value(crate::enums::CustomerSort::NameAsc)?]
        } else {
            opts.sort
                .iter()
                .map(serde_json::to_value)
                .collect::<Result<Vec<_>, _>>()?
        };
        let mut vars = Map::new();
        vars.insert("businessId".into(), json!(business_id));
        vars.insert("sort".into(), json!(sort));
        insert_opt(&mut vars, "page", &opts.page);
        insert_opt(&mut vars, "pageSize", &opts.page_size);
        insert_opt(&mut vars, "email", &opts.email);
        let data = self.execute(customer::LIST_CUSTOMERS, Value::Object(vars)).await?;
        let conn: Connection<Customer> =
            serde_json::from_value(data["business"]["customers"].clone())?;
        Ok(conn.into_page())
    }

    /// Get a single customer by ID.
    pub async fn get_customer(
        &self,
        business_id: &str,
        customer_id: &str,
    ) -> Result<Customer, WaveError> {
        let vars = json!({
            "businessId": business_id,
            "customerId": customer_id,
        });
        let data = self.execute(customer::GET_CUSTOMER, vars).await?;
        let cust: Customer = serde_json::from_value(data["business"]["customer"].clone())?;
        Ok(cust)
    }

    // ── Invoices ──

    /// List invoices for a business.
    pub async fn list_invoices(
        &self,
        business_id: &str,
        opts: ListInvoicesOptions,
    ) -> Result<Page<Invoice>, WaveError> {
        let sort = if opts.sort.is_empty() {
            vec![serde_json::to_value(crate::enums::InvoiceSort::CreatedAtDesc)?]
        } else {
            opts.sort
                .iter()
                .map(serde_json::to_value)
                .collect::<Result<Vec<_>, _>>()?
        };
        let mut vars = Map::new();
        vars.insert("businessId".into(), json!(business_id));
        vars.insert("sort".into(), json!(sort));
        insert_opt(&mut vars, "page", &opts.page);
        insert_opt(&mut vars, "pageSize", &opts.page_size);
        insert_opt(&mut vars, "status", &opts.status);
        insert_opt(&mut vars, "customerId", &opts.customer_id);
        insert_opt(&mut vars, "currency", &opts.currency);
        insert_opt(&mut vars, "invoiceDateStart", &opts.invoice_date_start);
        insert_opt(&mut vars, "invoiceDateEnd", &opts.invoice_date_end);
        insert_opt(&mut vars, "invoiceNumber", &opts.invoice_number);
        let data = self.execute(invoice::LIST_INVOICES, Value::Object(vars)).await?;
        let conn: Connection<Invoice> =
            serde_json::from_value(data["business"]["invoices"].clone())?;
        Ok(conn.into_page())
    }

    /// Get a single invoice by ID.
    pub async fn get_invoice(
        &self,
        business_id: &str,
        invoice_id: &str,
    ) -> Result<Invoice, WaveError> {
        let vars = json!({
            "businessId": business_id,
            "invoiceId": invoice_id,
        });
        let data = self.execute(invoice::GET_INVOICE, vars).await?;
        let inv: Invoice = serde_json::from_value(data["business"]["invoice"].clone())?;
        Ok(inv)
    }

    // ── Accounts ──

    /// List accounts (Chart of Accounts) for a business.
    pub async fn list_accounts(
        &self,
        business_id: &str,
        opts: ListAccountsOptions,
    ) -> Result<Page<Account>, WaveError> {
        let mut vars = Map::new();
        vars.insert("businessId".into(), json!(business_id));
        insert_opt(&mut vars, "page", &opts.page);
        insert_opt(&mut vars, "pageSize", &opts.page_size);
        insert_opt(&mut vars, "types", &opts.types);
        insert_opt(&mut vars, "subtypes", &opts.subtypes);
        insert_opt(&mut vars, "isArchived", &opts.is_archived);
        let data = self.execute(account::LIST_ACCOUNTS, Value::Object(vars)).await?;
        let conn: Connection<Account> =
            serde_json::from_value(data["business"]["accounts"].clone())?;
        Ok(conn.into_page())
    }

    /// Get a single account by ID.
    pub async fn get_account(
        &self,
        business_id: &str,
        account_id: &str,
    ) -> Result<Account, WaveError> {
        let vars = json!({
            "businessId": business_id,
            "accountId": account_id,
        });
        let data = self.execute(account::GET_ACCOUNT, vars).await?;
        let acct: Account = serde_json::from_value(data["business"]["account"].clone())?;
        Ok(acct)
    }

    // ── Products ──

    /// List products for a business.
    pub async fn list_products(
        &self,
        business_id: &str,
        opts: ListProductsOptions,
    ) -> Result<Page<Product>, WaveError> {
        let sort = if opts.sort.is_empty() {
            vec![serde_json::to_value(crate::enums::ProductSort::NameAsc)?]
        } else {
            opts.sort
                .iter()
                .map(serde_json::to_value)
                .collect::<Result<Vec<_>, _>>()?
        };
        let mut vars = Map::new();
        vars.insert("businessId".into(), json!(business_id));
        vars.insert("sort".into(), json!(sort));
        insert_opt(&mut vars, "page", &opts.page);
        insert_opt(&mut vars, "pageSize", &opts.page_size);
        insert_opt(&mut vars, "isSold", &opts.is_sold);
        insert_opt(&mut vars, "isBought", &opts.is_bought);
        insert_opt(&mut vars, "isArchived", &opts.is_archived);
        let data = self.execute(product::LIST_PRODUCTS, Value::Object(vars)).await?;
        let conn: Connection<Product> =
            serde_json::from_value(data["business"]["products"].clone())?;
        Ok(conn.into_page())
    }

    /// Get a single product by ID.
    pub async fn get_product(
        &self,
        business_id: &str,
        product_id: &str,
    ) -> Result<Product, WaveError> {
        let vars = json!({
            "businessId": business_id,
            "productId": product_id,
        });
        let data = self.execute(product::GET_PRODUCT, vars).await?;
        let prod: Product = serde_json::from_value(data["business"]["product"].clone())?;
        Ok(prod)
    }

    // ── Vendors ──

    /// List vendors for a business.
    pub async fn list_vendors(
        &self,
        business_id: &str,
        opts: ListVendorsOptions,
    ) -> Result<Page<Vendor>, WaveError> {
        let mut vars = Map::new();
        vars.insert("businessId".into(), json!(business_id));
        insert_opt(&mut vars, "page", &opts.page);
        insert_opt(&mut vars, "pageSize", &opts.page_size);
        insert_opt(&mut vars, "email", &opts.email);
        let data = self.execute(vendor::LIST_VENDORS, Value::Object(vars)).await?;
        let conn: Connection<Vendor> =
            serde_json::from_value(data["business"]["vendors"].clone())?;
        Ok(conn.into_page())
    }

    /// Get a single vendor by ID.
    pub async fn get_vendor(
        &self,
        business_id: &str,
        vendor_id: &str,
    ) -> Result<Vendor, WaveError> {
        let vars = json!({
            "businessId": business_id,
            "vendorId": vendor_id,
        });
        let data = self.execute(vendor::GET_VENDOR, vars).await?;
        let v: Vendor = serde_json::from_value(data["business"]["vendor"].clone())?;
        Ok(v)
    }

    // ── Sales Taxes ──

    /// List sales taxes for a business.
    pub async fn list_sales_taxes(
        &self,
        business_id: &str,
        opts: ListSalesTaxesOptions,
    ) -> Result<Page<SalesTax>, WaveError> {
        let mut vars = Map::new();
        vars.insert("businessId".into(), json!(business_id));
        insert_opt(&mut vars, "page", &opts.page);
        insert_opt(&mut vars, "pageSize", &opts.page_size);
        insert_opt(&mut vars, "isArchived", &opts.is_archived);
        let data = self.execute(sales_tax::LIST_SALES_TAXES, Value::Object(vars)).await?;
        let conn: Connection<SalesTax> =
            serde_json::from_value(data["business"]["salesTaxes"].clone())?;
        Ok(conn.into_page())
    }

    /// Get a single sales tax by ID.
    pub async fn get_sales_tax(
        &self,
        business_id: &str,
        sales_tax_id: &str,
    ) -> Result<SalesTax, WaveError> {
        let vars = json!({
            "businessId": business_id,
            "salesTaxId": sales_tax_id,
        });
        let data = self.execute(sales_tax::GET_SALES_TAX, vars).await?;
        let st: SalesTax = serde_json::from_value(data["business"]["salesTax"].clone())?;
        Ok(st)
    }

    // ── Constants ──

    /// List all currencies.
    pub async fn list_currencies(&self) -> Result<Vec<Currency>, WaveError> {
        let data = self.execute(constants::LIST_CURRENCIES, json!({})).await?;
        let currencies: Vec<Currency> = serde_json::from_value(data["currencies"].clone())?;
        Ok(currencies)
    }

    /// List all countries.
    pub async fn list_countries(&self) -> Result<Vec<Country>, WaveError> {
        let data = self.execute(constants::LIST_COUNTRIES, json!({})).await?;
        let countries: Vec<Country> = serde_json::from_value(data["countries"].clone())?;
        Ok(countries)
    }

    /// List account types.
    pub async fn list_account_types(&self) -> Result<Vec<AccountType>, WaveError> {
        let data = self
            .execute(constants::LIST_ACCOUNT_TYPES, json!({}))
            .await?;
        let types: Vec<AccountType> = serde_json::from_value(data["accountTypes"].clone())?;
        Ok(types)
    }

    /// List account subtypes.
    pub async fn list_account_subtypes(&self) -> Result<Vec<AccountSubtype>, WaveError> {
        let data = self
            .execute(constants::LIST_ACCOUNT_SUBTYPES, json!({}))
            .await?;
        let subtypes: Vec<AccountSubtype> =
            serde_json::from_value(data["accountSubtypes"].clone())?;
        Ok(subtypes)
    }
}