bc_envelope/base/
assertions.rs

1use anyhow::{ bail, Result };
2use bc_components::DigestProvider;
3
4use crate::{ Envelope, EnvelopeEncodable, Error };
5
6use super::envelope::EnvelopeCase;
7
8/// Support for adding assertions.
9///
10/// Assertions are predicate-object pairs that make statements about an envelope's subject.
11/// This implementation provides methods for adding various types of assertions to envelopes.
12impl Envelope {
13    /// Returns a new envelope with the given assertion added.
14    ///
15    /// This is the most common way to add an assertion to an envelope. It automatically
16    /// creates an assertion envelope from the predicate and object, then adds it to the
17    /// existing envelope. The resulting envelope has the same subject with the new assertion added.
18    ///
19    /// # Examples
20    ///
21    /// ```
22    /// # use bc_envelope::prelude::*;
23    /// let envelope = Envelope::new("Alice")
24    ///     .add_assertion("name", "Alice Smith")
25    ///     .add_assertion("age", 30);
26    ///
27    /// // The envelope now contains two assertions about Alice
28    /// ```
29    pub fn add_assertion(
30        &self,
31        predicate: impl EnvelopeEncodable,
32        object: impl EnvelopeEncodable
33    ) -> Self {
34        let assertion = Self::new_assertion(predicate, object);
35        self.add_optional_assertion_envelope(Some(assertion)).unwrap()
36    }
37
38    /// Returns a new envelope with the given assertion envelope added.
39    ///
40    /// This method allows adding a pre-constructed assertion envelope to an envelope.
41    /// It's useful when you have already created an assertion envelope separately
42    /// or when working with elided, encrypted, or compressed assertion envelopes.
43    ///
44    /// # Parameters
45    ///
46    /// * `assertion_envelope` - A valid assertion envelope (or an obscured variant) to add
47    ///
48    /// # Returns
49    ///
50    /// A new envelope with the assertion added, or an error if the provided envelope
51    /// is not a valid assertion envelope.
52    ///
53    /// # Errors
54    ///
55    /// Returns `EnvelopeError::InvalidFormat` if the provided envelope is not a valid
56    /// assertion envelope or an obscured variant of one.
57    ///
58    /// # Examples
59    ///
60    /// ```
61    /// # use bc_envelope::prelude::*;
62    /// // Create a separate assertion envelope
63    /// let assertion = Envelope::new_assertion("knows", "Bob");
64    ///
65    /// // Add it to an envelope
66    /// let envelope = Envelope::new("Alice")
67    ///     .add_assertion_envelope(assertion)
68    ///     .unwrap();
69    /// ```
70    pub fn add_assertion_envelope(
71        &self,
72        assertion_envelope: impl EnvelopeEncodable
73    ) -> Result<Self> {
74        self.add_optional_assertion_envelope(Some(assertion_envelope.into_envelope()))
75    }
76
77    /// Returns a new envelope with multiple assertion envelopes added.
78    ///
79    /// This is a convenience method for adding multiple assertions at once.
80    /// Each assertion in the array must be a valid assertion envelope or an
81    /// obscured variant of one.
82    ///
83    /// # Parameters
84    ///
85    /// * `assertions` - An array of valid assertion envelopes to add
86    ///
87    /// # Returns
88    ///
89    /// A new envelope with all the assertions added, or an error if any of the
90    /// provided envelopes are not valid assertion envelopes.
91    ///
92    /// # Errors
93    ///
94    /// Returns `EnvelopeError::InvalidFormat` if any of the provided envelopes
95    /// are not valid assertion envelopes or obscured variants.
96    ///
97    /// # Examples
98    ///
99    /// ```
100    /// # use bc_envelope::prelude::*;
101    /// // Create multiple assertion envelopes
102    /// let assertion1 = Envelope::new_assertion("name", "Alice Smith");
103    /// let assertion2 = Envelope::new_assertion("age", 30);
104    /// let assertion3 = Envelope::new_assertion("city", "Boston");
105    ///
106    /// // Add them all at once to an envelope
107    /// let envelope = Envelope::new("person")
108    ///     .add_assertion_envelopes(&[assertion1, assertion2, assertion3])
109    ///     .unwrap();
110    /// ```
111    pub fn add_assertion_envelopes(&self, assertions: &[Self]) -> Result<Self> {
112        let mut e = self.clone();
113        for assertion in assertions {
114            e = e.add_assertion_envelope(assertion.clone())?;
115        }
116        Ok(e)
117    }
118
119    /// Adds an optional assertion envelope to this envelope.
120    ///
121    /// If the optional assertion is present, adds it to the envelope.
122    /// Otherwise, returns the envelope unchanged. This method is particularly
123    /// useful when working with functions that may or may not return an assertion.
124    ///
125    /// The method also ensures that duplicate assertions (with the same digest)
126    /// are not added, making it idempotent.
127    ///
128    /// # Parameters
129    ///
130    /// * `assertion` - An optional assertion envelope to add
131    ///
132    /// # Returns
133    ///
134    /// A new envelope with the assertion added if provided, or the original envelope
135    /// if no assertion was provided or it was a duplicate.
136    ///
137    /// # Errors
138    ///
139    /// Returns `EnvelopeError::InvalidFormat` if the provided envelope is not a valid
140    /// assertion envelope or an obscured variant.
141    ///
142    /// # Examples
143    ///
144    /// ```
145    /// # use bc_envelope::prelude::*;
146    /// // A function that may return an assertion based on a condition
147    /// fn get_optional_assertion(include: bool) -> Option<Envelope> {
148    ///     if include {
149    ///         Some(Envelope::new_assertion("verified", true))
150    ///     } else {
151    ///         None
152    ///     }
153    /// }
154    ///
155    /// // Add the assertion only if it's available
156    /// let envelope = Envelope::new("document")
157    ///     .add_optional_assertion_envelope(get_optional_assertion(true))
158    ///     .unwrap();
159    /// ```
160    pub fn add_optional_assertion_envelope(&self, assertion: Option<Self>) -> Result<Self> {
161        match assertion {
162            Some(assertion) => {
163                if !assertion.is_subject_assertion() && !assertion.is_subject_obscured() {
164                    bail!(Error::InvalidFormat);
165                }
166
167                match self.case() {
168                    EnvelopeCase::Node { subject, assertions, .. } => {
169                        if !assertions.iter().any(|a| a.digest() == assertion.digest()) {
170                            let mut assertions = assertions.clone();
171                            assertions.push(assertion);
172                            Ok(Self::new_with_unchecked_assertions(subject.clone(), assertions))
173                        } else {
174                            Ok(self.clone())
175                        }
176                    }
177                    _ => Ok(Self::new_with_unchecked_assertions(self.subject(), vec![assertion])),
178                }
179            }
180            None => Ok(self.clone()),
181        }
182    }
183
184    /// Adds an assertion with the given predicate and optional object.
185    ///
186    /// This method is useful when you have a predicate but may or may not have
187    /// an object value to associate with it. If the object is present, an assertion
188    /// is created and added to the envelope. Otherwise, the envelope is returned unchanged.
189    ///
190    /// # Parameters
191    ///
192    /// * `predicate` - The predicate for the assertion
193    /// * `object` - An optional object value for the assertion
194    ///
195    /// # Returns
196    ///
197    /// A new envelope with the assertion added if the object was provided,
198    /// or the original envelope if no object was provided.
199    ///
200    /// # Examples
201    ///
202    /// ```
203    /// # use bc_envelope::prelude::*;
204    /// // A function that may return an optional value
205    /// fn get_optional_value(has_value: bool) -> Option<String> {
206    ///     if has_value {
207    ///         Some("John Smith".to_string())
208    ///     } else {
209    ///         None
210    ///     }
211    /// }
212    ///
213    /// // Person with a name if provided
214    /// let person = Envelope::new("person")
215    ///     .add_optional_assertion("name", get_optional_value(true));
216    ///
217    /// // Person without a name if not provided
218    /// let person_without_name = Envelope::new("person")
219    ///     .add_optional_assertion("name", get_optional_value(false));
220    /// ```
221    pub fn add_optional_assertion(
222        &self,
223        predicate: impl EnvelopeEncodable,
224        object: Option<impl EnvelopeEncodable>
225    ) -> Self {
226        if let Some(object) = object {
227            self.add_assertion_envelope(Self::new_assertion(predicate, object)).unwrap()
228        } else {
229            self.clone()
230        }
231    }
232
233    /// Adds an assertion with the given predicate and string value, but only if the string is non-empty.
234    ///
235    /// This is a convenience method that only adds an assertion if the string value
236    /// is non-empty. It's particularly useful when working with user input or optional
237    /// text fields that should only be included if they contain actual content.
238    ///
239    /// # Parameters
240    ///
241    /// * `predicate` - The predicate for the assertion
242    /// * `str` - The string value for the assertion
243    ///
244    /// # Returns
245    ///
246    /// A new envelope with the assertion added if the string is non-empty,
247    /// or the original envelope if the string is empty.
248    ///
249    /// # Examples
250    ///
251    /// ```
252    /// # use bc_envelope::prelude::*;
253    /// // Create a person with non-empty fields
254    /// let person = Envelope::new("person")
255    ///     .add_nonempty_string_assertion("name", "Alice Smith")
256    ///     .add_nonempty_string_assertion("notes", ""); // This won't be added
257    ///
258    /// // Only the name assertion is added
259    /// assert_eq!(person.assertions().len(), 1);
260    /// ```
261    pub fn add_nonempty_string_assertion(
262        &self,
263        predicate: impl EnvelopeEncodable,
264        str: impl AsRef<str>
265    ) -> Self {
266        let str = str.as_ref();
267        if str.is_empty() {
268            self.clone()
269        } else {
270            self.add_assertion(predicate, str)
271        }
272    }
273
274    /// Returns a new envelope with the given array of assertions added.
275    ///
276    /// Similar to `add_assertion_envelopes` but ignores any errors that might occur.
277    /// This is useful when you're certain all envelopes in the array are valid
278    /// assertion envelopes and don't need to handle errors.
279    ///
280    /// # Parameters
281    ///
282    /// * `envelopes` - An array of assertion envelopes to add
283    ///
284    /// # Returns
285    ///
286    /// A new envelope with all the valid assertions added
287    ///
288    /// # Examples
289    ///
290    /// ```
291    /// # use bc_envelope::prelude::*;
292    /// // Create multiple assertion envelopes
293    /// let assertion1 = Envelope::new_assertion("name", "Alice Smith");
294    /// let assertion2 = Envelope::new_assertion("age", 30);
295    ///
296    /// // Add them all at once to an envelope
297    /// let envelope = Envelope::new("person")
298    ///     .add_assertions(&[assertion1, assertion2]);
299    /// ```
300    pub fn add_assertions(&self, envelopes: &[Self]) -> Self {
301        let mut e = self.clone();
302        for envelope in envelopes {
303            e = e.add_assertion_envelope(envelope.clone()).unwrap();
304        }
305        e
306    }
307}
308
309/// Support for adding conditional assertions.
310///
311/// These methods add assertions only when certain conditions are met.
312impl Envelope {
313    /// Adds an assertion only if the provided condition is true.
314    ///
315    /// This method allows for conditional inclusion of assertions based on a boolean condition.
316    /// It's a convenient way to add assertions only in certain circumstances without
317    /// requiring separate conditional logic.
318    ///
319    /// # Parameters
320    ///
321    /// * `condition` - Boolean that determines whether to add the assertion
322    /// * `predicate` - The predicate for the assertion
323    /// * `object` - The object value for the assertion
324    ///
325    /// # Returns
326    ///
327    /// A new envelope with the assertion added if the condition is true,
328    /// or the original envelope if the condition is false.
329    ///
330    /// # Examples
331    ///
332    /// ```
333    /// # use bc_envelope::prelude::*;
334    /// let is_verified = true;
335    /// let is_expired = false;
336    ///
337    /// let document = Envelope::new("document")
338    ///     .add_assertion_if(is_verified, "verified", true)   // This will be added
339    ///     .add_assertion_if(is_expired, "expired", true);    // This won't be added
340    ///
341    /// // Only the verification assertion is present
342    /// assert_eq!(document.assertions().len(), 1);
343    /// ```
344    pub fn add_assertion_if(
345        &self,
346        condition: bool,
347        predicate: impl EnvelopeEncodable,
348        object: impl EnvelopeEncodable
349    ) -> Self {
350        if condition { self.add_assertion(predicate, object) } else { self.clone() }
351    }
352
353    /// Adds an assertion envelope only if the provided condition is true.
354    ///
355    /// Similar to `add_assertion_if` but works with pre-constructed assertion envelopes.
356    /// This is useful when you have already created an assertion envelope separately and
357    /// want to conditionally add it.
358    ///
359    /// # Parameters
360    ///
361    /// * `condition` - Boolean that determines whether to add the assertion envelope
362    /// * `assertion_envelope` - The assertion envelope to add
363    ///
364    /// # Returns
365    ///
366    /// A new envelope with the assertion added if the condition is true,
367    /// or the original envelope if the condition is false.
368    ///
369    /// # Errors
370    ///
371    /// Returns `EnvelopeError::InvalidFormat` if the provided envelope is not a valid
372    /// assertion envelope or an obscured variant and the condition is true.
373    ///
374    /// # Examples
375    ///
376    /// ```
377    /// # use bc_envelope::prelude::*;
378    /// let assertion = Envelope::new_assertion("verified", true);
379    /// let should_include = true;
380    ///
381    /// let document = Envelope::new("document")
382    ///     .add_assertion_envelope_if(should_include, assertion)
383    ///     .unwrap();
384    /// ```
385    pub fn add_assertion_envelope_if(
386        &self,
387        condition: bool,
388        assertion_envelope: Self
389    ) -> Result<Self> {
390        if condition { self.add_assertion_envelope(assertion_envelope) } else { Ok(self.clone()) }
391    }
392}
393
394#[cfg(feature = "salt")]
395/// Support for adding assertions with salt.
396///
397/// Salting adds random data to an assertion to change its digest
398/// while preserving semantic meaning. This is useful for decorrelation -
399/// making it impossible to determine if two elided envelopes contain the
400/// same assertion by comparing their digests.
401impl Envelope {
402    /// Returns the result of adding the given assertion to the envelope, optionally salting it.
403    pub fn add_assertion_salted<P, O>(&self, predicate: P, object: O, salted: bool) -> Self
404        where P: EnvelopeEncodable, O: EnvelopeEncodable
405    {
406        let assertion = Self::new_assertion(predicate, object);
407        self.add_optional_assertion_envelope_salted(Some(assertion), salted).unwrap()
408    }
409
410    /// Returns the result of adding the given assertion to the envelope, optionally salting it.
411    ///
412    /// The assertion envelope must be a valid assertion envelope, or an
413    /// obscured variant (elided, encrypted, compressed) of one.
414    pub fn add_assertion_envelope_salted(
415        &self,
416        assertion_envelope: Self,
417        salted: bool
418    ) -> Result<Self> {
419        self.add_optional_assertion_envelope_salted(Some(assertion_envelope), salted)
420    }
421
422    /// If the optional assertion is present, returns the result of adding it to
423    /// the envelope, optionally salting it. Otherwise, returns the envelope unchanged.
424    ///
425    /// The assertion envelope must be a valid assertion envelope, or an
426    /// obscured variant (elided, encrypted, compressed) of one.
427    pub fn add_optional_assertion_envelope_salted(
428        &self,
429        assertion: Option<Self>,
430        salted: bool
431    ) -> Result<Self> {
432        match assertion {
433            Some(assertion) => {
434                if !assertion.is_subject_assertion() && !assertion.is_subject_obscured() {
435                    bail!(Error::InvalidFormat);
436                }
437                let envelope2 = if salted { assertion.add_salt() } else { assertion };
438
439                match self.case() {
440                    EnvelopeCase::Node { subject, assertions, .. } => {
441                        if !assertions.iter().any(|a| a.digest() == envelope2.digest()) {
442                            let mut assertions = assertions.clone();
443                            assertions.push(envelope2);
444                            Ok(Self::new_with_unchecked_assertions(subject.clone(), assertions))
445                        } else {
446                            Ok(self.clone())
447                        }
448                    }
449                    _ => Ok(Self::new_with_unchecked_assertions(self.subject(), vec![envelope2])),
450                }
451            }
452            None => Ok(self.clone()),
453        }
454    }
455
456    pub fn add_assertions_salted(&self, assertions: &[Self], salted: bool) -> Self {
457        let mut e = self.clone();
458        for assertion in assertions {
459            e = e.add_assertion_envelope_salted(assertion.clone(), salted).unwrap();
460        }
461        e.clone()
462    }
463}
464
465/// Support for removing or replacing assertions.
466///
467/// These methods allow for modifying an envelope by removing or replacing
468/// existing assertions, while maintaining the envelope's immutability model.
469impl Envelope {
470    /// Returns a new envelope with the given assertion removed.
471    ///
472    /// Finds and removes an assertion matching the target assertion's digest.
473    /// If the assertion doesn't exist, returns the same envelope unchanged.
474    /// If removing the assertion would leave the envelope with no assertions,
475    /// returns just the subject as a new envelope.
476    ///
477    /// # Parameters
478    ///
479    /// * `target` - The assertion envelope to remove
480    ///
481    /// # Returns
482    ///
483    /// A new envelope with the specified assertion removed if found,
484    /// or the original envelope if not found.
485    ///
486    /// # Examples
487    ///
488    /// ```
489    /// # use bc_envelope::prelude::*;
490    /// // Create an envelope with assertions
491    /// let person = Envelope::new("Alice")
492    ///     .add_assertion("name", "Alice Smith")
493    ///     .add_assertion("age", 30);
494    ///
495    /// // Create the assertion to remove
496    /// let name_assertion = Envelope::new_assertion("name", "Alice Smith");
497    ///
498    /// // Remove the name assertion
499    /// let modified = person.remove_assertion(name_assertion);
500    ///
501    /// // The envelope now only has the age assertion
502    /// assert_eq!(modified.assertions().len(), 1);
503    /// ```
504    pub fn remove_assertion(&self, target: Self) -> Self {
505        let assertions = self.assertions();
506        let target = target.digest();
507        if let Some(index) = assertions.iter().position(|a| a.digest() == target) {
508            let mut assertions = assertions.clone();
509            assertions.remove(index);
510            if assertions.is_empty() {
511                self.subject()
512            } else {
513                Self::new_with_unchecked_assertions(self.subject(), assertions)
514            }
515        } else {
516            self.clone()
517        }
518    }
519
520    /// Returns a new envelope with the given assertion replaced by a new one.
521    ///
522    /// This method removes the specified assertion and adds a new one in its place.
523    /// If the targeted assertion does not exist, returns the same envelope with the
524    /// new assertion added.
525    ///
526    /// # Parameters
527    ///
528    /// * `assertion` - The assertion envelope to replace
529    /// * `new_assertion` - The new assertion envelope to add
530    ///
531    /// # Returns
532    ///
533    /// A new envelope with the assertion replaced if found,
534    /// or the original envelope with the new assertion added if not found.
535    ///
536    /// # Errors
537    ///
538    /// Returns `EnvelopeError::InvalidFormat` if the new assertion is not a valid
539    /// assertion envelope or an obscured variant.
540    ///
541    /// # Examples
542    ///
543    /// ```
544    /// # use bc_envelope::prelude::*;
545    /// // Create an envelope with assertions
546    /// let person = Envelope::new("Alice")
547    ///     .add_assertion("name", "Alice Smith")
548    ///     .add_assertion("age", 30);
549    ///
550    /// // Create the assertion to replace and the new assertion
551    /// let old_name = Envelope::new_assertion("name", "Alice Smith");
552    /// let new_name = Envelope::new_assertion("name", "Alice Johnson");
553    ///
554    /// // Replace the name assertion
555    /// let modified = person.replace_assertion(old_name, new_name).unwrap();
556    /// ```
557    pub fn replace_assertion(&self, assertion: Self, new_assertion: Self) -> Result<Self> {
558        self.remove_assertion(assertion).add_assertion_envelope(new_assertion)
559    }
560
561    /// Returns a new envelope with its subject replaced by the provided one.
562    ///
563    /// This method preserves all assertions from the original envelope but
564    /// applies them to a new subject. It effectively creates a new envelope
565    /// with the provided subject and copies over all assertions from the current envelope.
566    ///
567    /// # Parameters
568    ///
569    /// * `subject` - The new subject for the envelope
570    ///
571    /// # Returns
572    ///
573    /// A new envelope with the new subject and all assertions from the original envelope.
574    ///
575    /// # Examples
576    ///
577    /// ```
578    /// # use bc_envelope::prelude::*;
579    /// // Create an envelope for Alice
580    /// let alice = Envelope::new("Alice")
581    ///     .add_assertion("age", 30)
582    ///     .add_assertion("city", "Boston");
583    ///
584    /// // Replace the subject to create an envelope for Bob with the same assertions
585    /// let bob = alice.replace_subject(Envelope::new("Bob"));
586    ///
587    /// // Bob now has the same assertions
588    /// assert_eq!(bob.extract_subject::<String>().unwrap(), "Bob");
589    /// assert_eq!(bob.assertions().len(), 2);
590    /// ```
591    pub fn replace_subject(&self, subject: Self) -> Self {
592        self.assertions()
593            .into_iter()
594            .fold(subject, |e, a| e.add_assertion_envelope(a).unwrap())
595    }
596}