io_msgraph/v1/rest/
batch.rs1use alloc::{collections::BTreeMap, format, string::String, vec::Vec};
7
8use io_http::rfc6750::bearer::HttpAuthBearer;
9use log::{debug, trace};
10use serde::{Deserialize, Serialize, de::DeserializeOwned};
11use serde_json::Value;
12use url::Url;
13
14use crate::{
15 coroutine::*,
16 msgraph_try,
17 v1::send::{
18 MSGRAPH_API_BASE, MsgraphSend, MsgraphSendError, MsgraphSendOutput, parse_api_error,
19 },
20};
21
22pub const MSGRAPH_BATCH_MAX_REQUESTS: usize = 20;
24
25#[derive(Debug, Clone, Default, Deserialize, Serialize, Eq, PartialEq)]
27#[serde(rename_all = "camelCase")]
28pub struct MsgraphBatchRequest {
29 pub id: String,
32 pub method: String,
34 pub url: String,
37 #[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
39 pub headers: BTreeMap<String, String>,
40 #[serde(default, skip_serializing_if = "Option::is_none")]
42 pub body: Option<Value>,
43 #[serde(default, skip_serializing_if = "Vec::is_empty")]
45 pub depends_on: Vec<String>,
46}
47
48#[derive(Debug, Clone, Default, Deserialize, Serialize, Eq, PartialEq)]
50pub struct MsgraphBatchResponse {
51 pub id: String,
53 pub status: u16,
55 #[serde(default)]
57 pub headers: BTreeMap<String, String>,
58 #[serde(default)]
60 pub body: Option<Value>,
61}
62
63impl MsgraphBatchResponse {
64 pub fn parse<T: DeserializeOwned>(self) -> Result<T, MsgraphSendError> {
67 let body = self.body.unwrap_or(Value::Null);
68
69 if (200..300).contains(&self.status) {
70 return serde_json::from_value(body).map_err(MsgraphSendError::ParseResponse);
71 }
72
73 let body = serde_json::to_vec(&body).unwrap_or_default();
74 let (status, code, message) = parse_api_error(self.status, &body);
75 Err(MsgraphSendError::Api {
76 status,
77 code,
78 message,
79 })
80 }
81}
82
83#[derive(Debug, Clone, Default, Deserialize, Serialize, Eq, PartialEq)]
85pub struct MsgraphBatchResponses {
86 #[serde(default)]
88 pub responses: Vec<MsgraphBatchResponse>,
89}
90
91#[derive(Serialize)]
92struct Body<'a> {
93 requests: &'a [MsgraphBatchRequest],
94}
95
96pub struct MsgraphBatch {
98 send: MsgraphSend<MsgraphBatchResponses>,
99}
100
101impl MsgraphBatch {
102 pub fn new(
105 auth: &HttpAuthBearer,
106 requests: &[MsgraphBatchRequest],
107 ) -> Result<Self, MsgraphSendError> {
108 debug!("prepare microsoft graph batch");
109 trace!("requests: {requests:?}");
110
111 if requests.is_empty() || requests.len() > MSGRAPH_BATCH_MAX_REQUESTS {
112 let err = MsgraphSendError::InvalidRequest(format!(
113 "Batch must hold between 1 and {MSGRAPH_BATCH_MAX_REQUESTS} requests"
114 ));
115 return Err(err);
116 }
117
118 for (i, request) in requests.iter().enumerate() {
119 if requests[..i].iter().any(|prev| prev.id == request.id) {
120 let err = MsgraphSendError::InvalidRequest(format!(
121 "Batch request id `{}` is not unique",
122 request.id
123 ));
124 return Err(err);
125 }
126 }
127
128 let url = Url::parse(MSGRAPH_API_BASE)?.join("$batch")?;
129 let send = MsgraphSend::post_json(auth, url, &Body { requests })?;
130
131 Ok(Self { send })
132 }
133}
134
135impl MsgraphCoroutine for MsgraphBatch {
136 type Yield = MsgraphYield;
137 type Return = Result<MsgraphSendOutput<MsgraphBatchResponses>, MsgraphSendError>;
138
139 fn resume(&mut self, arg: Option<&[u8]>) -> MsgraphCoroutineState<Self::Yield, Self::Return> {
140 let out = msgraph_try!(&mut self.send, arg);
141 debug!("batch sent");
142 trace!("out: {out:?}");
143 MsgraphCoroutineState::Complete(Ok(out))
144 }
145}