1use serde::{Deserialize, Serialize};
2use serde_json::Value;
3use std::collections::HashMap;
4
5#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone)]
8#[serde(bound = "T: Serialize + serde::de::DeserializeOwned")]
9pub struct PlainMessage<T = Value> {
10 pub id: String,
12
13 #[serde(default = "default_typ")]
15 pub typ: String,
16
17 #[serde(rename = "type")]
22 pub type_: String,
23
24 pub body: T,
26
27 pub from: String,
30
31 #[serde(default)]
35 pub to: Vec<String>,
36
37 #[serde(skip_serializing_if = "Option::is_none")]
40 pub thid: Option<String>,
41
42 #[serde(skip_serializing_if = "Option::is_none")]
45 pub pthid: Option<String>,
46
47 #[serde(flatten)]
49 #[serde(skip_serializing_if = "HashMap::is_empty")]
50 pub extra_headers: HashMap<String, Value>,
51
52 #[serde(skip_serializing_if = "Option::is_none")]
57 pub created_time: Option<u64>,
58
59 #[serde(skip_serializing_if = "Option::is_none")]
64 pub expires_time: Option<u64>,
65
66 #[serde(skip_serializing_if = "Option::is_none")]
68 pub from_prior: Option<String>,
69
70 #[serde(skip_serializing_if = "Option::is_none")]
71 pub attachments: Option<Vec<Attachment>>,
72}
73
74pub type UntypedPlainMessage = PlainMessage<Value>;
76
77const PLAINTEXT_TYP: &str = "application/didcomm-plain+json";
78
79fn default_typ() -> String {
80 PLAINTEXT_TYP.to_string()
81}
82
83impl<T> PlainMessage<T>
85where
86 T: serde::Serialize + serde::de::DeserializeOwned,
87{
88 pub fn new(id: String, type_: String, body: T, from: String) -> Self {
90 Self {
91 id,
92 typ: default_typ(),
93 type_,
94 body,
95 from,
96 to: vec![],
97 thid: None,
98 pthid: None,
99 created_time: Some(chrono::Utc::now().timestamp() as u64),
100 expires_time: None,
101 from_prior: None,
102 attachments: None,
103 extra_headers: HashMap::new(),
104 }
105 }
106
107 pub fn with_recipients(mut self, to: Vec<String>) -> Self {
109 self.to = to;
110 self
111 }
112
113 pub fn with_recipient(mut self, recipient: &str) -> Self {
115 self.to.push(recipient.to_string());
116 self
117 }
118
119 pub fn with_thread_id(mut self, thid: Option<String>) -> Self {
121 self.thid = thid;
122 self
123 }
124
125 pub fn with_parent_thread_id(mut self, pthid: Option<String>) -> Self {
127 self.pthid = pthid;
128 self
129 }
130
131 pub fn with_expires_at(mut self, expires_time: u64) -> Self {
133 self.expires_time = Some(expires_time);
134 self
135 }
136
137 pub fn with_attachments(mut self, attachments: Vec<Attachment>) -> Self {
139 self.attachments = Some(attachments);
140 self
141 }
142
143 pub fn with_header(mut self, key: String, value: Value) -> Self {
145 self.extra_headers.insert(key, value);
146 self
147 }
148}
149
150impl<T> PlainMessage<T>
152where
153 T: crate::message::TapMessageBody + serde::Serialize + serde::de::DeserializeOwned,
154{
155 pub fn new_typed(body: T, from: &str) -> Self {
157 Self {
158 id: uuid::Uuid::new_v4().to_string(),
159 typ: default_typ(),
160 type_: T::message_type().to_string(),
161 body,
162 from: from.to_string(),
163 to: vec![],
164 thid: None,
165 pthid: None,
166 created_time: Some(chrono::Utc::now().timestamp() as u64),
167 expires_time: None,
168 from_prior: None,
169 attachments: None,
170 extra_headers: HashMap::new(),
171 }
172 }
173
174 pub fn to_plain_message(self) -> crate::error::Result<PlainMessage<Value>> {
176 let mut body_value = serde_json::to_value(&self.body)?;
178
179 if let Some(body_obj) = body_value.as_object_mut() {
181 body_obj.insert(
182 "@type".to_string(),
183 Value::String(T::message_type().to_string()),
184 );
185 }
186
187 Ok(PlainMessage {
188 id: self.id,
189 typ: self.typ,
190 type_: self.type_,
191 body: body_value,
192 from: self.from,
193 to: self.to,
194 thid: self.thid,
195 pthid: self.pthid,
196 created_time: self.created_time,
197 expires_time: self.expires_time,
198 from_prior: self.from_prior,
199 attachments: self.attachments,
200 extra_headers: self.extra_headers,
201 })
202 }
203
204 pub fn extract_participants(&self) -> Vec<String> {
206 let mut participants = vec![];
207
208 if let Some(ctx_participants) = self.try_extract_from_context() {
210 participants = ctx_participants;
211 } else {
212 if let Ok(plain_msg) = self.body.to_didcomm(&self.from) {
214 participants = plain_msg.to;
215 }
216 }
217
218 for recipient in &self.to {
220 if !participants.contains(recipient) {
221 participants.push(recipient.clone());
222 }
223 }
224
225 participants
226 }
227
228 fn try_extract_from_context(&self) -> Option<Vec<String>> {
230 None
237 }
238}
239
240impl<T> PlainMessage<T>
242where
243 T: crate::message::TapMessageBody
244 + crate::message::MessageContext
245 + serde::Serialize
246 + serde::de::DeserializeOwned,
247{
248 pub fn extract_participants_with_context(&self) -> Vec<String> {
250 self.body.participant_dids()
251 }
252
253 pub fn new_typed_with_context(body: T, from: &str) -> Self {
255 let participants = body.participant_dids();
256
257 Self {
258 id: uuid::Uuid::new_v4().to_string(),
259 typ: default_typ(),
260 type_: T::message_type().to_string(),
261 body,
262 from: from.to_string(),
263 to: participants.into_iter().filter(|did| did != from).collect(),
264 thid: None,
265 pthid: None,
266 created_time: Some(chrono::Utc::now().timestamp() as u64),
267 expires_time: None,
268 from_prior: None,
269 attachments: None,
270 extra_headers: HashMap::new(),
271 }
272 }
273
274 pub fn routing_hints(&self) -> crate::message::RoutingHints {
276 self.body.routing_hints()
277 }
278
279 pub fn transaction_context(&self) -> Option<crate::message::TransactionContext> {
281 self.body.transaction_context()
282 }
283}
284
285impl PlainMessage<Value> {
287 pub fn from_untyped(plain_msg: PlainMessage<Value>) -> Self {
289 plain_msg
290 }
291
292 pub fn parse_body<T: crate::message::TapMessageBody>(
294 self,
295 ) -> crate::error::Result<PlainMessage<T>> {
296 if self.type_ != T::message_type() {
298 return Err(crate::error::Error::Validation(format!(
299 "Type mismatch: expected {}, got {}",
300 T::message_type(),
301 self.type_
302 )));
303 }
304
305 let typed_body: T = serde_json::from_value(self.body)?;
307
308 Ok(PlainMessage {
309 id: self.id,
310 typ: self.typ,
311 type_: self.type_,
312 body: typed_body,
313 from: self.from,
314 to: self.to,
315 thid: self.thid,
316 pthid: self.pthid,
317 created_time: self.created_time,
318 expires_time: self.expires_time,
319 from_prior: self.from_prior,
320 attachments: self.attachments,
321 extra_headers: self.extra_headers,
322 })
323 }
324
325 pub fn parse_tap_message(
327 &self,
328 ) -> crate::error::Result<crate::message::tap_message_enum::TapMessage> {
329 crate::message::tap_message_enum::TapMessage::from_plain_message(self)
330 }
331}
332
333pub trait PlainMessageExt<T> {
335 fn into_typed(self) -> PlainMessage<T>;
337
338 fn parse_as<U: crate::message::TapMessageBody>(self) -> crate::error::Result<PlainMessage<U>>;
340}
341
342impl PlainMessageExt<Value> for PlainMessage<Value> {
343 fn into_typed(self) -> PlainMessage<Value> {
344 self
345 }
346
347 fn parse_as<U: crate::message::TapMessageBody>(self) -> crate::error::Result<PlainMessage<U>> {
348 self.parse_body()
349 }
350}
351
352impl<T: crate::message::TapMessageBody> TryFrom<PlainMessage<T>>
354 for crate::message::tap_message_enum::TapMessage
355where
356 crate::message::tap_message_enum::TapMessage: From<T>,
357{
358 type Error = crate::error::Error;
359
360 fn try_from(typed: PlainMessage<T>) -> crate::error::Result<Self> {
361 typed.to_plain_message()?.parse_tap_message()
364 }
365}
366
367#[derive(Debug, Clone, Serialize, Deserialize)]
369pub struct OutOfBand {
370 #[serde(rename = "goal_code")]
372 pub goal_code: String,
373
374 pub id: String,
376
377 pub label: String,
379
380 pub accept: Option<String>,
382
383 pub services: Vec<serde_json::Value>,
385}
386
387#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
393pub struct SimpleAttachmentData {
394 #[serde(skip_serializing_if = "Option::is_none")]
396 pub base64: Option<String>,
397
398 #[serde(skip_serializing_if = "Option::is_none")]
400 pub json: Option<serde_json::Value>,
401}
402
403#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone)]
405pub struct Attachment {
406 pub data: AttachmentData,
409
410 #[serde(skip_serializing_if = "Option::is_none")]
417 pub id: Option<String>,
418
419 #[serde(skip_serializing_if = "Option::is_none")]
421 pub description: Option<String>,
422
423 #[serde(skip_serializing_if = "Option::is_none")]
427 pub filename: Option<String>,
428
429 #[serde(skip_serializing_if = "Option::is_none")]
431 pub media_type: Option<String>,
432
433 #[serde(skip_serializing_if = "Option::is_none")]
435 pub format: Option<String>,
436
437 #[serde(skip_serializing_if = "Option::is_none")]
440 pub lastmod_time: Option<u64>,
441
442 #[serde(skip_serializing_if = "Option::is_none")]
446 pub byte_count: Option<u64>,
447}
448
449impl Attachment {
450 pub fn base64(base64: String) -> AttachmentBuilder {
451 AttachmentBuilder::new(AttachmentData::Base64 {
452 value: Base64AttachmentData { base64, jws: None },
453 })
454 }
455
456 pub fn json(json: Value) -> AttachmentBuilder {
457 AttachmentBuilder::new(AttachmentData::Json {
458 value: JsonAttachmentData { json, jws: None },
459 })
460 }
461
462 pub fn links(links: Vec<String>, hash: String) -> AttachmentBuilder {
463 AttachmentBuilder::new(AttachmentData::Links {
464 value: LinksAttachmentData {
465 links,
466 hash,
467 jws: None,
468 },
469 })
470 }
471}
472
473pub struct AttachmentBuilder {
474 data: AttachmentData,
475 id: Option<String>,
476 description: Option<String>,
477 filename: Option<String>,
478 media_type: Option<String>,
479 format: Option<String>,
480 lastmod_time: Option<u64>,
481 byte_count: Option<u64>,
482}
483
484impl AttachmentBuilder {
485 fn new(data: AttachmentData) -> Self {
486 AttachmentBuilder {
487 data,
488 id: None,
489 description: None,
490 filename: None,
491 media_type: None,
492 format: None,
493 lastmod_time: None,
494 byte_count: None,
495 }
496 }
497
498 pub fn id(mut self, id: String) -> Self {
499 self.id = Some(id);
500 self
501 }
502
503 pub fn description(mut self, description: String) -> Self {
504 self.description = Some(description);
505 self
506 }
507
508 pub fn filename(mut self, filename: String) -> Self {
509 self.filename = Some(filename);
510 self
511 }
512
513 pub fn media_type(mut self, media_type: String) -> Self {
514 self.media_type = Some(media_type);
515 self
516 }
517
518 pub fn format(mut self, format: String) -> Self {
519 self.format = Some(format);
520 self
521 }
522
523 pub fn lastmod_time(mut self, lastmod_time: u64) -> Self {
524 self.lastmod_time = Some(lastmod_time);
525 self
526 }
527
528 pub fn byte_count(mut self, byte_count: u64) -> Self {
529 self.byte_count = Some(byte_count);
530 self
531 }
532
533 pub fn jws(mut self, jws: String) -> Self {
534 match self.data {
535 AttachmentData::Base64 { ref mut value } => value.jws = Some(jws),
536 AttachmentData::Json { ref mut value } => value.jws = Some(jws),
537 AttachmentData::Links { ref mut value } => value.jws = Some(jws),
538 }
539
540 self
541 }
542
543 pub fn finalize(self) -> Attachment {
544 Attachment {
545 data: self.data,
546 id: self.id,
547 description: self.description,
548 filename: self.filename,
549 media_type: self.media_type,
550 format: self.format,
551 lastmod_time: self.lastmod_time,
552 byte_count: self.byte_count,
553 }
554 }
555}
556
557#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone)]
564#[serde(untagged)]
565pub enum AttachmentData {
566 Base64 {
567 #[serde(flatten)]
568 value: Base64AttachmentData,
569 },
570 Json {
571 #[serde(flatten)]
572 value: JsonAttachmentData,
573 },
574 Links {
575 #[serde(flatten)]
576 value: LinksAttachmentData,
577 },
578}
579
580#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone)]
581pub struct Base64AttachmentData {
582 pub base64: String,
584
585 #[serde(skip_serializing_if = "Option::is_none")]
587 pub jws: Option<String>,
588}
589
590#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone)]
591pub struct JsonAttachmentData {
592 pub json: Value,
594
595 #[serde(skip_serializing_if = "Option::is_none")]
597 pub jws: Option<String>,
598}
599
600#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone)]
601pub struct LinksAttachmentData {
602 pub links: Vec<String>,
604
605 pub hash: String,
607
608 #[serde(skip_serializing_if = "Option::is_none")]
610 pub jws: Option<String>,
611}
612
613#[cfg(test)]
614mod tests {
615 use core::panic;
616 use serde_json::json;
617
618 use super::*;
619
620 #[test]
621 fn attachment_base64_works() {
622 let attachment = Attachment::base64("ZXhhbXBsZQ==".to_owned())
623 .id("example-1".to_owned())
624 .description("example-1-description".to_owned())
625 .filename("attachment-1".to_owned())
626 .media_type("message/example".to_owned())
627 .format("json".to_owned())
628 .lastmod_time(10000)
629 .byte_count(200)
630 .jws("jws".to_owned())
631 .finalize();
632
633 let data = match attachment.data {
634 AttachmentData::Base64 { ref value } => value,
635 _ => panic!("data isn't base64."),
636 };
637
638 assert_eq!(data.base64, "ZXhhbXBsZQ==");
639 assert_eq!(data.jws, Some("jws".to_owned()));
640 assert_eq!(attachment.id, Some("example-1".to_owned()));
641
642 assert_eq!(
643 attachment.description,
644 Some("example-1-description".to_owned())
645 );
646
647 assert_eq!(attachment.filename, Some("attachment-1".to_owned()));
648 assert_eq!(attachment.media_type, Some("message/example".to_owned()));
649 assert_eq!(attachment.format, Some("json".to_owned()));
650 assert_eq!(attachment.lastmod_time, Some(10000));
651 assert_eq!(attachment.byte_count, Some(200));
652 }
653
654 #[test]
655 fn attachment_json_works() {
656 let attachment = Attachment::json(json!("example"))
657 .id("example-1".to_owned())
658 .description("example-1-description".to_owned())
659 .filename("attachment-1".to_owned())
660 .media_type("message/example".to_owned())
661 .format("json".to_owned())
662 .lastmod_time(10000)
663 .byte_count(200)
664 .jws("jws".to_owned())
665 .finalize();
666
667 let data = match attachment.data {
668 AttachmentData::Json { ref value } => value,
669 _ => panic!("data isn't json."),
670 };
671
672 assert_eq!(data.json, json!("example"));
673 assert_eq!(data.jws, Some("jws".to_owned()));
674 assert_eq!(attachment.id, Some("example-1".to_owned()));
675
676 assert_eq!(
677 attachment.description,
678 Some("example-1-description".to_owned())
679 );
680
681 assert_eq!(attachment.filename, Some("attachment-1".to_owned()));
682 assert_eq!(attachment.media_type, Some("message/example".to_owned()));
683 assert_eq!(attachment.format, Some("json".to_owned()));
684 assert_eq!(attachment.lastmod_time, Some(10000));
685 assert_eq!(attachment.byte_count, Some(200));
686 }
687
688 #[test]
689 fn attachment_links_works() {
690 let attachment = Attachment::links(
691 vec!["http://example1".to_owned(), "https://example2".to_owned()],
692 "50d858e0985ecc7f60418aaf0cc5ab587f42c2570a884095a9e8ccacd0f6545c".to_owned(),
693 )
694 .id("example-1".to_owned())
695 .description("example-1-description".to_owned())
696 .filename("attachment-1".to_owned())
697 .media_type("message/example".to_owned())
698 .format("json".to_owned())
699 .lastmod_time(10000)
700 .byte_count(200)
701 .jws("jws".to_owned())
702 .finalize();
703
704 let data = match attachment.data {
705 AttachmentData::Links { ref value } => value,
706 _ => panic!("data isn't links."),
707 };
708
709 assert_eq!(
710 data.links,
711 vec!["http://example1".to_owned(), "https://example2".to_owned()]
712 );
713
714 assert_eq!(
715 data.hash,
716 "50d858e0985ecc7f60418aaf0cc5ab587f42c2570a884095a9e8ccacd0f6545c".to_owned()
717 );
718
719 assert_eq!(data.jws, Some("jws".to_owned()));
720 assert_eq!(attachment.id, Some("example-1".to_owned()));
721
722 assert_eq!(
723 attachment.description,
724 Some("example-1-description".to_owned())
725 );
726
727 assert_eq!(attachment.filename, Some("attachment-1".to_owned()));
728 assert_eq!(attachment.media_type, Some("message/example".to_owned()));
729 assert_eq!(attachment.format, Some("json".to_owned()));
730 assert_eq!(attachment.lastmod_time, Some(10000));
731 assert_eq!(attachment.byte_count, Some(200));
732 }
733}