Skip to main content

nylas_types/
extract.rs

1//! ExtractAI types for intelligent data extraction.
2//!
3//! Extract structured data from emails using AI.
4
5use serde::{Deserialize, Serialize};
6use std::collections::HashMap;
7
8/// Request to extract contact information from messages.
9///
10/// # Example
11///
12/// ```
13/// # use nylas_types::ExtractContactRequest;
14/// let request = ExtractContactRequest {
15///     message_ids: vec!["msg_123".to_string(), "msg_456".to_string()],
16/// };
17/// ```
18#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
19pub struct ExtractContactRequest {
20    /// Message IDs to extract contacts from.
21    pub message_ids: Vec<String>,
22}
23
24/// Response from contact extraction.
25#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
26pub struct ExtractContactResponse {
27    /// Extracted contacts indexed by message ID.
28    pub contacts: HashMap<String, Vec<ExtractedContact>>,
29}
30
31/// An extracted contact from a message.
32#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
33pub struct ExtractedContact {
34    /// Contact name.
35    #[serde(skip_serializing_if = "Option::is_none")]
36    pub name: Option<String>,
37
38    /// Email address.
39    #[serde(skip_serializing_if = "Option::is_none")]
40    pub email: Option<String>,
41
42    /// Phone number.
43    #[serde(skip_serializing_if = "Option::is_none")]
44    pub phone: Option<String>,
45
46    /// Company/organization.
47    #[serde(skip_serializing_if = "Option::is_none")]
48    pub company: Option<String>,
49
50    /// Job title.
51    #[serde(skip_serializing_if = "Option::is_none")]
52    pub title: Option<String>,
53
54    /// Physical address.
55    #[serde(skip_serializing_if = "Option::is_none")]
56    pub address: Option<String>,
57
58    /// Confidence score (0.0 to 1.0).
59    #[serde(skip_serializing_if = "Option::is_none")]
60    pub confidence: Option<f64>,
61}
62
63/// Request to extract signature from messages.
64///
65/// # Example
66///
67/// ```
68/// # use nylas_types::ExtractSignatureRequest;
69/// let request = ExtractSignatureRequest {
70///     message_ids: vec!["msg_123".to_string()],
71///     parse_contacts: Some(true),
72/// };
73/// ```
74#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
75pub struct ExtractSignatureRequest {
76    /// Message IDs to extract signatures from.
77    pub message_ids: Vec<String>,
78
79    /// Whether to parse contact information from signatures.
80    #[serde(skip_serializing_if = "Option::is_none")]
81    pub parse_contacts: Option<bool>,
82}
83
84/// Response from signature extraction.
85#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
86pub struct ExtractSignatureResponse {
87    /// Extracted signatures indexed by message ID.
88    pub signatures: HashMap<String, ExtractedSignature>,
89}
90
91/// An extracted signature from a message.
92#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
93pub struct ExtractedSignature {
94    /// Raw signature text.
95    pub text: String,
96
97    /// Parsed contact information if requested.
98    #[serde(skip_serializing_if = "Option::is_none")]
99    pub contact: Option<ExtractedContact>,
100
101    /// Confidence score (0.0 to 1.0).
102    #[serde(skip_serializing_if = "Option::is_none")]
103    pub confidence: Option<f64>,
104}
105
106/// Request to extract orders/invoices from messages.
107///
108/// # Example
109///
110/// ```
111/// # use nylas_types::ExtractOrderRequest;
112/// let request = ExtractOrderRequest {
113///     message_ids: vec!["msg_123".to_string()],
114/// };
115/// ```
116#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
117pub struct ExtractOrderRequest {
118    /// Message IDs to extract orders from.
119    pub message_ids: Vec<String>,
120}
121
122/// Response from order extraction.
123#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
124pub struct ExtractOrderResponse {
125    /// Extracted orders indexed by message ID.
126    pub orders: HashMap<String, Vec<ExtractedOrder>>,
127}
128
129/// An extracted order/invoice from a message.
130#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
131pub struct ExtractedOrder {
132    /// Order/invoice number.
133    #[serde(skip_serializing_if = "Option::is_none")]
134    pub order_number: Option<String>,
135
136    /// Order date (Unix timestamp).
137    #[serde(skip_serializing_if = "Option::is_none")]
138    pub order_date: Option<i64>,
139
140    /// Total amount.
141    #[serde(skip_serializing_if = "Option::is_none")]
142    pub total: Option<f64>,
143
144    /// Currency code (e.g., "USD").
145    #[serde(skip_serializing_if = "Option::is_none")]
146    pub currency: Option<String>,
147
148    /// Merchant/vendor name.
149    #[serde(skip_serializing_if = "Option::is_none")]
150    pub merchant: Option<String>,
151
152    /// Order status.
153    #[serde(skip_serializing_if = "Option::is_none")]
154    pub status: Option<String>,
155
156    /// Line items in the order.
157    #[serde(skip_serializing_if = "Option::is_none")]
158    pub items: Option<Vec<OrderItem>>,
159
160    /// Shipping information.
161    #[serde(skip_serializing_if = "Option::is_none")]
162    pub shipping: Option<ShippingInfo>,
163
164    /// Confidence score (0.0 to 1.0).
165    #[serde(skip_serializing_if = "Option::is_none")]
166    pub confidence: Option<f64>,
167}
168
169/// A line item in an order.
170#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
171pub struct OrderItem {
172    /// Item name/description.
173    pub name: String,
174
175    /// Quantity.
176    #[serde(skip_serializing_if = "Option::is_none")]
177    pub quantity: Option<i32>,
178
179    /// Price per item.
180    #[serde(skip_serializing_if = "Option::is_none")]
181    pub price: Option<f64>,
182
183    /// Total price for this item.
184    #[serde(skip_serializing_if = "Option::is_none")]
185    pub total: Option<f64>,
186
187    /// SKU/product code.
188    #[serde(skip_serializing_if = "Option::is_none")]
189    pub sku: Option<String>,
190}
191
192/// Shipping information for an order.
193#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
194pub struct ShippingInfo {
195    /// Tracking number.
196    #[serde(skip_serializing_if = "Option::is_none")]
197    pub tracking_number: Option<String>,
198
199    /// Carrier name.
200    #[serde(skip_serializing_if = "Option::is_none")]
201    pub carrier: Option<String>,
202
203    /// Shipping address.
204    #[serde(skip_serializing_if = "Option::is_none")]
205    pub address: Option<String>,
206
207    /// Expected delivery date (Unix timestamp).
208    #[serde(skip_serializing_if = "Option::is_none")]
209    pub delivery_date: Option<i64>,
210}
211
212/// Request to extract generic entities from messages.
213///
214/// # Example
215///
216/// ```
217/// # use nylas_types::ExtractEntitiesRequest;
218/// let request = ExtractEntitiesRequest {
219///     message_ids: vec!["msg_123".to_string()],
220///     entity_types: vec!["date".to_string(), "time".to_string(), "location".to_string()],
221/// };
222/// ```
223#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
224pub struct ExtractEntitiesRequest {
225    /// Message IDs to extract entities from.
226    pub message_ids: Vec<String>,
227
228    /// Types of entities to extract (e.g., "date", "time", "location", "person", "organization").
229    pub entity_types: Vec<String>,
230}
231
232/// Response from entity extraction.
233#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
234pub struct ExtractEntitiesResponse {
235    /// Extracted entities indexed by message ID.
236    pub entities: HashMap<String, Vec<ExtractedEntity>>,
237}
238
239/// An extracted entity from a message.
240#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
241pub struct ExtractedEntity {
242    /// Entity type.
243    pub entity_type: String,
244
245    /// Extracted value.
246    pub value: String,
247
248    /// Original text snippet.
249    #[serde(skip_serializing_if = "Option::is_none")]
250    pub text: Option<String>,
251
252    /// Position in the message.
253    #[serde(skip_serializing_if = "Option::is_none")]
254    pub position: Option<EntityPosition>,
255
256    /// Confidence score (0.0 to 1.0).
257    #[serde(skip_serializing_if = "Option::is_none")]
258    pub confidence: Option<f64>,
259}
260
261/// Position of an entity in a message.
262#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
263pub struct EntityPosition {
264    /// Start offset in the text.
265    pub start: usize,
266
267    /// End offset in the text.
268    pub end: usize,
269}
270
271#[cfg(test)]
272mod tests {
273    use super::*;
274
275    #[test]
276    fn test_extract_contact_request() {
277        let request = ExtractContactRequest {
278            message_ids: vec!["msg_123".to_string()],
279        };
280
281        let json = serde_json::to_string(&request).unwrap();
282        let deserialized: ExtractContactRequest = serde_json::from_str(&json).unwrap();
283
284        assert_eq!(request, deserialized);
285    }
286
287    #[test]
288    fn test_extracted_contact() {
289        let contact = ExtractedContact {
290            name: Some("John Doe".to_string()),
291            email: Some("john@example.com".to_string()),
292            phone: Some("+1-555-0100".to_string()),
293            company: Some("Acme Corp".to_string()),
294            title: Some("CEO".to_string()),
295            address: Some("123 Main St".to_string()),
296            confidence: Some(0.95),
297        };
298
299        let json = serde_json::to_string(&contact).unwrap();
300        let deserialized: ExtractedContact = serde_json::from_str(&json).unwrap();
301
302        assert_eq!(contact, deserialized);
303    }
304
305    #[test]
306    fn test_extract_signature_request() {
307        let request = ExtractSignatureRequest {
308            message_ids: vec!["msg_123".to_string()],
309            parse_contacts: Some(true),
310        };
311
312        let json = serde_json::to_string(&request).unwrap();
313        let deserialized: ExtractSignatureRequest = serde_json::from_str(&json).unwrap();
314
315        assert_eq!(request, deserialized);
316    }
317
318    #[test]
319    fn test_extracted_order() {
320        let order = ExtractedOrder {
321            order_number: Some("ORD-12345".to_string()),
322            order_date: Some(1735689600),
323            total: Some(99.99),
324            currency: Some("USD".to_string()),
325            merchant: Some("Example Store".to_string()),
326            status: Some("shipped".to_string()),
327            items: Some(vec![OrderItem {
328                name: "Product A".to_string(),
329                quantity: Some(2),
330                price: Some(49.99),
331                total: Some(99.98),
332                sku: Some("SKU-001".to_string()),
333            }]),
334            shipping: Some(ShippingInfo {
335                tracking_number: Some("1Z999AA10123456784".to_string()),
336                carrier: Some("UPS".to_string()),
337                address: Some("456 Oak Ave".to_string()),
338                delivery_date: Some(1735776000),
339            }),
340            confidence: Some(0.92),
341        };
342
343        let json = serde_json::to_string(&order).unwrap();
344        let deserialized: ExtractedOrder = serde_json::from_str(&json).unwrap();
345
346        assert_eq!(order, deserialized);
347    }
348
349    #[test]
350    fn test_extract_entities_request() {
351        let request = ExtractEntitiesRequest {
352            message_ids: vec!["msg_123".to_string()],
353            entity_types: vec!["date".to_string(), "location".to_string()],
354        };
355
356        let json = serde_json::to_string(&request).unwrap();
357        let deserialized: ExtractEntitiesRequest = serde_json::from_str(&json).unwrap();
358
359        assert_eq!(request, deserialized);
360    }
361
362    #[test]
363    fn test_extracted_entity() {
364        let entity = ExtractedEntity {
365            entity_type: "date".to_string(),
366            value: "2025-01-01".to_string(),
367            text: Some("January 1st, 2025".to_string()),
368            position: Some(EntityPosition { start: 10, end: 27 }),
369            confidence: Some(0.98),
370        };
371
372        let json = serde_json::to_string(&entity).unwrap();
373        let deserialized: ExtractedEntity = serde_json::from_str(&json).unwrap();
374
375        assert_eq!(entity, deserialized);
376    }
377}