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}