Skip to main content

agentmail/types/
mod.rs

1//! Wire shapes from the AgentMail OpenAPI spec. Every type deserializes
2//! permissively (unknown fields ignored, optional fields default) so spec
3//! additions don't break callers; request types skip empty fields so the
4//! API's validators stay quiet.
5
6use crate::util::QueryBuilder;
7
8mod accounts;
9mod agent;
10mod api_keys;
11mod apps;
12mod attachments;
13mod auth;
14mod calendar;
15mod domains;
16mod drafts;
17mod inbox_events;
18mod inboxes;
19mod lists;
20mod messages;
21mod metrics;
22mod organizations;
23mod pods;
24mod threads;
25mod webhooks;
26
27pub use accounts::*;
28pub use agent::*;
29pub use api_keys::*;
30pub use apps::*;
31pub use attachments::*;
32pub use auth::*;
33pub use calendar::*;
34pub use domains::*;
35pub use drafts::*;
36pub use inbox_events::*;
37pub use inboxes::*;
38pub use lists::*;
39pub use messages::*;
40pub use metrics::*;
41pub use organizations::*;
42pub use pods::*;
43pub use threads::*;
44pub use webhooks::*;
45
46/// Pagination controls for the `list_*_page` calls. `Default` is the API's
47/// own defaults (first page, server-chosen page size).
48#[derive(Clone, Debug, Default)]
49pub struct Page {
50    /// Maximum items per page (the API caps this server-side).
51    pub limit: Option<u32>,
52    /// Cursor from a previous response's `next_page_token`.
53    pub page_token: Option<String>,
54}
55impl Page {
56    pub(crate) fn query(&self) -> Vec<(&'static str, String)> {
57        QueryBuilder::new()
58            .opt("limit", self.limit.as_ref())
59            .opt("page_token", self.page_token.as_ref())
60            .build()
61    }
62}
63#[cfg(test)]
64mod tests {
65    use super::*;
66
67    #[test]
68    fn wire_shapes_round_trip() {
69        // Response example from the OpenAPI spec.
70        let inbox: Inbox = serde_json::from_str(
71            r#"{"pod_id":"pod_1","inbox_id":"ib_1","email":"x@agentmail.to",
72                "display_name":"X","updated_at":"2024-01-15T09:30:00Z",
73                "created_at":"2024-01-15T09:30:00Z","surprise_field":1}"#,
74        )
75        .unwrap();
76        assert_eq!(inbox.email, "x@agentmail.to");
77
78        // List items are a subset of the get shape, both must parse.
79        let list: MessageList = serde_json::from_str(
80            r#"{"count":1,"messages":[{"message_id":"m1","thread_id":"t1",
81                "from":"a@b.c","subject":"hi","preview":"…","timestamp":"2026-01-01T00:00:00Z"}]}"#,
82        )
83        .unwrap();
84        assert_eq!(list.messages[0].message_id, "m1");
85        assert!(list.messages[0].text.is_none());
86
87        // Requests omit empties so the API's validators stay quiet.
88        let body = serde_json::to_value(SendMessage {
89            to: vec!["a@b.c".into()],
90            subject: Some("s".into()),
91            text: Some("t".into()),
92            ..Default::default()
93        })
94        .unwrap();
95        assert_eq!(
96            body,
97            serde_json::json!({"to":["a@b.c"],"subject":"s","text":"t"}),
98        );
99    }
100
101    #[test]
102    fn page_query_pairs() {
103        assert!(Page::default().query().is_empty());
104        let q = Page {
105            limit: Some(10),
106            page_token: Some("tok".into()),
107        }
108        .query();
109        assert_eq!(
110            q,
111            vec![
112                ("limit", "10".to_string()),
113                ("page_token", "tok".to_string())
114            ],
115        );
116    }
117}