ferrtable/ferrtable/src/client.rs
2025-09-16 23:12:20 -07:00

142 lines
4.4 KiB
Rust

use std::fmt::Debug;
use serde::Serialize;
use crate::{
create_records::CreateRecordsQueryBuilder, list_bases::ListBasesQueryBuilder,
list_records::ListRecordsQueryBuilder,
};
const DEFAULT_API_ROOT: &str = "https://api.airtable.com";
#[derive(Clone)]
pub struct Client {
// TODO: Does API root need to be customizable per client, e.g. for on-prem
// enterprise deployments, or is that not a thing?
api_root: String,
client: reqwest::Client,
token: String,
}
impl Client {
pub fn new_from_access_token(token: &str) -> Result<Self, reqwest::Error> {
Ok(Self {
api_root: DEFAULT_API_ROOT.to_owned(),
client: reqwest::ClientBuilder::default()
.https_only(true)
.build()
.expect("reqwest client is always built with the same configuration here"),
token: token.to_owned(),
})
}
/// Creates multiple records. Note that table names and table ids can be
/// used interchangeably. We recommend using table IDs so you don't need
/// to modify your API request when your table name changes.
///
/// Your request body should include an array of up to 10 record objects.
///
/// Returns a unique array of the newly created record ids if the call
/// succeeds.
///
/// # Examples
///
/// ```no_run
/// client
/// .create_records([
/// HashMap<&str, &str>::from([
/// ("name", "Steal Improbability Drive"),
/// ("notes", "Just for fun, no other reason."),
/// ("status", "In progress"),
/// ]),
/// ])
/// .with_base_id("***".to_owned())
/// .with_table_id("***".to_owned())
/// .build()?
/// .execute()
/// .await?;
/// ```
pub fn create_records<I, T>(&self, records: I) -> CreateRecordsQueryBuilder<T>
where
T: Serialize,
I: IntoIterator<Item = T>,
{
CreateRecordsQueryBuilder::default()
.with_client(self.clone())
.with_records(records.into_iter().collect())
}
/// List the bases the token can access
///
/// # Examples
///
/// ## Consuming as Stream
///
/// ```no_run
/// use futures::prelude::*;
///
/// let mut base_stream = client
/// .list_bases()
/// .build()?
/// .stream_items();
///
/// while let Some(result) = base_stream.next().await {
/// dbg!(result?);
/// }
/// ```
pub fn list_bases(&self) -> ListBasesQueryBuilder {
ListBasesQueryBuilder::default().with_client(self.clone())
}
/// List records in a table. Note that table names and table ids can be used
/// interchangeably. We recommend using table IDs so you don't need to modify
/// your API request when your table name changes.
///
/// # Examples
///
/// ## Consuming as Stream
///
/// ```no_run
/// let mut rec_stream = client
/// .list_records()
/// .with_base_id("***")
/// .with_table_id("***")
/// .build()?
/// .stream_items::<HashMap<String, serde_json::Value>>();
///
/// while let Some(result) = rec_stream.next().await {
/// dbg!(rec?.fields);
/// }
/// ```
pub fn list_records(&self) -> ListRecordsQueryBuilder {
ListRecordsQueryBuilder::default().with_client(self.clone())
}
/// Constructs a RequestBuilder with URL "{self.api_root}/{path}" and the
/// Authorization header set to the correct bearer auth value.
pub(crate) fn get_path(&self, path: &str) -> reqwest::RequestBuilder {
let Self {
api_root, token, ..
} = self;
self.client
.get(format!("{api_root}/{path}"))
.header(reqwest::header::AUTHORIZATION, format!("Bearer {token}"))
}
/// Constructs a RequestBuilder with URL "{self.api_root}/{path}" and the
/// Authorization header set to the correct bearer auth value.
pub(crate) fn post_path(&self, path: &str) -> reqwest::RequestBuilder {
let Self {
api_root, token, ..
} = self;
self.client
.post(format!("{api_root}/{path}"))
.header(reqwest::header::AUTHORIZATION, format!("Bearer {token}"))
}
}
impl Debug for Client {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "ferrtable::Client {{ *** }}")
}
}