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