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}