1use core::{error::Error, fmt, marker::PhantomData};
9
10use alloc::{
11 format,
12 string::{String, ToString},
13 vec::Vec,
14};
15
16use io_http::{
17 coroutine::{HttpCoroutine, HttpCoroutineState},
18 rfc6750::bearer::HttpAuthBearer,
19 rfc9110::{
20 request::HttpRequest,
21 send::{HttpSendOutput, HttpSendYield},
22 },
23 rfc9112::send::{Http11Send, Http11SendError},
24};
25use log::{debug, trace};
26use serde::{Deserialize, Deserializer, Serialize, de::DeserializeOwned};
27use url::Url;
28
29use crate::coroutine::{MsgraphCoroutine, MsgraphCoroutineState, MsgraphYield};
30
31pub const MSGRAPH_API_BASE: &str = "https://graph.microsoft.com/v1.0/";
33
34pub fn user_path(user_id: &str) -> String {
41 if user_id == "me" {
42 String::from("me")
43 } else {
44 format!("users/{user_id}")
45 }
46}
47
48#[derive(Debug, Clone, Copy, Default, Eq, PartialEq)]
51pub struct MsgraphNoResponse;
52
53impl<'de> Deserialize<'de> for MsgraphNoResponse {
54 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
55 where
56 D: Deserializer<'de>,
57 {
58 let _ = serde::de::IgnoredAny::deserialize(deserializer)?;
59 Ok(Self)
60 }
61}
62
63#[derive(Debug)]
65pub enum MsgraphSendError {
66 Send(Http11SendError),
68 SerializeRequest(serde_json::Error),
70 ParseResponse(serde_json::Error),
72 ParseUrl(url::ParseError),
74 InvalidRequest(String),
76 Api {
79 status: u16,
81 code: String,
83 message: String,
85 },
86 UnexpectedRedirect,
88}
89
90impl fmt::Display for MsgraphSendError {
91 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
92 match self {
93 Self::Send(err) => write!(f, "Microsoft Graph HTTP request failed: {err}"),
94 Self::SerializeRequest(err) => {
95 write!(f, "Microsoft Graph request serialization failed: {err}")
96 }
97 Self::ParseResponse(err) => write!(f, "Microsoft Graph response parsing failed: {err}"),
98 Self::ParseUrl(err) => write!(f, "Microsoft Graph URL parsing failed: {err}"),
99 Self::InvalidRequest(reason) => write!(f, "Invalid Microsoft Graph request: {reason}"),
100 Self::Api {
101 status,
102 code,
103 message,
104 } => write!(
105 f,
106 "Microsoft Graph API returned HTTP {status} ({code}): {message}"
107 ),
108 Self::UnexpectedRedirect => {
109 write!(f, "Microsoft Graph server returned an unexpected redirect")
110 }
111 }
112 }
113}
114
115impl Error for MsgraphSendError {
116 fn source(&self) -> Option<&(dyn Error + 'static)> {
117 match self {
118 Self::Send(err) => Some(err),
119 Self::SerializeRequest(err) | Self::ParseResponse(err) => Some(err),
120 Self::ParseUrl(err) => Some(err),
121 _ => None,
122 }
123 }
124}
125
126impl From<Http11SendError> for MsgraphSendError {
127 fn from(err: Http11SendError) -> Self {
128 Self::Send(err)
129 }
130}
131
132impl From<url::ParseError> for MsgraphSendError {
133 fn from(err: url::ParseError) -> Self {
134 Self::ParseUrl(err)
135 }
136}
137
138impl MsgraphSendError {
139 pub fn status(&self) -> Option<u16> {
142 match self {
143 Self::Api { status, .. } => Some(*status),
144 _ => None,
145 }
146 }
147
148 pub fn is_retryable(&self) -> bool {
150 matches!(self.status(), Some(429 | 500 | 502 | 503 | 504))
151 }
152}
153
154#[derive(Clone, Debug)]
157pub struct MsgraphSendOutput<T> {
158 pub response: T,
160 pub keep_alive: bool,
162}
163
164pub struct MsgraphSend<T> {
167 state: State,
168 _phantom: PhantomData<T>,
169}
170
171impl<T: DeserializeOwned> MsgraphSend<T> {
172 pub fn get(auth: &HttpAuthBearer, url: Url) -> Self {
174 Self::with_method(auth, "GET", url, None, Vec::new())
175 }
176
177 pub fn delete(auth: &HttpAuthBearer, url: Url) -> Self {
179 Self::with_method(auth, "DELETE", url, None, Vec::new())
180 }
181
182 pub fn post_json<B: Serialize>(
184 auth: &HttpAuthBearer,
185 url: Url,
186 body: &B,
187 ) -> Result<Self, MsgraphSendError> {
188 let body = serde_json::to_vec(body).map_err(MsgraphSendError::SerializeRequest)?;
189 Ok(Self::with_method(
190 auth,
191 "POST",
192 url,
193 Some("application/json"),
194 body,
195 ))
196 }
197
198 pub fn patch_json<B: Serialize>(
200 auth: &HttpAuthBearer,
201 url: Url,
202 body: &B,
203 ) -> Result<Self, MsgraphSendError> {
204 let body = serde_json::to_vec(body).map_err(MsgraphSendError::SerializeRequest)?;
205 Ok(Self::with_method(
206 auth,
207 "PATCH",
208 url,
209 Some("application/json"),
210 body,
211 ))
212 }
213
214 pub fn post_text(auth: &HttpAuthBearer, url: Url, body: Vec<u8>) -> Self {
216 Self::with_method(auth, "POST", url, Some("text/plain"), body)
217 }
218
219 pub fn with_method(
221 auth: &HttpAuthBearer,
222 method: &str,
223 url: Url,
224 content_type: Option<&str>,
225 body: Vec<u8>,
226 ) -> Self {
227 let mut request = HttpRequest::get(url.clone())
228 .header("Accept", "application/json")
229 .header("Authorization", auth.to_authorization())
230 .body(body);
231
232 if let Some(content_type) = content_type {
233 request = request.header("Content-Type", content_type);
234 }
235
236 request.method = method.into();
237
238 debug!("prepare request to send");
239 trace!("method: {method}");
240 trace!("url: {url}");
241
242 Self {
243 state: State::Send(Http11Send::new(request)),
244 _phantom: PhantomData,
245 }
246 }
247}
248
249impl<T: DeserializeOwned> MsgraphCoroutine for MsgraphSend<T> {
250 type Yield = MsgraphYield;
251 type Return = Result<MsgraphSendOutput<T>, MsgraphSendError>;
252
253 fn resume(&mut self, arg: Option<&[u8]>) -> MsgraphCoroutineState<Self::Yield, Self::Return> {
254 match &mut self.state {
255 State::Send(send) => match send.resume(arg) {
256 HttpCoroutineState::Yielded(HttpSendYield::WantsRead) => {
257 MsgraphCoroutineState::Yielded(MsgraphYield::WantsRead)
258 }
259 HttpCoroutineState::Yielded(HttpSendYield::WantsWrite(bytes)) => {
260 MsgraphCoroutineState::Yielded(MsgraphYield::WantsWrite(bytes))
261 }
262 HttpCoroutineState::Yielded(HttpSendYield::WantsRedirect { .. }) => {
263 MsgraphCoroutineState::Complete(Err(MsgraphSendError::UnexpectedRedirect))
264 }
265 HttpCoroutineState::Complete(Err(err)) => {
266 MsgraphCoroutineState::Complete(Err(err.into()))
267 }
268 HttpCoroutineState::Complete(Ok(HttpSendOutput {
269 response,
270 keep_alive,
271 ..
272 })) => {
273 if response.status.is_success() {
274 let body = if response.body.is_empty() {
275 b"null".as_slice()
276 } else {
277 response.body.as_slice()
278 };
279
280 match serde_json::from_slice::<T>(body) {
281 Ok(response) => {
282 MsgraphCoroutineState::Complete(Ok(MsgraphSendOutput {
283 response,
284 keep_alive,
285 }))
286 }
287 Err(err) => MsgraphCoroutineState::Complete(Err(
288 MsgraphSendError::ParseResponse(err),
289 )),
290 }
291 } else {
292 let (status, code, message) =
293 parse_api_error(*response.status, &response.body);
294 MsgraphCoroutineState::Complete(Err(MsgraphSendError::Api {
295 status,
296 code,
297 message,
298 }))
299 }
300 }
301 },
302 }
303 }
304}
305
306enum State {
307 Send(Http11Send),
308}
309
310#[derive(Debug, Deserialize)]
311struct ErrorEnvelope {
312 error: ErrorBody,
313}
314
315#[derive(Debug, Deserialize)]
316struct ErrorBody {
317 code: Option<String>,
318 message: Option<String>,
319}
320
321pub fn parse_api_error(http_status: u16, body: &[u8]) -> (u16, String, String) {
328 if let Ok(envelope) = serde_json::from_slice::<ErrorEnvelope>(body) {
329 let code = envelope
330 .error
331 .code
332 .filter(|code| !code.trim().is_empty())
333 .unwrap_or_else(|| String::from("unknown"));
334 let message = envelope
335 .error
336 .message
337 .filter(|message| !message.trim().is_empty())
338 .unwrap_or_else(|| String::from("unknown Microsoft Graph API error"));
339 return (http_status, code, message);
340 }
341
342 let message = String::from_utf8_lossy(body).trim().to_string();
343
344 if message.is_empty() {
345 (
346 http_status,
347 String::from("unknown"),
348 String::from("unknown Microsoft Graph API error"),
349 )
350 } else {
351 (http_status, String::from("unknown"), message)
352 }
353}