# Ferrtable: Ferris the Crab's Favorite Airtable Client Ferrtable provides async Rust bindings for a subset of the [Airtable web API](https://airtable.com/developers/web/api/introduction). Notably, it allows records to be read from and written to arbitrary Rust types that implement the `Clone`, `serde::Deserialize`, and `serde::Serialize` traits. This crate follows in the footsteps of the [airtable-api](https://crates.io/crates/airtable-api) crate from Oxide Computer Company, which appears to have be archived and unmaintained since 2022. By comparison, Ferrtable aims to provide a more flexible and expressive client interface as well as greater control over paginated responses with the help of async [streams](https://doc.rust-lang.org/book/ch17-04-streams.html). ## Status: Work in Progress Only a limited set of operations (e.g., creating and listing records) are currently supported. The goal is to implement coverage for at least the full set of non-enterprise API endpoints, but my initial emphasis is on getting a relatively small subset built and tested well. ## Usage ```rust use futures::prelude::*; // Ferrtable allows us to use any record types that implement Clone, // Deserialize, and Serialize. #[derive(Clone, Debug, Deserialize, Serialize)] struct MyRecord { #[serde(rename = "Name")] name: String, #[serde(rename = "Notes")] notes: String, #[serde(rename = "Assignee")] assignee: Option, #[serde(rename = "Status")] status: Status, #[serde(rename = "Attachments")] attachments: Vec, } #[derive(Clone, Debug, Deserialize, Serialize)] enum Status { Todo, #[serde(rename = "In progress")] InProgress, Done, } #[tokio::main] async fn main() { let client = ferrtable::Client::new_from_access_token("******").unwrap(); client .create_records([MyRecord { name: "Steal Improbability Drive".to_owned(), notes: "Just for fun, no other reason.".to_owned(), assignee: None, status: Status::InProgress, attachments: vec![], }]) .with_base_id("***".to_owned()) .with_table_id("***".to_owned()) .build() .unwrap() .execute() .await .unwrap(); let mut rec_stream = client .list_records() .with_base_id("***".to_owned()) .with_table_id("***".to_owned()) .with_filter("{status} = 'Todo' || {status} = 'In Progress'".to_owned()) .build() .unwrap() .stream_items::(); while let Some(result) = rec_stream.next().await { let rec = result.unwrap(); dbg!(rec.fields); } } ```