1use core::{error::Error, fmt};
5
6use alloc::{collections::BTreeMap, format, string::String, vec::Vec};
7
8use serde::{Deserialize, Serialize};
9use serde_json::Value;
10use url::Url;
11
12#[derive(Clone, Debug, Serialize, Deserialize)]
14#[serde(rename_all = "camelCase")]
15pub struct JmapSession {
16 pub username: String,
17 pub accounts: BTreeMap<String, JmapAccountInfo>,
18 pub primary_accounts: BTreeMap<String, String>,
19 pub capabilities: BTreeMap<String, Value>,
20 pub api_url: Url,
21 pub download_url: String,
22 pub upload_url: String,
23 pub event_source_url: String,
24 pub state: String,
25}
26
27impl JmapSession {
28 pub fn primary_account_id_for(&self, capability: &str) -> String {
31 self.primary_accounts
32 .get(capability)
33 .cloned()
34 .unwrap_or_default()
35 }
36}
37
38#[derive(Clone, Debug, Serialize, Deserialize)]
40#[serde(rename_all = "camelCase")]
41pub struct JmapAccountInfo {
42 pub name: String,
43 pub is_personal: bool,
44 pub is_read_only: bool,
45 pub account_capabilities: BTreeMap<String, Value>,
46}
47
48#[derive(Clone, Debug, Serialize, Deserialize)]
50#[serde(tag = "type", rename_all = "camelCase")]
51pub enum JmapMethodError {
52 ServerFail {
53 description: Option<String>,
54 },
55 ServerPartialFail,
56 ServerUnavailable {
57 description: Option<String>,
58 },
59 UnknownCapability {
60 description: Option<String>,
61 },
62 NotJson {
63 description: Option<String>,
64 },
65 NotRequest {
66 description: Option<String>,
67 },
68 Limit {
69 description: Option<String>,
70 limit: String,
71 },
72 InvalidArguments {
73 description: Option<String>,
74 },
75 RequestTooLarge,
76 NotFound,
77 InvalidPatch {
78 description: Option<String>,
79 },
80 WillDestroy {
81 description: Option<String>,
82 },
83 InvalidProperties {
84 description: Option<String>,
85 #[serde(default)]
86 properties: Vec<String>,
87 },
88 Singleton,
89 UnknownMethod {
90 description: Option<String>,
91 },
92 CannotCalculateChanges {
95 description: Option<String>,
96 },
97 #[serde(other)]
98 Unknown,
99}
100
101impl fmt::Display for JmapMethodError {
102 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
103 match self {
104 Self::ServerFail { description } => {
105 write!(f, "JMAP serverFail")?;
106 if let Some(d) = description {
107 write!(f, ": {d}")?;
108 }
109 Ok(())
110 }
111 Self::ServerPartialFail => write!(f, "JMAP serverPartialFail"),
112 Self::ServerUnavailable { description } => {
113 write!(f, "JMAP serverUnavailable")?;
114 if let Some(d) = description {
115 write!(f, ": {d}")?;
116 }
117 Ok(())
118 }
119 Self::UnknownCapability { description } => {
120 write!(f, "JMAP unknownCapability")?;
121 if let Some(d) = description {
122 write!(f, ": {d}")?;
123 }
124 Ok(())
125 }
126 Self::NotJson { description } => {
127 write!(f, "JMAP notJson")?;
128 if let Some(d) = description {
129 write!(f, ": {d}")?;
130 }
131 Ok(())
132 }
133 Self::NotRequest { description } => {
134 write!(f, "JMAP notRequest")?;
135 if let Some(d) = description {
136 write!(f, ": {d}")?;
137 }
138 Ok(())
139 }
140 Self::Limit { description, limit } => {
141 write!(f, "JMAP limit ({limit})")?;
142 if let Some(d) = description {
143 write!(f, ": {d}")?;
144 }
145 Ok(())
146 }
147 Self::InvalidArguments { description } => {
148 write!(f, "JMAP invalidArguments")?;
149 if let Some(d) = description {
150 write!(f, ": {d}")?;
151 }
152 Ok(())
153 }
154 Self::RequestTooLarge => write!(f, "JMAP requestTooLarge"),
155 Self::NotFound => write!(f, "JMAP notFound"),
156 Self::InvalidPatch { description } => {
157 write!(f, "JMAP invalidPatch")?;
158 if let Some(d) = description {
159 write!(f, ": {d}")?;
160 }
161 Ok(())
162 }
163 Self::WillDestroy { description } => {
164 write!(f, "JMAP willDestroy")?;
165 if let Some(d) = description {
166 write!(f, ": {d}")?;
167 }
168 Ok(())
169 }
170 Self::InvalidProperties {
171 description,
172 properties,
173 } => {
174 write!(f, "JMAP invalidProperties")?;
175 if !properties.is_empty() {
176 write!(f, " [{}]", properties.join(", "))?;
177 }
178 if let Some(d) = description {
179 write!(f, ": {d}")?;
180 }
181 Ok(())
182 }
183 Self::Singleton => write!(f, "JMAP singleton"),
184 Self::UnknownMethod { description } => {
185 write!(f, "JMAP unknownMethod")?;
186 if let Some(d) = description {
187 write!(f, ": {d}")?;
188 }
189 Ok(())
190 }
191 Self::CannotCalculateChanges { description } => {
192 write!(f, "JMAP cannotCalculateChanges")?;
193 if let Some(d) = description {
194 write!(f, ": {d}")?;
195 }
196 Ok(())
197 }
198 Self::Unknown => write!(f, "JMAP unknown error"),
199 }
200 }
201}
202
203impl Error for JmapMethodError {}
204
205#[derive(Clone, Debug, Deserialize)]
207#[serde(rename_all = "camelCase")]
208pub struct JmapSetError {
209 pub r#type: String,
210 pub description: Option<String>,
211 #[serde(default)]
212 pub properties: Vec<String>,
213}
214
215#[derive(Clone, Debug, Serialize, Deserialize)]
220#[serde(untagged)]
221pub enum JmapFilter<C> {
222 Operator(JmapFilterOperator<C>),
223 Condition(C),
224}
225
226impl<C> From<C> for JmapFilter<C> {
227 fn from(condition: C) -> Self {
228 JmapFilter::Condition(condition)
229 }
230}
231
232impl<C> JmapFilter<C> {
233 pub fn and(conditions: Vec<JmapFilter<C>>) -> Self {
235 JmapFilter::Operator(JmapFilterOperator {
236 operator: JmapFilterOperatorKind::And,
237 conditions,
238 })
239 }
240
241 pub fn or(conditions: Vec<JmapFilter<C>>) -> Self {
243 JmapFilter::Operator(JmapFilterOperator {
244 operator: JmapFilterOperatorKind::Or,
245 conditions,
246 })
247 }
248
249 pub fn not(conditions: Vec<JmapFilter<C>>) -> Self {
252 JmapFilter::Operator(JmapFilterOperator {
253 operator: JmapFilterOperatorKind::Not,
254 conditions,
255 })
256 }
257}
258
259#[derive(Clone, Debug, Serialize, Deserialize)]
261#[serde(rename_all = "camelCase")]
262pub struct JmapFilterOperator<C> {
263 pub operator: JmapFilterOperatorKind,
264 pub conditions: Vec<JmapFilter<C>>,
265}
266
267#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
269#[serde(rename_all = "UPPERCASE")]
270pub enum JmapFilterOperatorKind {
271 And,
272 Or,
273 Not,
274}
275
276#[derive(Serialize)]
279#[serde(rename_all = "camelCase")]
280pub struct JmapResultReference<'a> {
281 pub result_of: &'a str,
282 pub name: &'static str,
283 pub path: &'static str,
284}
285
286#[derive(Clone, Debug, Serialize)]
288#[serde(rename_all = "camelCase")]
289pub struct JmapRequest {
290 pub using: Vec<String>,
292
293 pub method_calls: Vec<(String, Value, String)>,
296
297 #[serde(skip_serializing_if = "Option::is_none")]
299 pub created_ids: Option<BTreeMap<String, String>>,
300}
301
302#[derive(Clone, Debug, Deserialize)]
304#[serde(rename_all = "camelCase")]
305pub struct JmapResponse {
306 pub method_responses: Vec<(String, Value, String)>,
311
312 #[serde(default)]
314 pub created_ids: Option<BTreeMap<String, String>>,
315
316 pub session_state: String,
318}
319
320#[derive(Debug, Default)]
323pub struct JmapBatch {
324 calls: Vec<(String, Value, String)>,
325 counter: usize,
326}
327
328impl JmapBatch {
329 pub fn new() -> Self {
331 Self::default()
332 }
333
334 pub fn add(&mut self, method: impl Into<String>, args: Value) -> String {
337 let call_id = format!("c{}", self.counter);
338 self.counter += 1;
339 self.calls.push((method.into(), args, call_id.clone()));
340 call_id
341 }
342
343 pub fn into_request(self, using: Vec<String>) -> JmapRequest {
345 JmapRequest {
346 using,
347 method_calls: self.calls,
348 created_ids: None,
349 }
350 }
351}
352
353#[derive(Clone, Debug, Deserialize)]
355pub struct JmapAddedItem {
356 pub id: String,
357 pub index: u64,
358}
359
360#[cfg(test)]
361mod tests {
362 use alloc::vec;
363
364 use serde_json::json;
365
366 use super::*;
367
368 #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
369 struct Cond {
370 from: Option<String>,
371 }
372
373 #[test]
374 fn condition_serializes_flat() {
375 let f: JmapFilter<Cond> = JmapFilter::Condition(Cond {
376 from: Some("alice".into()),
377 });
378 assert_eq!(
379 serde_json::to_value(&f).unwrap(),
380 json!({ "from": "alice" })
381 );
382 }
383
384 #[test]
385 fn and_serializes_with_operator_key() {
386 let f: JmapFilter<Cond> = JmapFilter::and(vec![
387 JmapFilter::Condition(Cond {
388 from: Some("a".into()),
389 }),
390 JmapFilter::Condition(Cond {
391 from: Some("b".into()),
392 }),
393 ]);
394 assert_eq!(
395 serde_json::to_value(&f).unwrap(),
396 json!({
397 "operator": "AND",
398 "conditions": [
399 { "from": "a" },
400 { "from": "b" },
401 ],
402 }),
403 );
404 }
405
406 #[test]
407 fn not_wraps_a_single_subfilter() {
408 let f: JmapFilter<Cond> = JmapFilter::not(vec![JmapFilter::Condition(Cond {
409 from: Some("a".into()),
410 })]);
411 assert_eq!(
412 serde_json::to_value(&f).unwrap(),
413 json!({
414 "operator": "NOT",
415 "conditions": [{ "from": "a" }],
416 }),
417 );
418 }
419
420 #[test]
421 fn deserialize_discriminates_on_operator_key() {
422 let v = json!({ "from": "alice" });
423 let f: JmapFilter<Cond> = serde_json::from_value(v).unwrap();
424 assert!(matches!(f, JmapFilter::Condition(Cond { from: Some(_) })));
425
426 let v = json!({
427 "operator": "OR",
428 "conditions": [{ "from": "a" }, { "from": "b" }],
429 });
430 let f: JmapFilter<Cond> = serde_json::from_value(v).unwrap();
431 assert!(matches!(
432 f,
433 JmapFilter::Operator(JmapFilterOperator {
434 operator: JmapFilterOperatorKind::Or,
435 ..
436 })
437 ));
438 }
439}