co_didcomm/messages/
attachment.rs1use std::convert::TryFrom;
2
3use serde::{Deserialize, Serialize};
4
5use crate::{Error, Message, Result};
6
7#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq, Default)]
10pub struct Attachment {
11 #[serde(skip_serializing_if = "Option::is_none")]
12 pub id: Option<String>,
13 #[serde(skip_serializing_if = "Option::is_none")]
14 pub description: Option<String>,
15 #[serde(skip_serializing_if = "Option::is_none")]
16 pub filename: Option<String>,
17 #[serde(skip_serializing_if = "Option::is_none")]
18 pub media_type: Option<String>,
19 #[serde(skip_serializing_if = "Option::is_none")]
20 pub format: Option<String>,
21 #[serde(skip_serializing_if = "Option::is_none")]
22 pub lastmod_time: Option<String>,
23 #[serde(skip_serializing_if = "Option::is_none")]
24 pub byte_count: Option<usize>,
25 pub data: AttachmentData,
26}
27
28#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq, Default)]
30pub struct AttachmentData {
31 #[serde(skip_serializing_if = "Option::is_none")]
32 pub jws: Option<String>,
33 #[serde(skip_serializing_if = "Option::is_none")]
34 pub hash: Option<String>,
35 #[serde(default)]
36 #[serde(skip_serializing_if = "Vec::is_empty")]
37 pub links: Vec<String>,
38 #[serde(skip_serializing_if = "Option::is_none")]
39 pub base64: Option<String>,
40 #[serde(skip_serializing_if = "Option::is_none")]
41 pub json: Option<String>,
42}
43
44pub struct AttachmentDataBuilder {
46 inner: AttachmentData,
47}
48
49impl AttachmentDataBuilder {
50 pub fn new() -> Self {
53 Self {
54 inner: AttachmentData::default(),
55 }
56 }
57
58 pub fn with_jws(mut self, jws: &str) -> Self {
65 self.inner.jws = Some(jws.into());
66 self
67 }
68
69 pub fn with_hash(mut self, hash: &str) -> Self {
78 self.inner.hash = Some(hash.into());
79 self
80 }
81
82 pub fn with_link(mut self, link: &str) -> Self {
90 self.inner.links.push(link.into());
91 self
92 }
93
94 pub fn with_raw_payload(mut self, payload: impl AsRef<[u8]>) -> Self {
102 self.inner.base64 = Some(base64_url::encode(payload.as_ref()));
103 self
104 }
105
106 pub fn with_encoded_payload(mut self, payload: &str) -> Self {
113 self.inner.base64 = Some(payload.into());
114 self
115 }
116
117 pub fn with_json(mut self, stringified: &str) -> Self {
124 self.inner.json = Some(stringified.into());
125 self
126 }
127
128 fn finalize(self) -> AttachmentData {
129 self.inner
130 }
131}
132
133impl Default for AttachmentDataBuilder {
134 fn default() -> Self {
135 Self::new()
136 }
137}
138
139pub struct AttachmentBuilder {
143 inner: Attachment,
144 timed: bool,
145}
146
147impl AttachmentBuilder {
148 pub fn new(include_mod_time: bool) -> Self {
158 Self {
159 inner: Attachment::default(),
160 timed: include_mod_time,
161 }
162 }
163
164 pub fn with_id(mut self, id: &str) -> Self {
171 self.inner.id = Some(id.into());
172 self
173 }
174
175 pub fn with_description(mut self, description: &str) -> Self {
182 self.inner.description = Some(description.into());
183 self
184 }
185
186 pub fn with_filename(mut self, filename: &str) -> Self {
193 self.inner.filename = Some(filename.into());
194 self
195 }
196
197 pub fn with_media_type(mut self, media_type: &str) -> Self {
204 self.inner.media_type = Some(media_type.into());
205 self
206 }
207
208 pub fn with_format(mut self, format: &str) -> Self {
215 self.inner.format = Some(format.into());
216 self
217 }
218
219 pub fn external_size(mut self, bytes: usize) -> Self {
227 self.inner.byte_count = Some(bytes);
228 self
229 }
230
231 pub fn with_data(mut self, attachment_data: AttachmentDataBuilder) -> Self {
239 self.inner.data = attachment_data.finalize();
240 self
241 }
242
243 fn timestamp(&mut self) {
244 if self.timed {
245 self.inner.lastmod_time = Some(chrono::Utc::now().to_string());
246 }
247 }
248
249 fn finalize(mut self) -> Attachment {
250 self.timestamp();
251 self.inner
252 }
253}
254
255impl<T> TryFrom<(&str, T)> for AttachmentBuilder
256where
257 T: Serialize,
258{
259 type Error = Error;
260 fn try_from((format, data): (&str, T)) -> std::result::Result<Self, Self::Error> {
261 let serialized = serde_json::to_string(&data)?;
262 let builder = AttachmentBuilder::new(true)
263 .with_media_type("application/json")
264 .with_format(format)
265 .with_data(AttachmentDataBuilder::new().with_json(&serialized));
266 Ok(builder)
267 }
268}
269
270impl Message {
271 pub fn append_attachment(&mut self, builder: AttachmentBuilder) {
279 self.attachments.push(builder.finalize());
280 }
281
282 pub fn attachment_iter(&self) -> impl DoubleEndedIterator<Item = &Attachment> {
284 self.attachments.iter()
285 }
286
287 pub fn deserialize_attachments<'de, T>(&'de self, fmt: &str) -> Result<Vec<T>>
292 where
293 T: Deserialize<'de>,
294 {
295 if fmt != "application/json" {
296 return Err(Error::AttachmentError("unsupported media type".into()));
297 }
298
299 self.attachments
300 .iter()
301 .filter(|&att| att.format == Some(fmt.into()))
302 .map(|attachment| match attachment.media_type {
303 Some(ref media_type) if media_type == "application/json" => {
304 match &attachment.data.json {
305 Some(json) => serde_json::from_str(json).map_err(Error::SerdeError),
306 None if attachment.id.is_some() => Err(Error::AttachmentError(format!(
307 "attachment with id {} contains invalid JSON data",
308 attachment.id.clone().unwrap()
309 ))),
310 _ => Err(Error::AttachmentError("NO ATTACHMENT ID".into())),
311 }
312 }
313 _ => Err(Error::AttachmentError("unsupported media type".into()))?,
314 })
315 .collect()
316 }
317}
318
319#[cfg(test)]
320mod tests {
321 use serde::{Deserialize, Serialize};
322
323 use super::Message;
324 use super::*;
325
326 #[derive(Serialize, Deserialize, Debug)]
327 struct Data;
328
329 #[test]
330 fn try_from_successfully_creates_builder() {
331 for (&format, data) in [
332 "dif/presentation-exchange/definitions@v1.0",
333 "dif/presentation-exchange/submission@v1.0",
334 ]
335 .iter()
336 .zip([Data, Data])
337 {
338 let builder = AttachmentBuilder::try_from((format, data));
339 assert!(builder.is_ok(), "failed to create builder");
340 }
341 }
342
343 #[test]
344 fn deserialize_json_formatteed_attachments_successfully() {
345 let mut message = Message::new();
346 let builder = AttachmentBuilder::try_from(("application/json", Data))
347 .expect("failed to create builder");
348 message.append_attachment(builder);
349 let data: Vec<Data> = message
350 .deserialize_attachments("application/json")
351 .expect("failed to get attachments");
352 assert_eq!(data.len(), 1)
353 }
354
355 #[test]
356 #[should_panic(expected = "unsupported media type")]
357 fn cannot_deserialize_attachments_with_invalid_format() {
358 let mut message = Message::new();
359 let builder = AttachmentBuilder::try_from(("application/json", Data))
360 .expect("failed to create builder");
361 message.append_attachment(builder);
362 message
363 .deserialize_attachments::<Data>("application/yaml")
364 .unwrap();
365 }
366}