bc_envelope/base/
assertions.rs

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