airtable_api/
lib.rs

1/*!
2 * A rust library for interacting with the Airtable API.
3 *
4 * For more information, the Airtable API is documented at [airtable.com/api](https://airtable.com/api).
5 *
6 * Example:
7 *
8 * ```ignore
9 * use airtable_api::{Airtable, Record};
10 * use serde::{Deserialize, Serialize};
11 *
12 * async fn get_records() {
13 *     // Initialize the Airtable client.
14 *     let airtable = Airtable::new_from_env();
15 *
16 *     // Get the current records from a table.
17 *     let mut records: Vec<Record<SomeFormat>> = airtable
18 *         .list_records(
19 *             "Table Name",
20 *             "Grid view",
21 *             vec!["the", "fields", "you", "want", "to", "return"],
22 *         )
23 *         .await
24 *         .unwrap();
25 *
26 *     // Iterate over the records.
27 *     for (i, record) in records.clone().iter().enumerate() {
28 *         println!("{} - {:?}", i, record);
29 *     }
30 * }
31 *
32 * #[derive(Debug, Clone, Serialize, Deserialize)]
33 * pub struct SomeFormat {
34 *     pub x: bool,
35 * }
36 * ```
37 */
38#![allow(clippy::field_reassign_with_default)]
39use std::{env, fmt, fmt::Debug};
40
41use anyhow::{bail, Result};
42use chrono::{offset::Utc, DateTime};
43use reqwest::{header, Method, Request, StatusCode, Url};
44use schemars::JsonSchema;
45use serde::{
46    de::{DeserializeOwned, MapAccess, SeqAccess, Visitor},
47    Deserialize, Deserializer, Serialize,
48};
49
50/// Endpoint for the Airtable API.
51const ENDPOINT: &str = "https://api.airtable.com/v0/";
52
53/// Entrypoint for interacting with the Airtable API.
54pub struct Airtable {
55    key: String,
56    base_id: String,
57    enterprise_account_id: String,
58
59    client: reqwest_middleware::ClientWithMiddleware,
60}
61
62/// Get the API key from the AIRTABLE_API_KEY env variable.
63pub fn api_key_from_env() -> String {
64    env::var("AIRTABLE_API_KEY").unwrap_or_default()
65}
66
67impl Airtable {
68    /// Create a new Airtable client struct. It takes a type that can convert into
69    /// an &str (`String` or `Vec<u8>` for example). As long as the function is
70    /// given a valid API Key and Base ID your requests will work.
71    /// You can leave the Enterprise Account ID empty if you are not using the
72    /// Enterprise API features.
73    pub fn new<K, B, E>(key: K, base_id: B, enterprise_account_id: E) -> Self
74    where
75        K: ToString,
76        B: ToString,
77        E: ToString,
78    {
79        let http = reqwest::Client::builder().build();
80        match http {
81            Ok(c) => {
82                let retry_policy = reqwest_retry::policies::ExponentialBackoff::builder().build_with_max_retries(3);
83                let client = reqwest_middleware::ClientBuilder::new(c)
84                    // Trace HTTP requests. See the tracing crate to make use of these traces.
85                    .with(reqwest_tracing::TracingMiddleware)
86                    // Retry failed requests.
87                    .with(reqwest_retry::RetryTransientMiddleware::new_with_policy(retry_policy))
88                    .build();
89
90                Self {
91                    key: key.to_string(),
92                    base_id: base_id.to_string(),
93                    enterprise_account_id: enterprise_account_id.to_string(),
94
95                    client,
96                }
97            }
98            Err(e) => panic!("creating client failed: {:?}", e),
99        }
100    }
101
102    /// Create a new Airtable client struct from environment variables. It
103    /// takes a type that can convert into
104    /// an &str (`String` or `Vec<u8>` for example). As long as the function is
105    /// given a valid API Key and Base ID your requests will work.
106    pub fn new_from_env() -> Self {
107        let base_id = env::var("AIRTABLE_BASE_ID").unwrap_or_default();
108        let enterprise_account_id = env::var("AIRTABLE_ENTERPRISE_ACCOUNT_ID").unwrap_or_default();
109
110        Airtable::new(api_key_from_env(), base_id, enterprise_account_id)
111    }
112
113    /// Get the currently set API key.
114    pub fn get_key(&self) -> &str {
115        &self.key
116    }
117
118    fn request<B>(&self, method: Method, path: String, body: B, query: Option<Vec<(&str, String)>>) -> Result<Request>
119    where
120        B: Serialize,
121    {
122        let base = Url::parse(ENDPOINT)?;
123        let url = base.join(&(self.base_id.to_string() + "/" + &path))?;
124
125        let bt = format!("Bearer {}", self.key);
126        let bearer = header::HeaderValue::from_str(&bt)?;
127
128        // Set the default headers.
129        let mut headers = header::HeaderMap::new();
130        headers.append(header::AUTHORIZATION, bearer);
131        headers.append(
132            header::CONTENT_TYPE,
133            header::HeaderValue::from_static("application/json"),
134        );
135
136        let mut rb = self.client.request(method.clone(), url).headers(headers);
137
138        match query {
139            None => (),
140            Some(val) => {
141                rb = rb.query(&val);
142            }
143        }
144
145        // Add the body, this is to ensure our GET and DELETE calls succeed.
146        if method != Method::GET && method != Method::DELETE {
147            rb = rb.json(&body);
148        }
149
150        // Build the request.
151        Ok(rb.build()?)
152    }
153
154    /// List records in a table for a particular view.
155    pub async fn list_records<T: DeserializeOwned>(
156        &self,
157        table: &str,
158        view: &str,
159        fields: Vec<&str>,
160    ) -> Result<Vec<Record<T>>> {
161        let mut params = vec![("pageSize", "100".to_string()), ("view", view.to_string())];
162        for field in fields {
163            params.push(("fields[]", field.to_string()));
164        }
165
166        // Build the request.
167        let mut request = self.request(Method::GET, table.to_string(), (), Some(params))?;
168
169        let mut resp = self.client.execute(request).await?;
170        match resp.status() {
171            StatusCode::OK => (),
172            s => {
173                bail!("status code: {}, body: {}", s, resp.text().await?);
174            }
175        };
176
177        // Try to deserialize the response.
178        let mut r: APICall<T> = resp.json().await?;
179
180        let mut records = r.records;
181
182        let mut offset = r.offset;
183
184        // Paginate if we should.
185        // TODO: make this more DRY
186        while !offset.is_empty() {
187            request = self.request(
188                Method::GET,
189                table.to_string(),
190                (),
191                Some(vec![
192                    ("pageSize", "100".to_string()),
193                    ("view", view.to_string()),
194                    ("offset", offset),
195                ]),
196            )?;
197
198            resp = self.client.execute(request).await?;
199            match resp.status() {
200                StatusCode::OK => (),
201                s => {
202                    bail!("status code: {}, body: {}", s, resp.text().await?);
203                }
204            };
205
206            // Try to deserialize the response.
207            r = resp.json().await?;
208
209            records.append(&mut r.records);
210
211            offset = r.offset;
212        }
213
214        Ok(records)
215    }
216
217    /// Get record from a table.
218    pub async fn get_record<T: DeserializeOwned>(&self, table: &str, record_id: &str) -> Result<Record<T>> {
219        // Build the request.
220        let request = self.request(Method::GET, format!("{}/{}", table, record_id), (), None)?;
221
222        let resp = self.client.execute(request).await?;
223        match resp.status() {
224            StatusCode::OK => (),
225            s => {
226                bail!("status code: {}, body: {}", s, resp.text().await?);
227            }
228        };
229
230        // Try to deserialize the response.
231        let record: Record<T> = resp.json().await?;
232
233        Ok(record)
234    }
235
236    /// Delete record from a table.
237    pub async fn delete_record(&self, table: &str, record_id: &str) -> Result<()> {
238        // Build the request.
239        let request = self.request(
240            Method::DELETE,
241            table.to_string(),
242            (),
243            Some(vec![("records[]", record_id.to_string())]),
244        )?;
245
246        let resp = self.client.execute(request).await?;
247        match resp.status() {
248            StatusCode::OK => (),
249            s => {
250                bail!("status code: {}, body: {}", s, resp.text().await?);
251            }
252        };
253
254        Ok(())
255    }
256
257    /// Bulk create records in a table.
258    ///
259    /// Due to limitations on the Airtable API, you can only bulk create 10
260    /// records at a time.
261    pub async fn create_records<T: Serialize + DeserializeOwned>(
262        &self,
263        table: &str,
264        records: Vec<Record<T>>,
265    ) -> Result<Vec<Record<T>>> {
266        // Build the request.
267        let request = self.request(
268            Method::POST,
269            table.to_string(),
270            APICall {
271                records,
272                offset: "".to_string(),
273                typecast: Some(true),
274            },
275            None,
276        )?;
277
278        let resp = self.client.execute(request).await?;
279        match resp.status() {
280            StatusCode::OK => (),
281            s => {
282                bail!("status code: {}, body: {}", s, resp.text().await?);
283            }
284        };
285
286        // Try to deserialize the response.
287        let r: APICall<T> = resp.json().await?;
288
289        Ok(r.records)
290    }
291
292    /// Bulk update records in a table.
293    ///
294    /// Due to limitations on the Airtable API, you can only bulk update 10
295    /// records at a time.
296    pub async fn update_records<T: Serialize + DeserializeOwned>(
297        &self,
298        table: &str,
299        records: Vec<Record<T>>,
300    ) -> Result<Vec<Record<T>>> {
301        // Build the request.
302        let request = self.request(
303            Method::PATCH,
304            table.to_string(),
305            APICall {
306                records,
307                offset: "".to_string(),
308                typecast: Some(true),
309            },
310            None,
311        )?;
312
313        let resp = self.client.execute(request).await?;
314        match resp.status() {
315            StatusCode::OK => (),
316            s => {
317                bail!("status code: {}, body: {}", s, resp.text().await?);
318            }
319        };
320
321        // Try to deserialize the response.
322        match resp.json::<APICall<T>>().await {
323            Ok(v) => Ok(v.records),
324            Err(_) => {
325                // This might fail. On a faiture just return an empty vector.
326                Ok(vec![])
327            }
328        }
329    }
330
331    /// List users.
332    /// This is for an enterprise admin to do only.
333    /// FROM: https://airtable.com/api/enterprise
334    pub async fn list_users(&self) -> Result<Vec<User>> {
335        if self.enterprise_account_id.is_empty() {
336            // Return an error early.
337            bail!("An enterprise account id is required.");
338        }
339
340        // Build the request.
341        let request = self.request(
342            Method::GET,
343            format!("v0/meta/enterpriseAccounts/{}/users", self.enterprise_account_id),
344            (),
345            Some(vec![("state", "provisioned".to_string())]),
346        )?;
347
348        let resp = self.client.execute(request).await?;
349        match resp.status() {
350            StatusCode::OK => (),
351            s => {
352                bail!("status code: {}, body: {}", s, resp.text().await?);
353            }
354        };
355
356        // Try to deserialize the response.
357        let result: UsersResponse = resp.json().await?;
358
359        Ok(result.users)
360    }
361
362    /// Get an enterprise user.
363    /// This is for an enterprise admin to do only.
364    /// FROM: https://airtable.com/api/enterprise#enterpriseAccountUserGetInformationByEmail
365    /// Permission level can be: owner | create | edit | comment | read
366    pub async fn get_enterprise_user(&self, email: &str) -> Result<EnterpriseUser> {
367        if self.enterprise_account_id.is_empty() {
368            // Return an error early.
369            bail!("An enterprise account id is required.");
370        }
371
372        // Build the request.
373        let request = self.request(
374            Method::GET,
375            format!("v0/meta/enterpriseAccounts/{}/users", self.enterprise_account_id),
376            (),
377            Some(vec![
378                ("email", email.to_string()),
379                ("include", "collaborations".to_string()),
380            ]),
381        )?;
382
383        let resp = self.client.execute(request).await?;
384
385        match resp.status() {
386            StatusCode::OK => (),
387            s => {
388                bail!("status code: {}, body: {}", s, resp.text().await?);
389            }
390        };
391
392        let r: EnterpriseUsersResponse = resp.json().await?;
393
394        if r.users.is_empty() {
395            bail!("no user was returned");
396        }
397
398        Ok(r.users.get(0).unwrap().clone())
399    }
400
401    /// Add a collaborator to a workspace.
402    /// This is for an enterprise admin to do only.
403    /// FROM: https://airtable.com/api/enterprise#enterpriseWorkspaceAddCollaborator
404    /// Permission level can be: owner | create | edit | comment | read
405    pub async fn add_collaborator_to_workspace(
406        &self,
407        workspace_id: &str,
408        user_id: &str,
409        permission_level: &str,
410    ) -> Result<()> {
411        if self.enterprise_account_id.is_empty() {
412            // Return an error early.
413            bail!("An enterprise account id is required.");
414        }
415
416        // Build the request.
417        let request = self.request(
418            Method::POST,
419            format!("v0/meta/workspaces/{}/collaborators", workspace_id),
420            NewCollaborator {
421                collaborators: vec![Collaborator {
422                    user: User {
423                        id: user_id.to_string(),
424                        email: Default::default(),
425                        name: Default::default(),
426                    },
427                    permission_level: permission_level.to_string(),
428                }],
429            },
430            None,
431        )?;
432
433        let resp = self.client.execute(request).await?;
434        match resp.status() {
435            StatusCode::OK => (),
436            s => {
437                bail!("status code: {}, body: {}", s, resp.text().await?);
438            }
439        };
440
441        Ok(())
442    }
443
444    /// Returns basic information on the workspace. Does not include deleted collaborators
445    /// and only include outstanding invites.
446    /// FROM: https://airtable.com/api/enterprise#enterpriseWorkspaceGetInformation
447    pub async fn get_enterprise_workspace<const N: usize>(
448        &self,
449        workspace_id: &str,
450        includes: Option<[WorkspaceIncludes; N]>,
451    ) -> Result<Workspace> {
452        if self.enterprise_account_id.is_empty() {
453            // Return an error early.
454            bail!("An enterprise account id is required.");
455        }
456
457        // Build the request.
458        let request = self.request(
459            Method::GET,
460            format!("v0/meta/workspaces/{}?", workspace_id),
461            (),
462            includes.map(|includes| {
463                includes
464                    .map(|include| {
465                        (
466                            "include",
467                            match include {
468                                WorkspaceIncludes::Collaborators => "collaborators".to_string(),
469                                WorkspaceIncludes::InviteLinks => "inviteLinks".to_string(),
470                            },
471                        )
472                    })
473                    .to_vec()
474            }),
475        )?;
476
477        let resp = self.client.execute(request).await?;
478        match resp.status() {
479            StatusCode::OK => (),
480            s => {
481                bail!("status code: {}, body: {}", s, resp.text().await?);
482            }
483        };
484
485        let r: Workspace = resp.json().await?;
486
487        Ok(r)
488    }
489
490    /// Delete internal user by email.
491    /// This is for an enterprise admin to do only.
492    /// The user must be an internal user, meaning they have an email with the company domain.
493    /// FROM: https://airtable.com/api/enterprise#enterpriseAccountUserDeleteUserByEmail
494    pub async fn delete_internal_user_by_email(&self, email: &str) -> Result<()> {
495        if self.enterprise_account_id.is_empty() {
496            // Return an error early.
497            bail!("An enterprise account id is required.");
498        }
499
500        // Build the request.
501        let request = self.request(
502            Method::DELETE,
503            format!("v0/meta/enterpriseAccounts/{}/users", self.enterprise_account_id),
504            (),
505            Some(vec![("email", email.to_string())]),
506        )?;
507
508        let resp = self.client.execute(request).await?;
509        match resp.status() {
510            StatusCode::OK => (),
511            s => {
512                bail!("status code: {}, body: {}", s, resp.text().await?);
513            }
514        };
515
516        // Try to deserialize the response.
517        let result: DeleteUserResponse = resp.json().await?;
518        if !result.errors.is_empty() {
519            bail!("body: {:?}", result);
520        }
521
522        Ok(())
523    }
524}
525
526#[derive(Debug, Clone, Serialize, Deserialize)]
527struct APICall<T> {
528    /// If there are more records, the response will contain an
529    /// offset. To fetch the next page of records, include offset
530    /// in the next request's parameters.
531    #[serde(default, skip_serializing_if = "String::is_empty")]
532    pub offset: String,
533    /// The current page number of returned records.
534    pub records: Vec<Record<T>>,
535    /// The Airtable API will perform best-effort automatic data conversion
536    /// from string values if the typecast parameter is passed in. Automatic
537    /// conversion is disabled by default to ensure data integrity, but it may
538    /// be helpful for integrating with 3rd party data sources.
539    #[serde(skip_serializing_if = "Option::is_none")]
540    pub typecast: Option<bool>,
541}
542
543/// An Airtable record.
544#[derive(Debug, Clone, Serialize, Deserialize)]
545pub struct Record<T> {
546    #[serde(default, skip_serializing_if = "String::is_empty")]
547    pub id: String,
548    pub fields: T,
549    #[serde(skip_serializing_if = "Option::is_none")]
550    pub created_time: Option<DateTime<Utc>>,
551}
552
553/// An airtable user.
554#[derive(Debug, Default, Clone, Serialize, JsonSchema, Deserialize)]
555pub struct User {
556    #[serde(default, skip_serializing_if = "String::is_empty")]
557    pub id: String,
558    #[serde(default, skip_serializing_if = "String::is_empty")]
559    pub email: String,
560    #[serde(default, skip_serializing_if = "String::is_empty")]
561    pub name: String,
562}
563
564enum UserField {
565    Id,
566    Email,
567    Name,
568}
569
570const USERFIELDS: &[&str] = &["id", "email", "name"];
571
572impl<'de> Deserialize<'de> for UserField {
573    fn deserialize<D>(deserializer: D) -> Result<UserField, D::Error>
574    where
575        D: Deserializer<'de>,
576    {
577        struct UserFieldVisitor;
578
579        impl<'de> Visitor<'de> for UserFieldVisitor {
580            type Value = UserField;
581
582            fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
583                formatter.write_str("`id` `email` or `name`")
584            }
585
586            fn visit_str<E>(self, value: &str) -> Result<UserField, E>
587            where
588                E: serde::de::Error,
589            {
590                match value {
591                    "id" => Ok(UserField::Id),
592                    "email" => Ok(UserField::Email),
593                    "name" => Ok(UserField::Name),
594                    _ => Err(serde::de::Error::unknown_field(value, USERFIELDS)),
595                }
596            }
597        }
598
599        deserializer.deserialize_identifier(UserFieldVisitor)
600    }
601}
602
603struct UserVisitor;
604
605impl<'de> Visitor<'de> for UserVisitor {
606    type Value = User;
607
608    fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
609        formatter.write_str("struct User")
610    }
611
612    fn visit_seq<V>(self, mut seq: V) -> Result<User, V::Error>
613    where
614        V: SeqAccess<'de>,
615    {
616        let id = seq
617            .next_element()?
618            .ok_or_else(|| serde::de::Error::invalid_length(0, &self))?;
619        let email = seq
620            .next_element()?
621            .ok_or_else(|| serde::de::Error::invalid_length(1, &self))?;
622        let name = seq
623            .next_element()?
624            .ok_or_else(|| serde::de::Error::invalid_length(2, &self))?;
625        Ok(User { id, email, name })
626    }
627
628    fn visit_map<V>(self, mut map: V) -> Result<User, V::Error>
629    where
630        V: MapAccess<'de>,
631    {
632        let mut id = None;
633        let mut email = None;
634        let mut name = None;
635        while let Some(key) = map.next_key()? {
636            match key {
637                UserField::Id => {
638                    if id.is_some() {
639                        return Err(serde::de::Error::duplicate_field("id"));
640                    }
641                    id = Some(map.next_value()?);
642                }
643                UserField::Email => {
644                    if email.is_some() {
645                        return Err(serde::de::Error::duplicate_field("email"));
646                    }
647                    email = Some(map.next_value()?);
648                }
649                UserField::Name => {
650                    if name.is_some() {
651                        return Err(serde::de::Error::duplicate_field("name"));
652                    }
653                    name = Some(map.next_value()?);
654                }
655            }
656        }
657        let id = id.unwrap_or_default();
658        let email = email.ok_or_else(|| serde::de::Error::missing_field("email"))?;
659        let name = name.unwrap_or_default();
660        Ok(User { id, email, name })
661    }
662}
663
664struct UsersVisitor;
665
666impl<'de> Visitor<'de> for UsersVisitor {
667    type Value = Vec<User>;
668
669    fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
670        formatter.write_str("a very special vector")
671    }
672
673    fn visit_seq<A: SeqAccess<'de>>(self, mut access: A) -> Result<Self::Value, A::Error> {
674        let mut users: Vec<User> = Default::default();
675
676        // While there are entries remaining in the input, add them
677        // into our vector.
678        while let Some(user) = access.next_element::<User>()? {
679            users.push(user);
680        }
681
682        Ok(users)
683    }
684}
685
686/// The response returned from listing users.
687#[derive(Debug, Default, Clone, Serialize, Deserialize)]
688pub struct UsersResponse {
689    #[serde(default, skip_serializing_if = "Vec::is_empty")]
690    pub users: Vec<User>,
691}
692
693/// The response returned from deleting a user.
694/// FROM: https://airtable.com/api/enterprise#enterpriseAccountUserDeleteUserByEmail
695#[derive(Debug, Default, Clone, Serialize, Deserialize)]
696pub struct DeleteUserResponse {
697    #[serde(default, skip_serializing_if = "Vec::is_empty", rename = "deletedUsers")]
698    pub deleted_users: Vec<User>,
699    #[serde(default, skip_serializing_if = "Vec::is_empty")]
700    pub errors: Vec<ErrorResponse>,
701}
702
703#[derive(Debug, Default, Clone, Serialize, Deserialize)]
704pub struct ErrorResponse {
705    #[serde(default, skip_serializing_if = "String::is_empty")]
706    pub email: String,
707    #[serde(default, skip_serializing_if = "String::is_empty", rename = "type")]
708    pub type_: String,
709    #[serde(default, skip_serializing_if = "String::is_empty")]
710    pub message: String,
711}
712
713#[derive(Debug, Default, Clone, Serialize, Deserialize)]
714pub struct AttachmentShort {
715    #[serde(default, skip_serializing_if = "String::is_empty")]
716    pub url: String,
717}
718
719#[derive(Debug, Default, Clone, Serialize, Deserialize)]
720pub struct Attachment {
721    #[serde(default, skip_serializing_if = "String::is_empty")]
722    pub id: String,
723    #[serde(default, skip_serializing_if = "String::is_empty")]
724    pub url: String,
725    #[serde(default, skip_serializing_if = "String::is_empty")]
726    pub filename: String,
727    #[serde(default)]
728    pub size: i64,
729    #[serde(default, skip_serializing_if = "String::is_empty", rename = "type")]
730    pub type_: String,
731    #[serde(default)]
732    pub thumbnails: Thumbnails,
733}
734
735#[derive(Debug, Default, Clone, Serialize, Deserialize)]
736pub struct Thumbnails {
737    #[serde(default)]
738    pub small: Full,
739    #[serde(default)]
740    pub large: Full,
741    #[serde(default)]
742    pub full: Full,
743}
744
745#[derive(Debug, Default, Clone, Serialize, Deserialize)]
746pub struct Full {
747    #[serde(default, skip_serializing_if = "String::is_empty")]
748    pub url: String,
749    #[serde(default)]
750    pub width: i64,
751    #[serde(default)]
752    pub height: i64,
753}
754
755#[derive(Debug, Default, Clone, Serialize, Deserialize)]
756pub struct NewCollaborator {
757    #[serde(default, skip_serializing_if = "Vec::is_empty")]
758    pub collaborators: Vec<Collaborator>,
759}
760
761#[derive(Debug, Default, Clone, Serialize, Deserialize)]
762pub struct Collaborator {
763    #[serde(default)]
764    pub user: User,
765    #[serde(
766        default,
767        skip_serializing_if = "String::is_empty",
768        deserialize_with = "deserialize_null_string::deserialize",
769        rename = "permissionLevel"
770    )]
771    pub permission_level: String,
772}
773
774#[derive(Debug, Default, Clone, Serialize, Deserialize)]
775pub struct EnterpriseUsersResponse {
776    #[serde(default, skip_serializing_if = "Vec::is_empty")]
777    pub users: Vec<EnterpriseUser>,
778}
779
780#[derive(Debug, Clone, Serialize, Deserialize)]
781pub struct EnterpriseUser {
782    #[serde(
783        default,
784        skip_serializing_if = "String::is_empty",
785        deserialize_with = "deserialize_null_string::deserialize"
786    )]
787    pub id: String,
788    #[serde(
789        default,
790        skip_serializing_if = "String::is_empty",
791        deserialize_with = "deserialize_null_string::deserialize"
792    )]
793    pub state: String,
794    #[serde(
795        default,
796        skip_serializing_if = "String::is_empty",
797        deserialize_with = "deserialize_null_string::deserialize"
798    )]
799    pub email: String,
800    #[serde(
801        default,
802        skip_serializing_if = "String::is_empty",
803        deserialize_with = "deserialize_null_string::deserialize"
804    )]
805    pub name: String,
806    #[serde(
807        default,
808        skip_serializing_if = "Option::is_none",
809        rename = "lastActivityTime",
810        deserialize_with = "deserialize_missing_timezone::deserialize"
811    )]
812    pub last_activity_time: Option<DateTime<Utc>>,
813    #[serde(
814        default,
815        skip_serializing_if = "String::is_empty",
816        deserialize_with = "deserialize_null_string::deserialize",
817        rename = "invitedToAirtableByUserId"
818    )]
819    pub invited_to_airtable_by_user_id: String,
820    #[serde(rename = "createdTime")]
821    pub created_time: DateTime<Utc>,
822    #[serde(default)]
823    pub collaborations: Collaborations,
824}
825
826#[derive(Debug, Default, Clone, Serialize, Deserialize)]
827pub struct Collaborations {
828    #[serde(default, skip_serializing_if = "Vec::is_empty", rename = "workspaceCollaborations")]
829    pub workspace_collaborations: Vec<Collaboration>,
830    #[serde(default, skip_serializing_if = "Vec::is_empty", rename = "baseCollaborations")]
831    pub base_collaborations: Vec<Collaboration>,
832}
833
834#[derive(Debug, Clone, Serialize, Deserialize)]
835pub struct Collaboration {
836    #[serde(
837        default,
838        skip_serializing_if = "String::is_empty",
839        deserialize_with = "deserialize_null_string::deserialize",
840        rename = "baseId"
841    )]
842    pub base_id: String,
843    #[serde(
844        default,
845        skip_serializing_if = "String::is_empty",
846        deserialize_with = "deserialize_null_string::deserialize",
847        rename = "permissionLevel"
848    )]
849    pub permission_level: String,
850    #[serde(rename = "createdTime")]
851    pub created_time: DateTime<Utc>,
852    #[serde(
853        default,
854        skip_serializing_if = "String::is_empty",
855        deserialize_with = "deserialize_null_string::deserialize",
856        rename = "grantedByUserId"
857    )]
858    pub granted_by_user_id: String,
859    #[serde(
860        default,
861        skip_serializing_if = "String::is_empty",
862        deserialize_with = "deserialize_null_string::deserialize",
863        rename = "workspaceId"
864    )]
865    pub workspace_id: String,
866}
867
868/// Optional include flags that can be passed to [get_enterprise_workspace] to control
869/// fields are returned
870pub enum WorkspaceIncludes {
871    Collaborators,
872    InviteLinks,
873}
874
875#[derive(Debug, Clone, Serialize, Deserialize)]
876pub struct Workspace {
877    pub id: String,
878    pub name: String,
879    pub created_time: Option<DateTime<Utc>>,
880    #[serde(rename = "baseIds")]
881    pub base_ids: Vec<String>,
882    #[serde(rename = "individualCollaborators")]
883    pub individual_collaborators: Option<WorkspaceCollaborators>,
884    #[serde(rename = "baseCollaborators")]
885    pub group_collaborators: Option<WorkspaceCollaborators>,
886    pub invite_links: Option<InviteLinks>,
887}
888
889#[derive(Debug, Clone, Serialize, Deserialize)]
890pub struct WorkspaceCollaborators {
891    #[serde(rename = "workspaceCollaborators")]
892    pub workspace_collaborators: Vec<WorkspaceCollaborator>,
893    #[serde(rename = "baseCollaborators")]
894    pub base_collaborators: Vec<BaseCollaborator>,
895}
896
897#[derive(Debug, Clone, Serialize, Deserialize)]
898pub struct WorkspaceCollaborator {
899    #[serde(rename = "userId")]
900    pub user_id: String,
901    pub email: String,
902    #[serde(rename = "permissionLevel")]
903    pub permission_level: String,
904    #[serde(rename = "createdTime")]
905    pub created_time: Option<DateTime<Utc>>,
906    #[serde(rename = "grantedByUserId")]
907    pub granted_by_user_id: String,
908}
909
910#[derive(Debug, Clone, Serialize, Deserialize)]
911pub struct BaseCollaborator {
912    #[serde(rename = "baseId")]
913    pub base_id: String,
914    #[serde(rename = "userId")]
915    pub user_id: String,
916    pub email: String,
917    #[serde(rename = "permissionLevel")]
918    pub permission_level: String,
919    #[serde(rename = "createdTime")]
920    pub created_time: Option<DateTime<Utc>>,
921    #[serde(rename = "grantedByUserId")]
922    pub granted_by_user_id: String,
923}
924
925#[derive(Debug, Clone, Serialize, Deserialize)]
926pub struct InviteLinks {
927    pub workspace_invite_links: Vec<WorkspaceInviteLink>,
928    pub base_invite_links: Vec<BaseInviteLink>,
929}
930
931#[derive(Debug, Clone, Serialize, Deserialize)]
932pub struct WorkspaceInviteLink {
933    pub id: String,
934    #[serde(rename = "type")]
935    pub _type: String,
936    #[serde(rename = "invitedEmail")]
937    pub invited_email: String,
938    #[serde(rename = "restrictedToEmailDomains")]
939    pub restricted_to_email_domains: Vec<String>,
940    #[serde(rename = "createdTime")]
941    pub created_time: Option<DateTime<Utc>>,
942    #[serde(rename = "permissionLevel")]
943    pub permission_level: String,
944    #[serde(rename = "referredByUserId")]
945    pub referred_by_user_id: String,
946}
947
948#[derive(Debug, Clone, Serialize, Deserialize)]
949pub struct BaseInviteLink {
950    pub id: String,
951    #[serde(rename = "baseId")]
952    pub base_id: String,
953    #[serde(rename = "type")]
954    pub _type: String,
955    #[serde(rename = "invitedEmail")]
956    pub invited_email: String,
957    #[serde(rename = "restrictedToEmailDomains")]
958    pub restricted_to_email_domains: Vec<String>,
959    #[serde(rename = "createdTime")]
960    pub created_time: Option<DateTime<Utc>>,
961    #[serde(rename = "permissionLevel")]
962    pub permission_level: String,
963    #[serde(rename = "referredByUserId")]
964    pub referred_by_user_id: String,
965}
966
967struct AttachmentsVisitor;
968
969impl<'de> Visitor<'de> for AttachmentsVisitor {
970    type Value = Vec<Attachment>;
971
972    fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
973        formatter.write_str("a very special vector")
974    }
975
976    fn visit_seq<A: SeqAccess<'de>>(self, mut access: A) -> Result<Self::Value, A::Error> {
977        let mut attachments: Vec<Attachment> = Default::default();
978
979        // While there are entries remaining in the input, add them
980        // into our vector.
981        while let Some(attachment) = access.next_element::<Attachment>()? {
982            attachments.push(attachment);
983        }
984
985        Ok(attachments)
986    }
987}
988
989pub mod user_format_as_array_of_strings {
990    use serde::{self, ser::SerializeSeq, Deserializer, Serializer};
991
992    use super::{User, UsersVisitor};
993
994    // The signature of a serialize_with function must follow the pattern:
995    //
996    //    fn serialize<S>(&T, S) -> Result<S::Ok, S::Error>
997    //    where
998    //        S: Serializer
999    //
1000    // although it may also be generic over the input types T.
1001    pub fn serialize<S>(array: &[String], serializer: S) -> Result<S::Ok, S::Error>
1002    where
1003        S: Serializer,
1004    {
1005        // Make our array of Airtable user objects.
1006        let mut seq = serializer.serialize_seq(Some(array.len())).unwrap();
1007        for e in array {
1008            seq.serialize_element(&User {
1009                id: Default::default(),
1010                email: e.to_string(),
1011                name: Default::default(),
1012            })
1013            .unwrap();
1014        }
1015        seq.end()
1016    }
1017
1018    // The signature of a deserialize_with function must follow the pattern:
1019    //
1020    //    fn deserialize<'de, D>(D) -> Result<T, D::Error>
1021    //    where
1022    //        D: Deserializer<'de>
1023    //
1024    // although it may also be generic over the output types T.
1025    pub fn deserialize<'de, D>(deserializer: D) -> Result<Vec<String>, D::Error>
1026    where
1027        D: Deserializer<'de>,
1028    {
1029        let airtable_users = deserializer.deserialize_seq(UsersVisitor {}).unwrap();
1030
1031        let mut users: Vec<String> = Default::default();
1032        for a in airtable_users {
1033            users.push(a.email.to_string());
1034        }
1035
1036        Ok(users)
1037    }
1038}
1039
1040pub mod user_format_as_string {
1041    use serde::{self, ser::SerializeStruct, Deserializer, Serializer};
1042
1043    use super::{UserVisitor, USERFIELDS};
1044
1045    // The signature of a serialize_with function must follow the pattern:
1046    //
1047    //    fn serialize<S>(&T, S) -> Result<S::Ok, S::Error>
1048    //    where
1049    //        S: Serializer
1050    //
1051    // although it may also be generic over the input types T.
1052    pub fn serialize<S>(email: &str, serializer: S) -> Result<S::Ok, S::Error>
1053    where
1054        S: Serializer,
1055    {
1056        let mut state = serializer.serialize_struct("User", 1)?;
1057        state.serialize_field("email", &email)?;
1058        state.end()
1059    }
1060
1061    // The signature of a deserialize_with function must follow the pattern:
1062    //
1063    //    fn deserialize<'de, D>(D) -> Result<T, D::Error>
1064    //    where
1065    //        D: Deserializer<'de>
1066    //
1067    // although it may also be generic over the output types T.
1068    pub fn deserialize<'de, D>(deserializer: D) -> Result<String, D::Error>
1069    where
1070        D: Deserializer<'de>,
1071    {
1072        let user = deserializer
1073            .deserialize_struct("User", USERFIELDS, UserVisitor)
1074            .unwrap();
1075        Ok(user.email)
1076    }
1077}
1078
1079pub mod attachment_format_as_array_of_strings {
1080    use serde::{self, ser::SerializeSeq, Deserializer, Serializer};
1081
1082    use super::{AttachmentShort, AttachmentsVisitor};
1083
1084    // The signature of a serialize_with function must follow the pattern:
1085    //
1086    //    fn serialize<S>(&T, S) -> Result<S::Ok, S::Error>
1087    //    where
1088    //        S: Serializer
1089    //
1090    // although it may also be generic over the input types T.
1091    pub fn serialize<S>(array: &[String], serializer: S) -> Result<S::Ok, S::Error>
1092    where
1093        S: Serializer,
1094    {
1095        // Make our array of Airtable attachment objects.
1096        let mut seq = serializer.serialize_seq(Some(array.len())).unwrap();
1097        for e in array {
1098            let mut attachment: AttachmentShort = Default::default();
1099            attachment.url = e.to_string();
1100            seq.serialize_element(&attachment).unwrap();
1101        }
1102        seq.end()
1103    }
1104
1105    // The signature of a deserialize_with function must follow the pattern:
1106    //
1107    //    fn deserialize<'de, D>(D) -> Result<T, D::Error>
1108    //    where
1109    //        D: Deserializer<'de>
1110    //
1111    // although it may also be generic over the output types T.
1112    pub fn deserialize<'de, D>(deserializer: D) -> Result<Vec<String>, D::Error>
1113    where
1114        D: Deserializer<'de>,
1115    {
1116        let airtable_attachments = deserializer.deserialize_seq(AttachmentsVisitor {}).unwrap();
1117
1118        let mut attachments: Vec<String> = Default::default();
1119        for a in airtable_attachments {
1120            attachments.push(a.url.to_string());
1121        }
1122
1123        Ok(attachments)
1124    }
1125}
1126
1127pub mod attachment_format_as_string {
1128    use serde::{self, ser::SerializeSeq, Deserializer, Serializer};
1129
1130    use super::{Attachment, AttachmentsVisitor};
1131
1132    // The signature of a serialize_with function must follow the pattern:
1133    //
1134    //    fn serialize<S>(&T, S) -> Result<S::Ok, S::Error>
1135    //    where
1136    //        S: Serializer
1137    //
1138    // although it may also be generic over the input types T.
1139    pub fn serialize<S>(url: &str, serializer: S) -> Result<S::Ok, S::Error>
1140    where
1141        S: Serializer,
1142    {
1143        // Make our array of Airtable attachment objects.
1144        let mut seq = serializer.serialize_seq(Some(1)).unwrap();
1145        let mut attachment: Attachment = Default::default();
1146        attachment.url = url.to_string();
1147        seq.serialize_element(&attachment).unwrap();
1148        seq.end()
1149    }
1150
1151    // The signature of a deserialize_with function must follow the pattern:
1152    //
1153    //    fn deserialize<'de, D>(D) -> Result<T, D::Error>
1154    //    where
1155    //        D: Deserializer<'de>
1156    //
1157    // although it may also be generic over the output types T.
1158    pub fn deserialize<'de, D>(deserializer: D) -> Result<String, D::Error>
1159    where
1160        D: Deserializer<'de>,
1161    {
1162        let airtable_attachments = deserializer.deserialize_seq(AttachmentsVisitor {}).unwrap();
1163        let mut url = String::new();
1164        if !airtable_attachments.is_empty() {
1165            url = airtable_attachments[0].url.to_string();
1166        }
1167        Ok(url)
1168    }
1169}
1170
1171/// An airtable barcode.
1172#[derive(Debug, Default, Clone, Serialize, JsonSchema, Deserialize)]
1173pub struct Barcode {
1174    #[serde(default, skip_serializing_if = "String::is_empty")]
1175    pub text: String,
1176    #[serde(default, skip_serializing_if = "String::is_empty", rename = "type")]
1177    pub type_: String,
1178}
1179
1180struct BarcodeVisitor;
1181
1182impl<'de> Visitor<'de> for BarcodeVisitor {
1183    type Value = Barcode;
1184
1185    fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
1186        formatter.write_str("struct Barcode")
1187    }
1188
1189    fn visit_seq<V>(self, mut seq: V) -> Result<Barcode, V::Error>
1190    where
1191        V: SeqAccess<'de>,
1192    {
1193        let text = seq
1194            .next_element()?
1195            .ok_or_else(|| serde::de::Error::invalid_length(0, &self))?;
1196        let type_ = seq
1197            .next_element()?
1198            .ok_or_else(|| serde::de::Error::invalid_length(1, &self))?;
1199        Ok(Barcode { text, type_ })
1200    }
1201
1202    fn visit_map<V>(self, mut map: V) -> Result<Barcode, V::Error>
1203    where
1204        V: MapAccess<'de>,
1205    {
1206        let mut text = None;
1207        let mut type_ = None;
1208        while let Some(key) = map.next_key()? {
1209            match key {
1210                BarcodeField::Text => {
1211                    if text.is_some() {
1212                        return Err(serde::de::Error::duplicate_field("text"));
1213                    }
1214                    text = Some(map.next_value()?);
1215                }
1216                BarcodeField::Type => {
1217                    if type_.is_some() {
1218                        return Err(serde::de::Error::duplicate_field("type"));
1219                    }
1220                    type_ = Some(map.next_value()?);
1221                }
1222            }
1223        }
1224        let text = text.ok_or_else(|| serde::de::Error::missing_field("text"))?;
1225        let type_ = type_.ok_or_else(|| serde::de::Error::missing_field("type"))?;
1226        Ok(Barcode { text, type_ })
1227    }
1228}
1229
1230enum BarcodeField {
1231    Text,
1232    Type,
1233}
1234
1235const BARCODEFIELDS: &[&str] = &["text", "type"];
1236
1237impl<'de> Deserialize<'de> for BarcodeField {
1238    fn deserialize<D>(deserializer: D) -> Result<BarcodeField, D::Error>
1239    where
1240        D: Deserializer<'de>,
1241    {
1242        struct BarcodeFieldVisitor;
1243
1244        impl<'de> Visitor<'de> for BarcodeFieldVisitor {
1245            type Value = BarcodeField;
1246
1247            fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
1248                formatter.write_str("`text` or `type`")
1249            }
1250
1251            fn visit_str<E>(self, value: &str) -> Result<BarcodeField, E>
1252            where
1253                E: serde::de::Error,
1254            {
1255                match value {
1256                    "text" => Ok(BarcodeField::Text),
1257                    "type" => Ok(BarcodeField::Type),
1258                    _ => Err(serde::de::Error::unknown_field(value, BARCODEFIELDS)),
1259                }
1260            }
1261        }
1262
1263        deserializer.deserialize_identifier(BarcodeFieldVisitor)
1264    }
1265}
1266
1267pub mod barcode_format_as_string {
1268    use serde::{self, ser::SerializeStruct, Deserializer, Serializer};
1269
1270    use super::{BarcodeVisitor, BARCODEFIELDS};
1271
1272    // The signature of a serialize_with function must follow the pattern:
1273    //
1274    //    fn serialize<S>(&T, S) -> Result<S::Ok, S::Error>
1275    //    where
1276    //        S: Serializer
1277    //
1278    // although it may also be generic over the input types T.
1279    pub fn serialize<S>(text: &str, serializer: S) -> Result<S::Ok, S::Error>
1280    where
1281        S: Serializer,
1282    {
1283        let mut state = serializer.serialize_struct("Barcode", 1)?;
1284        state.serialize_field("text", &text)?;
1285        // This needs to be code39 or upce.
1286        state.serialize_field("type", "code39")?;
1287        state.end()
1288    }
1289
1290    // The signature of a deserialize_with function must follow the pattern:
1291    //
1292    //    fn deserialize<'de, D>(D) -> Result<T, D::Error>
1293    //    where
1294    //        D: Deserializer<'de>
1295    //
1296    // although it may also be generic over the output types T.
1297    pub fn deserialize<'de, D>(deserializer: D) -> Result<String, D::Error>
1298    where
1299        D: Deserializer<'de>,
1300    {
1301        let barcode = deserializer
1302            .deserialize_struct("Barcode", BARCODEFIELDS, BarcodeVisitor)
1303            .unwrap();
1304        Ok(barcode.text)
1305    }
1306}
1307
1308pub mod deserialize_null_string {
1309    use serde::{self, Deserialize, Deserializer};
1310
1311    // The signature of a deserialize_with function must follow the pattern:
1312    //
1313    //    fn deserialize<'de, D>(D) -> Result<T, D::Error>
1314    //    where
1315    //        D: Deserializer<'de>
1316    //
1317    // although it may also be generic over the output types T.
1318    pub fn deserialize<'de, D>(deserializer: D) -> Result<String, D::Error>
1319    where
1320        D: Deserializer<'de>,
1321    {
1322        let s = String::deserialize(deserializer).unwrap_or_default();
1323
1324        Ok(s)
1325    }
1326}
1327
1328// The Airtable API started returning EnterpriseUser results with timestamps that do
1329// not specify a timezone. We assume these to be Utc
1330pub mod deserialize_missing_timezone {
1331    use chrono::{DateTime, Utc};
1332    use serde::{self, de::Error, Deserialize, Deserializer};
1333
1334    // The signature of a deserialize_with function must follow the pattern:
1335    //
1336    //    fn deserialize<'de, D>(D) -> Result<T, D::Error>
1337    //    where
1338    //        D: Deserializer<'de>
1339    //
1340    // although it may also be generic over the output types T.
1341    pub fn deserialize<'de, D>(deserializer: D) -> Result<Option<DateTime<Utc>>, D::Error>
1342    where
1343        D: Deserializer<'de>,
1344    {
1345        // Airtable can have issues with timezones, so try to deserialize to a
1346        // string instead for parsing
1347        let mut s = String::deserialize(deserializer).unwrap_or_default();
1348
1349        DateTime::parse_from_rfc3339(s.as_str())
1350            .or_else(|_| {
1351                // The initial parse failed, so instead we can try to fix the incoming
1352                // string representation and parse again. If this constructs yet another
1353                // invalid timestamp, then deserialization will fail
1354                s += "Z";
1355                DateTime::parse_from_rfc3339(s.as_str())
1356            })
1357            .map(|fixed_offset| Some(fixed_offset.with_timezone(&Utc)))
1358            .map_err(D::Error::custom)
1359    }
1360}