tap_msg/message/
did_presentation.rs1use serde::{Deserialize, Serialize};
6
7use crate::didcomm::Attachment;
8use crate::error::{Error, Result};
9use crate::message::tap_message_trait::TapMessageBody;
10use crate::TapMessage;
11
12fn default_id() -> String {
13 uuid::Uuid::new_v4().to_string()
14}
15
16#[derive(Debug, Clone, Serialize, Deserialize, TapMessage)]
18pub struct DIDCommPresentation {
19 #[serde(default = "default_id")]
21 pub id: String,
22
23 #[serde(default)]
25 pub formats: Vec<String>,
26
27 pub attachments: Vec<Attachment>,
29
30 #[serde(skip_serializing_if = "Option::is_none")]
32 #[tap(thread_id)]
33 pub thid: Option<String>,
34}
35
36impl DIDCommPresentation {
37 pub fn new(formats: Vec<String>, attachments: Vec<Attachment>, thid: Option<String>) -> Self {
39 Self {
40 id: default_id(),
41 formats,
42 attachments,
43 thid,
44 }
45 }
46}
47
48impl TapMessageBody for DIDCommPresentation {
49 fn message_type() -> &'static str {
50 "https://didcomm.org/present-proof/3.0/presentation"
51 }
52
53 fn validate(&self) -> Result<()> {
54 if self.attachments.is_empty() {
56 return Err(Error::Validation(
57 "Presentation must have at least one attachment".to_string(),
58 ));
59 }
60
61 for (i, attachment) in self.attachments.iter().enumerate() {
63 if let Some(id) = &attachment.id {
64 if id.is_empty() {
65 return Err(Error::Validation(format!(
66 "Attachment {} has an empty ID",
67 i
68 )));
69 }
70 }
71 }
72
73 if self.formats.is_empty() {
75 return Err(Error::Validation(
76 "Presentation must have at least one format specified".to_string(),
77 ));
78 }
79
80 for (i, attachment) in self.attachments.iter().enumerate() {
82 if attachment.format.is_none() {
83 return Err(Error::Validation(format!(
84 "Attachment {} is missing the 'format' field",
85 i
86 )));
87 }
88 }
89
90 Ok(())
91 }
92
93 fn to_didcomm(&self, from: &str) -> Result<crate::didcomm::PlainMessage> {
94 let body_json =
96 serde_json::to_value(self).map_err(|e| Error::SerializationError(e.to_string()))?;
97
98 let now = chrono::Utc::now().timestamp() as u64;
99
100 let message = crate::didcomm::PlainMessage {
102 id: uuid::Uuid::new_v4().to_string(),
103 typ: "application/didcomm-plain+json".to_string(),
104 type_: Self::message_type().to_string(),
105 body: body_json,
106 from: from.to_string(),
107 to: Vec::new(), thid: self.thid.clone(),
109 pthid: None,
110 created_time: Some(now),
111 expires_time: None,
112 extra_headers: std::collections::HashMap::new(),
113 from_prior: None,
114 attachments: None,
115 };
116
117 Ok(message)
118 }
119
120 fn from_didcomm(message: &crate::didcomm::PlainMessage) -> Result<Self> {
121 if message.type_ != Self::message_type() {
123 return Err(Error::InvalidMessageType(format!(
124 "Expected {} but got {}",
125 Self::message_type(),
126 message.type_
127 )));
128 }
129
130 let body = message.body.clone();
132 let mut body_obj = body
133 .as_object()
134 .ok_or_else(|| Error::SerializationError("Body is not a JSON object".to_string()))?
135 .clone();
136
137 let mut attachments_in_body = if body_obj.contains_key("attachments") {
139 match &body_obj["attachments"] {
140 serde_json::Value::Array(arr) => arr.clone(),
141 _ => Vec::new(),
142 }
143 } else {
144 Vec::new()
145 };
146
147 if let Some(msg_attachments) = &message.attachments {
149 if let Ok(serde_json::Value::Array(arr)) = serde_json::to_value(msg_attachments) {
151 attachments_in_body.extend(arr);
152 }
153 }
154
155 body_obj.insert(
157 "attachments".to_string(),
158 serde_json::Value::Array(attachments_in_body.clone()),
159 );
160
161 if !body_obj.contains_key("formats") {
163 let mut formats = Vec::new();
165 for attachment in &attachments_in_body {
166 if let Some(format) = attachment.get("format") {
167 if let Some(format_str) = format.as_str() {
168 formats.push(format_str.to_string());
169 }
170 }
171 }
172
173 if formats.is_empty() {
175 formats.push("dif/presentation-exchange/submission@v1.0".to_string());
176 }
177
178 body_obj.insert(
179 "formats".to_string(),
180 serde_json::to_value(formats).unwrap(),
181 );
182 }
183
184 let mut presentation: DIDCommPresentation =
186 serde_json::from_value(serde_json::Value::Object(body_obj))
187 .map_err(|e| Error::SerializationError(e.to_string()))?;
188
189 if presentation.thid.is_none() {
191 presentation.thid = message.thid.clone();
192 }
193
194 Ok(presentation)
195 }
196}