Skip to main content

mcp_gmailcal/
people_api.rs

1use crate::auth::TokenManager;
2use crate::config::Config;
3use crate::errors::{PeopleApiError, PeopleResult};
4use log::{debug, error};
5use reqwest::Client;
6use serde::{Deserialize, Serialize};
7use std::sync::Arc;
8use tokio::sync::Mutex;
9
10const PEOPLE_API_BASE_URL: &str = "https://people.googleapis.com/v1";
11
12// Alias for backward compatibility within this module
13type Result<T> = PeopleResult<T>;
14
15// Contact information representation
16#[derive(Debug, Clone, Serialize, Deserialize)]
17pub struct Contact {
18    pub resource_name: String,
19    pub name: Option<PersonName>,
20    pub email_addresses: Vec<EmailAddress>,
21    pub phone_numbers: Vec<PhoneNumber>,
22    pub organizations: Vec<Organization>,
23    pub photos: Vec<Photo>,
24}
25
26#[derive(Debug, Clone, Serialize, Deserialize)]
27pub struct PersonName {
28    pub display_name: String,
29    pub given_name: Option<String>,
30    pub family_name: Option<String>,
31}
32
33#[derive(Debug, Clone, Serialize, Deserialize)]
34pub struct EmailAddress {
35    pub value: String,
36    pub type_: Option<String>,
37}
38
39#[derive(Debug, Clone, Serialize, Deserialize)]
40pub struct PhoneNumber {
41    pub value: String,
42    pub type_: Option<String>,
43}
44
45#[derive(Debug, Clone, Serialize, Deserialize)]
46pub struct Organization {
47    pub name: Option<String>,
48    pub title: Option<String>,
49}
50
51#[derive(Debug, Clone, Serialize, Deserialize)]
52pub struct Photo {
53    pub url: String,
54    pub default: bool,
55}
56
57#[derive(Debug, Clone, Serialize, Deserialize)]
58pub struct ContactList {
59    pub contacts: Vec<Contact>,
60    pub next_page_token: Option<String>,
61    pub total_items: Option<u32>,
62}
63
64// People API client
65#[derive(Debug, Clone)]
66pub struct PeopleClient {
67    client: Client,
68    token_manager: Arc<Mutex<TokenManager>>,
69}
70
71impl PeopleClient {
72    pub fn new(config: &Config) -> Self {
73        let client = Client::new();
74        // Reuse the Gmail token manager since they share the same OAuth flow
75        let token_manager = Arc::new(Mutex::new(TokenManager::new(config)));
76
77        Self {
78            client,
79            token_manager,
80        }
81    }
82
83    // Get a list of contacts
84    pub async fn list_contacts(&self, max_results: Option<u32>) -> Result<ContactList> {
85        let token = self
86            .token_manager
87            .lock()
88            .await
89            .get_token(&self.client)
90            .await
91            .map_err(|e| PeopleApiError::AuthError(e.to_string()))?;
92
93        let mut url = format!("{}/people/me/connections", PEOPLE_API_BASE_URL);
94
95        // Build query parameters
96        let mut query_parts = Vec::new();
97
98        // Request specific fields
99        let fields = [
100            "names",
101            "emailAddresses",
102            "phoneNumbers",
103            "organizations",
104            "photos",
105        ];
106        query_parts.push(format!("personFields={}", fields.join(",")));
107
108        if let Some(max) = max_results {
109            query_parts.push(format!("pageSize={}", max));
110        }
111
112        if !query_parts.is_empty() {
113            url = format!("{}?{}", url, query_parts.join("&"));
114        }
115
116        debug!("Listing contacts from: {}", url);
117
118        let response = self
119            .client
120            .get(&url)
121            .header("Authorization", format!("Bearer {}", token))
122            .send()
123            .await
124            .map_err(|e| PeopleApiError::NetworkError(e.to_string()))?;
125
126        let status = response.status();
127        if !status.is_success() {
128            let error_text = response
129                .text()
130                .await
131                .unwrap_or_else(|_| "<no response body>".to_string());
132            return Err(PeopleApiError::ApiError(format!(
133                "Failed to list contacts. Status: {}, Error: {}",
134                status, error_text
135            )));
136        }
137
138        let json_response = response
139            .json::<serde_json::Value>()
140            .await
141            .map_err(|e| PeopleApiError::ParseError(e.to_string()))?;
142
143        let mut contacts = Vec::new();
144
145        if let Some(connections) = json_response.get("connections").and_then(|v| v.as_array()) {
146            for connection in connections {
147                if let Ok(contact) = self.parse_contact(connection) {
148                    contacts.push(contact);
149                } else {
150                    // Log parsing error but continue with other contacts
151                    error!("Failed to parse contact: {:?}", connection);
152                }
153            }
154        }
155
156        let next_page_token = json_response
157            .get("nextPageToken")
158            .and_then(|v| v.as_str())
159            .map(|s| s.to_string());
160
161        let total_items = json_response
162            .get("totalItems")
163            .and_then(|v| v.as_u64())
164            .map(|n| n as u32);
165
166        Ok(ContactList {
167            contacts,
168            next_page_token,
169            total_items,
170        })
171    }
172
173    // Search contacts by query
174    pub async fn search_contacts(
175        &self,
176        query: &str,
177        max_results: Option<u32>,
178    ) -> Result<ContactList> {
179        let token = self
180            .token_manager
181            .lock()
182            .await
183            .get_token(&self.client)
184            .await
185            .map_err(|e| PeopleApiError::AuthError(e.to_string()))?;
186
187        let mut url = format!("{}/people:searchContacts", PEOPLE_API_BASE_URL);
188
189        // Build query parameters
190        let mut query_parts = Vec::new();
191
192        // Add search query
193        query_parts.push(format!("query={}", query));
194
195        // Request specific fields
196        let fields = [
197            "names",
198            "emailAddresses",
199            "phoneNumbers",
200            "organizations",
201            "photos",
202        ];
203        query_parts.push(format!("readMask={}", fields.join(",")));
204
205        if let Some(max) = max_results {
206            query_parts.push(format!("pageSize={}", max));
207        }
208
209        if !query_parts.is_empty() {
210            url = format!("{}?{}", url, query_parts.join("&"));
211        }
212
213        debug!("Searching contacts: {}", url);
214
215        let response = self
216            .client
217            .get(&url)
218            .header("Authorization", format!("Bearer {}", token))
219            .send()
220            .await
221            .map_err(|e| PeopleApiError::NetworkError(e.to_string()))?;
222
223        let status = response.status();
224        if !status.is_success() {
225            let error_text = response
226                .text()
227                .await
228                .unwrap_or_else(|_| "<no response body>".to_string());
229            return Err(PeopleApiError::ApiError(format!(
230                "Failed to search contacts. Status: {}, Error: {}",
231                status, error_text
232            )));
233        }
234
235        let json_response = response
236            .json::<serde_json::Value>()
237            .await
238            .map_err(|e| PeopleApiError::ParseError(e.to_string()))?;
239
240        let mut contacts = Vec::new();
241
242        if let Some(results) = json_response.get("results").and_then(|v| v.as_array()) {
243            for result in results {
244                if let Some(person) = result.get("person") {
245                    if let Ok(contact) = self.parse_contact(person) {
246                        contacts.push(contact);
247                    } else {
248                        // Log parsing error but continue with other contacts
249                        error!("Failed to parse contact: {:?}", person);
250                    }
251                }
252            }
253        }
254
255        let next_page_token = json_response
256            .get("nextPageToken")
257            .and_then(|v| v.as_str())
258            .map(|s| s.to_string());
259
260        let total_items = json_response
261            .get("totalPeople")
262            .and_then(|v| v.as_u64())
263            .map(|n| n as u32);
264
265        Ok(ContactList {
266            contacts,
267            next_page_token,
268            total_items,
269        })
270    }
271
272    // Get contact by resource name
273    pub async fn get_contact(&self, resource_name: &str) -> Result<Contact> {
274        let token = self
275            .token_manager
276            .lock()
277            .await
278            .get_token(&self.client)
279            .await
280            .map_err(|e| PeopleApiError::AuthError(e.to_string()))?;
281
282        let mut url = format!("{}/{}", PEOPLE_API_BASE_URL, resource_name);
283
284        // Build query parameters for fields
285        let fields = [
286            "names",
287            "emailAddresses",
288            "phoneNumbers",
289            "organizations",
290            "photos",
291        ];
292        url = format!("{}?personFields={}", url, fields.join(","));
293
294        debug!("Getting contact: {}", url);
295
296        let response = self
297            .client
298            .get(&url)
299            .header("Authorization", format!("Bearer {}", token))
300            .send()
301            .await
302            .map_err(|e| PeopleApiError::NetworkError(e.to_string()))?;
303
304        let status = response.status();
305        if !status.is_success() {
306            let error_text = response
307                .text()
308                .await
309                .unwrap_or_else(|_| "<no response body>".to_string());
310            return Err(PeopleApiError::ApiError(format!(
311                "Failed to get contact. Status: {}, Error: {}",
312                status, error_text
313            )));
314        }
315
316        let json_response = response
317            .json::<serde_json::Value>()
318            .await
319            .map_err(|e| PeopleApiError::ParseError(e.to_string()))?;
320
321        self.parse_contact(&json_response)
322    }
323
324    // Helper method to parse a contact from API response
325    fn parse_contact(&self, data: &serde_json::Value) -> Result<Contact> {
326        let resource_name = data
327            .get("resourceName")
328            .and_then(|v| v.as_str())
329            .ok_or_else(|| PeopleApiError::ParseError("Missing resourceName".to_string()))?
330            .to_string();
331
332        // Parse name
333        let name = if let Some(names) = data.get("names").and_then(|v| v.as_array()) {
334            if let Some(primary_name) = names.first() {
335                let display_name = primary_name
336                    .get("displayName")
337                    .and_then(|v| v.as_str())
338                    .unwrap_or("Unknown")
339                    .to_string();
340
341                let given_name = primary_name
342                    .get("givenName")
343                    .and_then(|v| v.as_str())
344                    .map(|s| s.to_string());
345
346                let family_name = primary_name
347                    .get("familyName")
348                    .and_then(|v| v.as_str())
349                    .map(|s| s.to_string());
350
351                Some(PersonName {
352                    display_name,
353                    given_name,
354                    family_name,
355                })
356            } else {
357                None
358            }
359        } else {
360            None
361        };
362
363        // Parse email addresses
364        let mut email_addresses = Vec::new();
365        if let Some(emails) = data.get("emailAddresses").and_then(|v| v.as_array()) {
366            for email in emails {
367                if let Some(value) = email.get("value").and_then(|v| v.as_str()) {
368                    let type_ = email
369                        .get("type")
370                        .and_then(|v| v.as_str())
371                        .map(|s| s.to_string());
372
373                    email_addresses.push(EmailAddress {
374                        value: value.to_string(),
375                        type_,
376                    });
377                }
378            }
379        }
380
381        // Parse phone numbers
382        let mut phone_numbers = Vec::new();
383        if let Some(phones) = data.get("phoneNumbers").and_then(|v| v.as_array()) {
384            for phone in phones {
385                if let Some(value) = phone.get("value").and_then(|v| v.as_str()) {
386                    let type_ = phone
387                        .get("type")
388                        .and_then(|v| v.as_str())
389                        .map(|s| s.to_string());
390
391                    phone_numbers.push(PhoneNumber {
392                        value: value.to_string(),
393                        type_,
394                    });
395                }
396            }
397        }
398
399        // Parse organizations
400        let mut organizations = Vec::new();
401        if let Some(orgs) = data.get("organizations").and_then(|v| v.as_array()) {
402            for org in orgs {
403                let name = org
404                    .get("name")
405                    .and_then(|v| v.as_str())
406                    .map(|s| s.to_string());
407
408                let title = org
409                    .get("title")
410                    .and_then(|v| v.as_str())
411                    .map(|s| s.to_string());
412
413                organizations.push(Organization { name, title });
414            }
415        }
416
417        // Parse photos
418        let mut photos = Vec::new();
419        if let Some(pics) = data.get("photos").and_then(|v| v.as_array()) {
420            for pic in pics {
421                if let Some(url) = pic.get("url").and_then(|v| v.as_str()) {
422                    let default = pic
423                        .get("default")
424                        .and_then(|v| v.as_bool())
425                        .unwrap_or(false);
426
427                    photos.push(Photo {
428                        url: url.to_string(),
429                        default,
430                    });
431                }
432            }
433        }
434
435        Ok(Contact {
436            resource_name,
437            name,
438            email_addresses,
439            phone_numbers,
440            organizations,
441            photos,
442        })
443    }
444}