bc_envelope/base/assertion.rs
1use std::borrow::Cow;
2use anyhow::{ bail, Error, Result };
3use bc_components::{ Digest, DigestProvider };
4use dcbor::prelude::*;
5
6use crate::{ Envelope, EnvelopeEncodable };
7
8/// A predicate-object relationship representing an assertion about a subject.
9///
10/// In Gordian Envelope, assertions are the basic building blocks for attaching
11/// information to a subject. An assertion consists of a predicate (which states
12/// what is being asserted) and an object (which provides the assertion's value).
13///
14/// Assertions can be attached to envelope subjects to form semantic statements like:
15/// "subject hasAttribute value" or "document signedBy signature".
16///
17/// Assertions are equivalent to RDF (Resource Description Framework) triples,
18/// where:
19/// - The envelope's subject is the subject of the triple
20/// - The assertion's predicate is the predicate of the triple
21/// - The assertion's object is the object of the triple
22///
23/// Generally you do not create an instance of this type directly, but
24/// instead use [`Envelope::new_assertion`], or the various functions
25/// on `Envelope` that create assertions.
26#[derive(Clone, Debug)]
27pub struct Assertion {
28 predicate: Envelope,
29 object: Envelope,
30 digest: Digest,
31}
32
33impl Assertion {
34 /// Creates a new assertion and calculates its digest.
35 ///
36 /// This constructor takes a predicate and object, both of which are converted to
37 /// envelopes using the `EnvelopeEncodable` trait. It then calculates the assertion's
38 /// digest by combining the digests of the predicate and object.
39 ///
40 /// The digest is calculated according to the Gordian Envelope specification, which
41 /// ensures that semantically equivalent assertions always produce the same digest.
42 ///
43 /// # Parameters
44 ///
45 /// * `predicate` - The predicate of the assertion, which states what is being asserted
46 /// * `object` - The object of the assertion, which provides the assertion's value
47 ///
48 /// # Returns
49 ///
50 /// A new assertion with the specified predicate, object, and calculated digest.
51 ///
52 /// # Example
53 ///
54 /// ```
55 /// # use bc_envelope::prelude::*;
56 /// // Direct method - create an assertion envelope
57 /// let assertion_envelope = Envelope::new_assertion("name", "Alice");
58 ///
59 /// // Or create and add an assertion to a subject
60 /// let person = Envelope::new("person")
61 /// .add_assertion("name", "Alice");
62 /// ```
63 pub fn new(predicate: impl EnvelopeEncodable, object: impl EnvelopeEncodable) -> Self {
64 let predicate = predicate.into_envelope();
65 let object = object.into_envelope();
66 let digest = Digest::from_digests(
67 &[predicate.digest().into_owned(), object.digest().into_owned()]
68 );
69 Self {
70 predicate,
71 object,
72 digest,
73 }
74 }
75
76 /// Returns the predicate of the assertion.
77 ///
78 /// The predicate states what is being asserted about the subject. It is typically
79 /// a string or known value, but can be any envelope.
80 ///
81 /// # Returns
82 ///
83 /// A clone of the assertion's predicate envelope.
84 pub fn predicate(&self) -> Envelope {
85 self.predicate.clone()
86 }
87
88 /// Returns the object of the assertion.
89 ///
90 /// The object provides the value or content of the assertion. It can be any
91 /// type that can be represented as an envelope.
92 ///
93 /// # Returns
94 ///
95 /// A clone of the assertion's object envelope.
96 pub fn object(&self) -> Envelope {
97 self.object.clone()
98 }
99
100 /// Returns a reference to the digest of the assertion.
101 ///
102 /// The digest is calculated when the assertion is created and is used for
103 /// verification and deduplication. The digest calculation follows the rules
104 /// specified in the Gordian Envelope IETF draft, Section 4.4.
105 ///
106 /// # Returns
107 ///
108 /// A reference to the assertion's digest.
109 pub fn digest_ref(&self) -> &Digest {
110 &self.digest
111 }
112}
113
114/// Equality is based on digest equality, not structural equality.
115///
116/// Two assertions are considered equal if they have the same digest,
117/// regardless of how they were constructed.
118impl PartialEq for Assertion {
119 fn eq(&self, other: &Self) -> bool {
120 self.digest_ref() == other.digest_ref()
121 }
122}
123
124/// Assertion implements full equality.
125impl Eq for Assertion {}
126
127/// Implementation of `DigestProvider` for `Assertion`.
128///
129/// This allows an assertion to provide its digest for calculation of
130/// higher-level digests in the envelope digest tree.
131impl DigestProvider for Assertion {
132 /// Returns a reference to the assertion's digest.
133 ///
134 /// This is used in the envelope digest tree calculation.
135 fn digest(&self) -> Cow<'_, Digest> {
136 Cow::Borrowed(&self.digest)
137 }
138}
139
140/// Converts an assertion to its CBOR representation.
141///
142/// The CBOR representation of an assertion is a map with a single key-value pair,
143/// where the key is the predicate's CBOR and the value is the object's CBOR.
144impl From<Assertion> for CBOR {
145 fn from(value: Assertion) -> Self {
146 let mut map = Map::new();
147 map.insert(value.predicate.untagged_cbor(), value.object.untagged_cbor());
148 map.into()
149 }
150}
151
152/// Attempts to convert a CBOR value to an assertion.
153///
154/// The CBOR must be a map with exactly one entry, where the key represents
155/// the predicate and the value represents the object.
156impl TryFrom<CBOR> for Assertion {
157 type Error = Error;
158
159 fn try_from(value: CBOR) -> Result<Self> {
160 if let CBORCase::Map(map) = value.as_case() {
161 return map.clone().try_into();
162 }
163 bail!(crate::Error::InvalidAssertion)
164 }
165}
166
167/// Attempts to convert a CBOR map to an assertion.
168///
169/// The map must have exactly one entry, where the key represents the
170/// predicate and the value represents the object. This is used in
171/// the deserialization process.
172impl TryFrom<Map> for Assertion {
173 type Error = Error;
174
175 fn try_from(map: Map) -> Result<Self> {
176 if map.len() != 1 {
177 bail!(crate::Error::InvalidAssertion);
178 }
179 let elem = map.iter().next().unwrap();
180 let predicate = Envelope::from_untagged_cbor(elem.0.clone())?;
181 let object = Envelope::from_untagged_cbor(elem.1.clone())?;
182 Ok(Self::new(predicate, object))
183 }
184}