bc_envelope/base/
assertion.rs

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