1use crate::id::{Id, State};
4use serde::{Deserialize, Serialize};
5use std::collections::HashMap;
6
7pub type Invocation = (String, serde_json::Value, String);
11
12#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
14#[non_exhaustive]
15pub struct JmapRequest {
16 pub using: Vec<String>,
18 #[serde(rename = "methodCalls")]
20 pub method_calls: Vec<Invocation>,
21 #[serde(rename = "createdIds", skip_serializing_if = "Option::is_none")]
23 pub created_ids: Option<HashMap<Id, Id>>,
24}
25
26#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
28#[non_exhaustive]
29pub struct JmapResponse {
30 #[serde(rename = "methodResponses")]
32 pub method_responses: Vec<Invocation>,
33 #[serde(rename = "sessionState")]
35 pub session_state: State,
36 #[serde(rename = "createdIds", skip_serializing_if = "Option::is_none")]
39 pub created_ids: Option<HashMap<Id, Id>>,
40}
41
42impl JmapRequest {
43 pub fn new(
48 using: Vec<String>,
49 method_calls: Vec<Invocation>,
50 created_ids: Option<HashMap<Id, Id>>,
51 ) -> Self {
52 Self {
53 using,
54 method_calls,
55 created_ids,
56 }
57 }
58}
59
60impl JmapResponse {
61 pub fn new(
66 method_responses: Vec<Invocation>,
67 session_state: State,
68 created_ids: Option<HashMap<Id, Id>>,
69 ) -> Self {
70 Self {
71 method_responses,
72 session_state,
73 created_ids,
74 }
75 }
76}
77
78#[cfg(test)]
79mod tests {
80 use super::*;
81 use serde_json::json;
82
83 #[test]
85 fn request_deserializes_from_rfc_fixture() {
86 let raw = include_str!("../tests/fixtures/rfc8620-request.json");
87 let req: JmapRequest = serde_json::from_str(raw).expect("deserialize JmapRequest");
88 assert_eq!(req.using.len(), 2);
89 assert_eq!(req.using[0], "urn:ietf:params:jmap:core");
90 assert_eq!(req.using[1], "urn:ietf:params:jmap:mail");
91 assert_eq!(req.method_calls.len(), 3);
92 assert_eq!(req.method_calls[0].0, "method1");
93 assert_eq!(req.method_calls[0].2, "c1");
94 assert_eq!(req.method_calls[2].0, "method3");
95 assert_eq!(req.method_calls[2].1, json!({}));
97 assert!(req.created_ids.is_none());
98 }
99
100 #[test]
102 fn response_deserializes_from_rfc_fixture() {
103 let raw = include_str!("../tests/fixtures/rfc8620-response.json");
104 let resp: JmapResponse = serde_json::from_str(raw).expect("deserialize JmapResponse");
105 assert_eq!(resp.session_state.as_ref(), "75128aab4b1b");
106 assert_eq!(resp.method_responses.len(), 4);
107 assert_eq!(resp.method_responses[0].0, "method1");
108 assert_eq!(resp.method_responses[3].0, "error");
109 assert_eq!(resp.method_responses[3].2, "c3");
110 assert!(resp.created_ids.is_none());
111 }
112
113 #[test]
115 fn request_serializes_camelcase() {
116 let req = JmapRequest {
117 using: vec!["urn:ietf:params:jmap:core".into()],
118 method_calls: vec![],
119 created_ids: None,
120 };
121 let j = serde_json::to_string(&req).expect("serialize");
122 assert!(
123 j.contains("\"methodCalls\""),
124 "must use camelCase methodCalls"
125 );
126 assert!(!j.contains("\"method_calls\""), "must not use snake_case");
127 }
128
129 #[test]
131 fn response_serializes_camelcase() {
132 let resp = JmapResponse {
133 method_responses: vec![],
134 session_state: "s-1".into(),
135 created_ids: None,
136 };
137 let j = serde_json::to_string(&resp).expect("serialize");
138 assert!(j.contains("\"methodResponses\""));
139 assert!(j.contains("\"sessionState\""));
140 }
141
142 #[test]
144 fn created_ids_absent_when_none() {
145 let resp = JmapResponse {
146 method_responses: vec![],
147 session_state: "s-1".into(),
148 created_ids: None,
149 };
150 let j = serde_json::to_string(&resp).expect("serialize");
151 assert!(
152 !j.contains("createdIds"),
153 "createdIds must be absent when None"
154 );
155 }
156
157 #[test]
159 fn created_ids_present_when_some() {
160 let mut ids = std::collections::HashMap::new();
161 ids.insert(Id::from("c0"), Id::from("server-1"));
162 let resp = JmapResponse {
163 method_responses: vec![],
164 session_state: "s-1".into(),
165 created_ids: Some(ids),
166 };
167 let v = serde_json::to_value(&resp).expect("serialize");
168 assert_eq!(v["createdIds"]["c0"], "server-1");
169 }
170
171 #[test]
173 fn invocation_is_three_element_array() {
174 let inv: Invocation = ("m/get".into(), json!({"accountId": "a1"}), "c0".into());
175 let j = serde_json::to_string(&inv).expect("serialize");
176 assert_eq!(j, r#"["m/get",{"accountId":"a1"},"c0"]"#);
177 }
178}