pidge_client/graph/
people.rs1use serde::Deserialize;
5
6use crate::error::ClientError;
7
8#[derive(Debug, Clone, PartialEq, Eq)]
11pub struct Person {
12 pub display_name: String,
13 pub address: String,
14}
15
16#[derive(Deserialize)]
17struct GraphPeopleList {
18 value: Vec<GraphPerson>,
19}
20
21#[derive(Deserialize)]
22struct GraphPerson {
23 #[serde(rename = "displayName", default)]
24 display_name: Option<String>,
25 #[serde(rename = "scoredEmailAddresses", default)]
26 scored_email_addresses: Vec<GraphScoredEmailAddress>,
27}
28
29#[derive(Deserialize)]
30struct GraphScoredEmailAddress {
31 address: String,
32}
33
34pub async fn list_people(
36 http: &reqwest::Client,
37 base_url: &str,
38 access_token: &str,
39 top: usize,
40) -> Result<Vec<Person>, ClientError> {
41 let url = format!("{base_url}/me/people");
42 let resp = super::send_with_retry(http.get(&url).bearer_auth(access_token).query(&[
43 ("$top", top.to_string()),
44 ("$select", "displayName,scoredEmailAddresses".to_string()),
45 ]))
46 .await?;
47 let status = resp.status();
48 if !status.is_success() {
49 let text = resp.text().await.unwrap_or_default();
50 return Err(ClientError::Graph {
51 status: status.as_u16(),
52 message: text,
53 });
54 }
55 let list: GraphPeopleList = resp.json().await?;
56 Ok(list
57 .value
58 .into_iter()
59 .filter_map(|p| {
60 let address = p.scored_email_addresses.into_iter().next()?.address;
61 Some(Person {
62 display_name: super::mail::clean(p.display_name),
63 address,
64 })
65 })
66 .collect())
67}
68
69#[cfg(test)]
70mod tests {
71 use super::*;
72 use wiremock::matchers::{method, path, query_param};
73 use wiremock::{Mock, MockServer, ResponseTemplate};
74
75 #[tokio::test]
76 async fn list_people_maps_one_person() {
77 let server = MockServer::start().await;
78 Mock::given(method("GET"))
79 .and(path("/me/people"))
80 .and(query_param("$top", "10"))
81 .and(query_param("$select", "displayName,scoredEmailAddresses"))
82 .respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!({
83 "value": [
84 {
85 "displayName": "Anna",
86 "scoredEmailAddresses": [{"address": "anna@example.com"}]
87 }
88 ]
89 })))
90 .mount(&server)
91 .await;
92 let http = reqwest::Client::new();
93 let people = list_people(&http, &server.uri(), "AT", 10).await.unwrap();
94 assert_eq!(people.len(), 1);
95 assert_eq!(people[0].display_name, "Anna");
96 assert_eq!(people[0].address, "anna@example.com");
97 }
98
99 #[tokio::test]
100 async fn list_people_skips_entries_without_a_scored_address() {
101 let server = MockServer::start().await;
102 Mock::given(method("GET"))
103 .and(path("/me/people"))
104 .respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!({
105 "value": [
106 { "displayName": "No Address", "scoredEmailAddresses": [] },
107 {
108 "displayName": "Anna",
109 "scoredEmailAddresses": [
110 {"address": "anna@example.com"},
111 {"address": "anna.alt@example.com"}
112 ]
113 }
114 ]
115 })))
116 .mount(&server)
117 .await;
118 let http = reqwest::Client::new();
119 let people = list_people(&http, &server.uri(), "AT", 10).await.unwrap();
120 assert_eq!(people.len(), 1);
121 assert_eq!(people[0].address, "anna@example.com");
122 }
123}