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