ferrtable 0.0.2

Ferris the crab's favorite Airtable library
Documentation
use std::{collections::VecDeque, fmt::Debug, pin::Pin};

use derive_builder::Builder;
use futures::prelude::*;
use percent_encoding::{NON_ALPHANUMERIC, utf8_percent_encode};
use serde::{Deserialize, Serialize, de::DeserializeOwned};

use crate::{
    client::Client,
    errors::ExecutionError,
    pagination::{PaginatedQuery, PaginatedResponse, execute_paginated},
    types::AirtableRecord,
};

#[derive(Builder, Clone, Debug, Serialize)]
#[builder(pattern = "owned", setter(prefix = "with"))]
pub struct ListRecordsQuery {
    #[serde(skip)]
    base_id: String,

    #[serde(skip)]
    #[builder(vis = "pub(crate)")]
    client: Client,

    /// Only data for fields whose names or IDs are in this list will be
    /// included in the result. If you don't need every field, you can use this
    /// parameter to reduce the amount of data transferred.
    #[builder(default)]
    fields: Option<Vec<String>>,

    /// A formula used to filter records. The formula will be evaluated for
    /// each record, and if the result is not 0, false, "", NaN, [], or #Error!
    /// the record will be included in the response.
    ///
    /// If combined with the view parameter, only records in that view which
    /// satisfy the formula will be returned.
    #[builder(default)]
    // filterByFormula is renamed so that the builder method, that is,
    // `.with_filter()`, reads more cleanly.
    #[serde(rename = "filterByFormula")]
    filter: Option<String>,

    #[builder(default, private)]
    offset: Option<String>,

    #[serde(rename = "pageSize")]
    #[builder(default)]
    page_size: Option<usize>,

    #[serde(skip)]
    table_id: String,
}

impl<T> PaginatedQuery<AirtableRecord<T>, ListRecordsResponse<T>> for ListRecordsQuery
where
    T: Clone + Debug + DeserializeOwned,
{
    fn get_offset(&self) -> Option<String> {
        self.offset.clone()
    }

    fn set_offset(&mut self, value: Option<String>) {
        self.offset = value;
    }

    fn get_req_builder(&self) -> reqwest::RequestBuilder {
        let base_id = utf8_percent_encode(&self.base_id, NON_ALPHANUMERIC).to_string();
        let table_id = utf8_percent_encode(&self.table_id, NON_ALPHANUMERIC).to_string();
        self.client
            .post_path(&format!("v0/{base_id}/{table_id}/listRecords",))
            .json(&self)
    }
}

impl ListRecordsQuery {
    pub fn stream_items<T>(
        self,
    ) -> Pin<Box<impl Stream<Item = Result<AirtableRecord<T>, ExecutionError>>>>
    where
        T: Clone + Debug + DeserializeOwned,
    {
        execute_paginated::<AirtableRecord<T>, ListRecordsResponse<T>>(self)
    }
}

#[derive(Clone, Deserialize)]
struct ListRecordsResponse<T>
where
    T: Clone + Debug,
{
    offset: Option<String>,
    records: VecDeque<AirtableRecord<T>>,
}

impl<T> PaginatedResponse<AirtableRecord<T>> for ListRecordsResponse<T>
where
    T: Clone + Debug + DeserializeOwned,
{
    fn get_offset(&self) -> Option<String> {
        self.offset.clone()
    }

    fn get_items(&self) -> VecDeque<AirtableRecord<T>> {
        self.records.clone()
    }
}