bc_envelope/base/
assertion.rs

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