1use core::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 thiserror::Error;
28use url::Url;
29
30use crate::coroutine::{MsgraphCoroutine, MsgraphCoroutineState, MsgraphYield};
31
32pub const MSGRAPH_API_BASE: &str = "https://graph.microsoft.com/v1.0/";
34
35pub fn user_path(user_id: &str) -> String {
42 if user_id == "me" {
43 String::from("me")
44 } else {
45 format!("users/{user_id}")
46 }
47}
48
49#[derive(Debug, Clone, Copy, Default, Eq, PartialEq)]
52pub struct MsgraphNoResponse;
53
54impl<'de> Deserialize<'de> for MsgraphNoResponse {
55 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
56 where
57 D: Deserializer<'de>,
58 {
59 let _ = serde::de::IgnoredAny::deserialize(deserializer)?;
60 Ok(Self)
61 }
62}
63
64#[derive(Debug, Error)]
66pub enum MsgraphSendError {
67 #[error("Microsoft Graph HTTP request failed: {0}")]
69 Send(#[from] Http11SendError),
70 #[error("Microsoft Graph request serialization failed: {0}")]
72 SerializeRequest(#[source] serde_json::Error),
73 #[error("Microsoft Graph response parsing failed: {0}")]
75 ParseResponse(#[source] serde_json::Error),
76 #[error("Microsoft Graph URL parsing failed: {0}")]
78 ParseUrl(#[from] url::ParseError),
79 #[error("Invalid Microsoft Graph request: {0}")]
81 InvalidRequest(String),
82 #[error("Microsoft Graph API returned HTTP {status} ({code}): {message}")]
85 Api {
86 status: u16,
88 code: String,
90 message: String,
92 },
93 #[error("Microsoft Graph server returned an unexpected redirect")]
95 UnexpectedRedirect,
96}
97
98impl MsgraphSendError {
99 pub fn status(&self) -> Option<u16> {
102 match self {
103 Self::Api { status, .. } => Some(*status),
104 _ => None,
105 }
106 }
107
108 pub fn is_retryable(&self) -> bool {
110 matches!(self.status(), Some(429 | 500 | 502 | 503 | 504))
111 }
112}
113
114#[derive(Clone, Debug)]
117pub struct MsgraphSendOutput<T> {
118 pub response: T,
120 pub keep_alive: bool,
122}
123
124pub struct MsgraphSend<T> {
127 state: State,
128 _phantom: PhantomData<T>,
129}
130
131impl<T: DeserializeOwned> MsgraphSend<T> {
132 pub fn get(auth: &HttpAuthBearer, url: Url) -> Self {
134 Self::with_method(auth, "GET", url, None, Vec::new())
135 }
136
137 pub fn delete(auth: &HttpAuthBearer, url: Url) -> Self {
139 Self::with_method(auth, "DELETE", url, None, Vec::new())
140 }
141
142 pub fn post_json<B: Serialize>(
144 auth: &HttpAuthBearer,
145 url: Url,
146 body: &B,
147 ) -> Result<Self, MsgraphSendError> {
148 let body = serde_json::to_vec(body).map_err(MsgraphSendError::SerializeRequest)?;
149 Ok(Self::with_method(
150 auth,
151 "POST",
152 url,
153 Some("application/json"),
154 body,
155 ))
156 }
157
158 pub fn patch_json<B: Serialize>(
160 auth: &HttpAuthBearer,
161 url: Url,
162 body: &B,
163 ) -> Result<Self, MsgraphSendError> {
164 let body = serde_json::to_vec(body).map_err(MsgraphSendError::SerializeRequest)?;
165 Ok(Self::with_method(
166 auth,
167 "PATCH",
168 url,
169 Some("application/json"),
170 body,
171 ))
172 }
173
174 pub fn post_text(auth: &HttpAuthBearer, url: Url, body: Vec<u8>) -> Self {
176 Self::with_method(auth, "POST", url, Some("text/plain"), body)
177 }
178
179 pub fn with_method(
181 auth: &HttpAuthBearer,
182 method: &str,
183 url: Url,
184 content_type: Option<&str>,
185 body: Vec<u8>,
186 ) -> Self {
187 let mut request = HttpRequest::get(url.clone())
188 .header("Accept", "application/json")
189 .header("Authorization", auth.to_authorization())
190 .body(body);
191
192 if let Some(content_type) = content_type {
193 request = request.header("Content-Type", content_type);
194 }
195
196 request.method = method.into();
197
198 debug!("prepare request to send");
199 trace!("method: {method}");
200 trace!("url: {url}");
201
202 Self {
203 state: State::Send(Http11Send::new(request)),
204 _phantom: PhantomData,
205 }
206 }
207}
208
209impl<T: DeserializeOwned> MsgraphCoroutine for MsgraphSend<T> {
210 type Yield = MsgraphYield;
211 type Return = Result<MsgraphSendOutput<T>, MsgraphSendError>;
212
213 fn resume(&mut self, arg: Option<&[u8]>) -> MsgraphCoroutineState<Self::Yield, Self::Return> {
214 match &mut self.state {
215 State::Send(send) => match send.resume(arg) {
216 HttpCoroutineState::Yielded(HttpSendYield::WantsRead) => {
217 MsgraphCoroutineState::Yielded(MsgraphYield::WantsRead)
218 }
219 HttpCoroutineState::Yielded(HttpSendYield::WantsWrite(bytes)) => {
220 MsgraphCoroutineState::Yielded(MsgraphYield::WantsWrite(bytes))
221 }
222 HttpCoroutineState::Yielded(HttpSendYield::WantsRedirect { .. }) => {
223 MsgraphCoroutineState::Complete(Err(MsgraphSendError::UnexpectedRedirect))
224 }
225 HttpCoroutineState::Complete(Err(err)) => {
226 MsgraphCoroutineState::Complete(Err(err.into()))
227 }
228 HttpCoroutineState::Complete(Ok(HttpSendOutput {
229 response,
230 keep_alive,
231 ..
232 })) => {
233 if response.status.is_success() {
234 let body = if response.body.is_empty() {
235 b"null".as_slice()
236 } else {
237 response.body.as_slice()
238 };
239
240 match serde_json::from_slice::<T>(body) {
241 Ok(response) => {
242 MsgraphCoroutineState::Complete(Ok(MsgraphSendOutput {
243 response,
244 keep_alive,
245 }))
246 }
247 Err(err) => MsgraphCoroutineState::Complete(Err(
248 MsgraphSendError::ParseResponse(err),
249 )),
250 }
251 } else {
252 let (status, code, message) =
253 parse_api_error(*response.status, &response.body);
254 MsgraphCoroutineState::Complete(Err(MsgraphSendError::Api {
255 status,
256 code,
257 message,
258 }))
259 }
260 }
261 },
262 }
263 }
264}
265
266enum State {
267 Send(Http11Send),
268}
269
270#[derive(Debug, Deserialize)]
271struct ErrorEnvelope {
272 error: ErrorBody,
273}
274
275#[derive(Debug, Deserialize)]
276struct ErrorBody {
277 code: Option<String>,
278 message: Option<String>,
279}
280
281pub fn parse_api_error(http_status: u16, body: &[u8]) -> (u16, String, String) {
288 if let Ok(envelope) = serde_json::from_slice::<ErrorEnvelope>(body) {
289 let code = envelope
290 .error
291 .code
292 .filter(|code| !code.trim().is_empty())
293 .unwrap_or_else(|| String::from("unknown"));
294 let message = envelope
295 .error
296 .message
297 .filter(|message| !message.trim().is_empty())
298 .unwrap_or_else(|| String::from("unknown Microsoft Graph API error"));
299 return (http_status, code, message);
300 }
301
302 let message = String::from_utf8_lossy(body).trim().to_string();
303
304 if message.is_empty() {
305 (
306 http_status,
307 String::from("unknown"),
308 String::from("unknown Microsoft Graph API error"),
309 )
310 } else {
311 (http_status, String::from("unknown"), message)
312 }
313}