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}