bc_envelope/base/
assertions.rs

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