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}