Skip to main content

bc_envelope/extension/attachment/
attachments.rs

1//! Extensible metadata attachment container.
2//!
3//! This module provides the infrastructure for attaching arbitrary metadata
4//! to envelopes. Attachments enable flexible, extensible data storage
5//! without modifying the core data model, facilitating interoperability and
6//! future compatibility.
7
8use std::collections::HashMap;
9
10use bc_components::{Digest, DigestProvider};
11
12use crate::{Envelope, EnvelopeEncodable, Result};
13
14/// A container for vendor-specific metadata attachments.
15///
16/// `Attachments` provides a flexible mechanism for attaching arbitrary metadata
17/// to envelopes without modifying their core structure.
18#[derive(Debug, Clone, PartialEq, Eq)]
19pub struct Attachments {
20    /// Storage mapping from digest to envelope
21    envelopes: HashMap<Digest, Envelope>,
22}
23
24impl Default for Attachments {
25    fn default() -> Self { Self::new() }
26}
27
28impl Attachments {
29    /// Creates a new empty attachments container.
30    pub fn new() -> Self { Self { envelopes: HashMap::new() } }
31
32    /// Adds a new attachment with the specified payload and metadata.
33    ///
34    /// # Arguments
35    /// * `payload` - The data to attach, which must be encodable in an envelope
36    /// * `vendor` - A string identifying the entity that defined the attachment
37    ///   format
38    /// * `conforms_to` - An optional string identifying the structure the
39    ///   payload conforms to
40    pub fn add(
41        &mut self,
42        payload: impl EnvelopeEncodable,
43        vendor: impl AsRef<str>,
44        conforms_to: Option<impl AsRef<str>>,
45    ) {
46        let attachment = Envelope::new_attachment(
47            payload,
48            vendor.as_ref(),
49            conforms_to.as_ref().map(|s| s.as_ref()),
50        );
51        self.envelopes.insert(attachment.digest(), attachment);
52    }
53
54    /// Retrieves an attachment by its digest.
55    ///
56    /// # Arguments
57    /// * `digest` - The unique digest of the attachment to retrieve
58    ///
59    /// # Returns
60    /// A reference to the envelope if found, or None if no attachment exists
61    /// with the given digest
62    pub fn get(&self, digest: Digest) -> Option<&Envelope> {
63        self.envelopes.get(&digest)
64    }
65
66    /// Removes an attachment by its digest.
67    ///
68    /// # Arguments
69    /// * `digest` - The unique digest of the attachment to remove
70    ///
71    /// # Returns
72    /// The removed envelope if found, or None if no attachment exists with the
73    /// given digest
74    pub fn remove(&mut self, digest: Digest) -> Option<Envelope> {
75        self.envelopes.remove(&digest)
76    }
77
78    /// Removes all attachments from the container.
79    pub fn clear(&mut self) { self.envelopes.clear(); }
80
81    /// Returns whether the container has any attachments.
82    ///
83    /// # Returns
84    /// `true` if there are no attachments, `false` otherwise
85    pub fn is_empty(&self) -> bool { self.envelopes.is_empty() }
86
87    pub fn add_to_envelope(&self, envelope: Envelope) -> Envelope {
88        let mut new_envelope = envelope;
89        for (_digest, envelope) in self.envelopes.iter() {
90            new_envelope =
91                new_envelope.add_assertion_envelope(envelope).unwrap();
92        }
93        new_envelope
94    }
95
96    pub fn try_from_envelope(envelope: &Envelope) -> Result<Attachments> {
97        let attachment_envelopes = envelope.attachments()?;
98        let mut attachments = Attachments::new();
99        for attachment in attachment_envelopes {
100            let digest = attachment.digest();
101            attachments.envelopes.insert(digest, attachment);
102        }
103        Ok(attachments)
104    }
105}
106
107/// A trait for types that can have metadata attachments.
108///
109/// `Attachable` provides a consistent interface for working with metadata
110/// attachments. Types implementing this trait can store and retrieve
111/// vendor-specific data without modifying their core structure.
112#[allow(dead_code)]
113pub trait Attachable {
114    /// Returns a reference to the attachments container.
115    fn attachments(&self) -> &Attachments;
116
117    /// Returns a mutable reference to the attachments container.
118    fn attachments_mut(&mut self) -> &mut Attachments;
119
120    /// Adds a new attachment with the specified payload and metadata.
121    ///
122    /// # Arguments
123    /// * `payload` - The data to attach, which must be encodable in an envelope
124    /// * `vendor` - A string identifying the entity that defined the attachment
125    ///   format
126    /// * `conforms_to` - An optional string identifying the structure the
127    ///   payload conforms to
128    fn add_attachment(
129        &mut self,
130        payload: impl EnvelopeEncodable,
131        vendor: &str,
132        conforms_to: Option<&str>,
133    ) {
134        self.attachments_mut().add(payload, vendor, conforms_to);
135    }
136
137    /// Retrieves an attachment by its digest.
138    ///
139    /// # Arguments
140    /// * `digest` - The unique digest of the attachment to retrieve
141    ///
142    /// # Returns
143    /// A reference to the envelope if found, or None if no attachment exists
144    /// with the given digest
145    fn get_attachment(&self, digest: Digest) -> Option<&Envelope> {
146        self.attachments().get(digest)
147    }
148
149    /// Removes an attachment by its digest.
150    ///
151    /// # Arguments
152    /// * `digest` - The unique digest of the attachment to remove
153    ///
154    /// # Returns
155    /// The removed envelope if found, or None if no attachment exists with the
156    /// given digest
157    fn remove_attachment(&mut self, digest: Digest) -> Option<Envelope> {
158        self.attachments_mut().remove(digest)
159    }
160
161    /// Removes all attachments from the object.
162    fn clear_attachments(&mut self) { self.attachments_mut().clear(); }
163
164    /// Returns whether the object has any attachments.
165    ///
166    /// # Returns
167    /// `true` if there are attachments, `false` otherwise
168    fn has_attachments(&self) -> bool { !self.attachments().is_empty() }
169}