1use serde::{Deserialize, Serialize};
2use serde_json::Value;
3use std::collections::HashMap;
4
5use super::Attachment;
6use crate::error::{err_msg, ErrorKind, Result, ToResult};
7
8#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone)]
10pub struct Message {
11 pub id: String,
13
14 #[serde(default = "default_typ")]
16 pub typ: String,
17
18 #[serde(rename = "type")]
23 pub type_: String,
24
25 pub body: Value,
27
28 #[serde(skip_serializing_if = "Option::is_none")]
31 pub from: Option<String>,
32
33 #[serde(skip_serializing_if = "Option::is_none")]
37 pub to: Option<Vec<String>>,
38
39 #[serde(skip_serializing_if = "Option::is_none")]
42 pub thid: Option<String>,
43
44 #[serde(skip_serializing_if = "Option::is_none")]
47 pub pthid: Option<String>,
48
49 #[serde(flatten)]
51 #[serde(skip_serializing_if = "HashMap::is_empty")]
52 pub extra_headers: HashMap<String, Value>,
53
54 #[serde(skip_serializing_if = "Option::is_none")]
59 pub created_time: Option<u64>,
60
61 #[serde(skip_serializing_if = "Option::is_none")]
66 pub expires_time: Option<u64>,
67
68 #[serde(skip_serializing_if = "Option::is_none")]
70 pub from_prior: Option<String>,
71
72 #[serde(skip_serializing_if = "Option::is_none")]
74 pub attachments: Option<Vec<Attachment>>,
75}
76
77const PLAINTEXT_TYP: &str = "application/didcomm-plain+json";
78
79fn default_typ() -> String {
80 PLAINTEXT_TYP.to_string()
81}
82
83impl Message {
84 pub fn build(id: String, type_: String, body: Value) -> MessageBuilder {
85 MessageBuilder::new(id, type_, body)
86 }
87
88 pub(crate) fn from_str(s: &str) -> Result<Message> {
89 serde_json::from_str(s).to_didcomm("Unable deserialize jwm")
90 }
91
92 pub(crate) fn validate(self) -> Result<Self> {
93 if self.typ != PLAINTEXT_TYP {
94 Err(err_msg(
95 ErrorKind::Malformed,
96 format!("`typ` must be \"{}\"", PLAINTEXT_TYP),
97 ))?;
98 }
99 Ok(self)
100 }
101}
102
103pub struct MessageBuilder {
104 id: String,
105 type_: String,
106 body: Value,
107 from: Option<String>,
108 to: Option<Vec<String>>,
109 thid: Option<String>,
110 pthid: Option<String>,
111 extra_headers: HashMap<String, Value>,
112 created_time: Option<u64>,
113 expires_time: Option<u64>,
114 from_prior: Option<String>,
115 attachments: Option<Vec<Attachment>>,
116}
117
118impl MessageBuilder {
119 fn new(id: String, type_: String, body: Value) -> Self {
120 MessageBuilder {
121 id,
122 type_,
123 body,
124 from: None,
125 to: None,
126 thid: None,
127 pthid: None,
128 extra_headers: HashMap::new(),
129 created_time: None,
130 expires_time: None,
131 from_prior: None,
132 attachments: None,
133 }
134 }
135
136 pub fn to(mut self, to: String) -> Self {
137 if let Some(ref mut sto) = self.to {
138 sto.push(to);
139 self
140 } else {
141 self.to = Some(vec![to]);
142 self
143 }
144 }
145
146 pub fn to_many(mut self, to: Vec<String>) -> Self {
147 if let Some(ref mut sto) = self.to {
148 let mut to = to;
149 sto.append(&mut to);
150 self
151 } else {
152 self.to = Some(to);
153 self
154 }
155 }
156
157 pub fn from(mut self, from: String) -> Self {
158 self.from = Some(from);
159 self
160 }
161
162 pub fn thid(mut self, thid: String) -> Self {
163 self.thid = Some(thid);
164 self
165 }
166
167 pub fn pthid(mut self, pthid: String) -> Self {
168 self.pthid = Some(pthid);
169 self
170 }
171
172 pub fn header(mut self, key: String, value: Value) -> Self {
173 self.extra_headers.insert(key, value);
174 self
175 }
176
177 pub fn created_time(mut self, created_time: u64) -> Self {
178 self.created_time = Some(created_time);
179 self
180 }
181
182 pub fn expires_time(mut self, expires_time: u64) -> Self {
183 self.expires_time = Some(expires_time);
184 self
185 }
186
187 pub fn from_prior(mut self, from_prior: String) -> Self {
188 self.from_prior = Some(from_prior);
189 self
190 }
191
192 pub fn attachment(mut self, attachment: Attachment) -> Self {
193 if let Some(ref mut attachments) = self.attachments {
194 attachments.push(attachment);
195 self
196 } else {
197 self.attachments = Some(vec![attachment]);
198 self
199 }
200 }
201
202 pub fn attachments(mut self, attachments: Vec<Attachment>) -> Self {
203 if let Some(ref mut sattachments) = self.attachments {
204 let mut attachments = attachments;
205 sattachments.append(&mut attachments);
206 self
207 } else {
208 self.attachments = Some(attachments);
209 self
210 }
211 }
212
213 pub fn finalize(self) -> Message {
214 Message {
215 id: self.id,
216 typ: PLAINTEXT_TYP.to_owned(),
217 type_: self.type_,
218 body: self.body,
219 to: self.to,
220 thid: self.thid,
221 pthid: self.pthid,
222 from: self.from,
223 extra_headers: self.extra_headers,
224 created_time: self.created_time,
225 expires_time: self.expires_time,
226 from_prior: self.from_prior,
227 attachments: self.attachments,
228 }
229 }
230}
231
232#[cfg(test)]
233mod tests {
234 use serde_json::json;
235
236 use super::*;
237
238 #[test]
239 fn message_build_works() {
240 let message = Message::build(
241 "example-1".into(),
242 "example/v1".into(),
243 json!("example-body"),
244 )
245 .to("did:example:1".into())
246 .to_many(vec!["did:example:2".into(), "did:example:3".into()])
247 .from("did:example:4".into())
248 .thid("example-thread-1".into())
249 .pthid("example-parent-thread-1".into())
250 .header("example-header-1".into(), json!("example-header-1-value"))
251 .header("example-header-2".into(), json!("example-header-2-value"))
252 .created_time(10000)
253 .expires_time(20000)
254 .attachment(
255 Attachment::base64("ZXhhbXBsZQ==".into())
256 .id("attachment1".into())
257 .finalize(),
258 )
259 .attachments(vec![
260 Attachment::json(json!("example"))
261 .id("attachment2".into())
262 .finalize(),
263 Attachment::json(json!("example"))
264 .id("attachment3".into())
265 .finalize(),
266 ])
267 .finalize();
268
269 assert_eq!(message.id, "example-1");
270 assert_eq!(message.typ, "application/didcomm-plain+json");
271 assert_eq!(message.type_, "example/v1");
272 assert_eq!(message.body, json!("example-body"));
273 assert_eq!(message.from, Some("did:example:4".into()));
274 assert_eq!(message.thid, Some("example-thread-1".into()));
275 assert_eq!(message.pthid, Some("example-parent-thread-1".into()));
276 assert_eq!(message.created_time, Some(10000));
277 assert_eq!(message.expires_time, Some(20000));
278
279 assert_eq!(
280 message.to,
281 Some(vec![
282 "did:example:1".into(),
283 "did:example:2".into(),
284 "did:example:3".into()
285 ])
286 );
287
288 let extra_headers = message.extra_headers;
289 assert_eq!(extra_headers.len(), 2);
290
291 assert!(extra_headers.contains_key(&"example-header-1".to_owned()));
292
293 assert_eq!(
294 extra_headers[&"example-header-1".to_owned()],
295 "example-header-1-value"
296 );
297
298 assert!(extra_headers.contains_key(&"example-header-2".to_owned()));
299
300 assert_eq!(
301 extra_headers[&"example-header-2".to_owned()],
302 "example-header-2-value"
303 );
304
305 let attachments = message.attachments.expect("attachments is some.");
306 assert_eq!(attachments.len(), 3);
307 assert_eq!(attachments[0].id, Some("attachment1".into()));
308 assert_eq!(attachments[1].id, Some("attachment2".into()));
309 assert_eq!(attachments[2].id, Some("attachment3".into()));
310 }
311}