bc_envelope/base/
assertion.rs

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