bc_envelope/extension/
types.rs

1use bc_components::DigestProvider;
2
3use crate::{
4    Envelope, EnvelopeEncodable, Error, KnownValue, Result, known_values,
5};
6
7/// # Type System for Gordian Envelopes
8///
9/// This module provides functionality for adding, querying, and verifying types
10/// within envelopes. In Gordian Envelope, types are implemented using the
11/// special `'isA'` Known Value predicate, which is semantically equivalent to
12/// the RDF `rdf:type` concept.
13///
14/// Type information enables:
15/// - Semantic classification of envelopes
16/// - Type verification before processing content
17/// - Conversion between domain objects and envelopes
18/// - Schema validation
19///
20/// ## Type Representation
21///
22/// Types are represented as assertions with the `'isA'` predicate (known value
23/// 1) and an object that specifies the type. The type object is typically
24///    either:
25///
26/// 1. A Known Value from the registry (e.g., `known_values::SEED_TYPE`)
27/// 2. A custom type represented as an envelope
28///
29/// ## Usage Patterns
30///
31/// The type system is commonly used in two ways:
32///
33/// 1. **Type Tagging**: Adding type information to envelopes to indicate their
34///    semantic meaning
35///
36///    ```rust
37///    use bc_envelope::prelude::*;
38///
39///    // Create an envelope representing a person
40///    let person = Envelope::new("Alice")
41///        .add_type("Person")
42///        .add_assertion("age", 30);
43///    ```
44///
45/// 2. **Type Checking**: Verifying that an envelope has the expected type
46///    before processing
47///
48///    ```no_run
49///    use bc_envelope::prelude::*;
50///
51///    fn process_person(envelope: &Envelope) -> EnvelopeResult<()> {
52///        // Verify this is a person before processing
53///        envelope.check_type("Person")?;
54///
55///        // Now we can safely extract person-specific information
56///        let name: String = envelope.subject().try_into()?;
57///        let age = envelope.extract_object_for_predicate::<u8>("age")?;
58///
59///        println!("{} is {} years old", name, age);
60///        Ok(())
61///    }
62///    ```
63///
64/// ## Domain Object Conversion
65///
66/// The type system also enables conversion between domain objects and
67/// envelopes. The pattern typically involves:
68///
69/// 1. Implementing `From<DomainObject> for Envelope` to convert objects to
70///    envelopes
71/// 2. Implementing `TryFrom<Envelope> for DomainObject` to convert envelopes
72///    back to objects
73/// 3. Using `check_type_value()` or `check_type()` in the TryFrom
74///    implementation to verify the envelope has the correct type
75///
76/// See the `test_seed.rs` file in the tests directory for an example of this
77/// pattern.
78impl Envelope {
79    /// Adds a type assertion to the envelope using the `'isA'` predicate.
80    ///
81    /// This method provides a convenient way to declare the type of an envelope
82    /// using the standard `'isA'` predicate (known value 1). The type can be
83    /// any value that can be converted to an envelope, typically a string
84    /// or a Known Value from the registry.
85    ///
86    /// # Parameters
87    /// - `object`: The type to assign to this envelope
88    ///
89    /// # Returns
90    /// A new envelope with the type assertion added
91    ///
92    /// # Examples
93    ///
94    /// Using a string type:
95    ///
96    /// ```
97    /// use bc_envelope::prelude::*;
98    ///
99    /// // Create a document and declare its type
100    /// let document = Envelope::new("Important Content").add_type("Document");
101    ///
102    /// // Verify the type was added
103    /// assert!(document.has_type("Document"));
104    /// ```
105    ///
106    /// Using a predefined Known Value type:
107    ///
108    /// ```
109    /// use bc_envelope::prelude::*;
110    ///
111    /// // Create a seed envelope with the standard SEED_TYPE
112    /// let seed_data = "seed data".to_string();
113    /// let seed = Envelope::new(seed_data).add_type(known_values::SEED_TYPE);
114    ///
115    /// // Verify the type was added
116    /// assert!(seed.has_type_value(&known_values::SEED_TYPE));
117    /// ```
118    pub fn add_type(&self, object: impl EnvelopeEncodable) -> Self {
119        self.add_assertion(known_values::IS_A, object)
120    }
121
122    /// Returns all type objects from the envelope's `'isA'` assertions.
123    ///
124    /// This method retrieves all objects of assertions that use the `'isA'`
125    /// predicate. Each returned envelope represents a type that has been
126    /// assigned to this envelope.
127    ///
128    /// # Returns
129    /// A vector of envelopes, each representing a type assigned to this
130    /// envelope
131    ///
132    /// # Examples
133    ///
134    /// ```
135    /// use std::convert::TryInto;
136    ///
137    /// use bc_envelope::prelude::*;
138    ///
139    /// // Create an envelope with multiple types
140    /// let multi_typed = Envelope::new("Versatile Entity")
141    ///     .add_type("Person")
142    ///     .add_type("Employee")
143    ///     .add_type("Manager");
144    ///
145    /// // Get all the type objects
146    /// let types = multi_typed.types();
147    ///
148    /// // There should be 3 types
149    /// assert_eq!(types.len(), 3);
150    ///
151    /// // For each type, we could verify its presence directly using digests
152    /// let person_type = Envelope::new("Person");
153    /// let employee_type = Envelope::new("Employee");
154    /// let manager_type = Envelope::new("Manager");
155    ///
156    /// let has_person = types.iter().any(|e| e.digest() == person_type.digest());
157    /// let has_employee =
158    ///     types.iter().any(|e| e.digest() == employee_type.digest());
159    /// let has_manager = types.iter().any(|e| e.digest() == manager_type.digest());
160    ///
161    /// assert!(has_person);
162    /// assert!(has_employee);
163    /// assert!(has_manager);
164    /// ```
165    pub fn types(&self) -> Vec<Self> {
166        self.objects_for_predicate(known_values::IS_A)
167    }
168
169    /// Gets a single type object from the envelope's `'isA'` assertions.
170    ///
171    /// This method is useful when an envelope is expected to have exactly one
172    /// type. It returns an error if the envelope has zero or multiple
173    /// types.
174    ///
175    /// # Returns
176    /// - `Ok(Envelope)`: The single type object if exactly one exists
177    /// - `Err(EnvelopeError::AmbiguousType)`: If multiple types exist
178    ///
179    /// # Examples
180    ///
181    /// With a single type:
182    ///
183    /// ```
184    /// use bc_envelope::prelude::*;
185    ///
186    /// // Create an envelope with a single type
187    /// let person = Envelope::new("Alice").add_type("Person");
188    ///
189    /// // Get the type
190    /// let type_obj = person.get_type().unwrap();
191    /// let type_string: String = type_obj.try_into().unwrap();
192    /// assert_eq!(type_string, "Person");
193    /// ```
194    ///
195    /// With multiple types (results in error):
196    ///
197    /// ```
198    /// use bc_envelope::prelude::*;
199    ///
200    /// // Create an envelope with multiple types
201    /// let multi_typed = Envelope::new("Alice")
202    ///     .add_type("Person")
203    ///     .add_type("Employee");
204    ///
205    /// // Trying to get a single type will fail
206    /// let result = multi_typed.get_type();
207    /// assert!(result.is_err());
208    /// ```
209    pub fn get_type(&self) -> Result<Self> {
210        let t = self.types();
211        if t.len() == 1 {
212            Ok(t[0].clone())
213        } else {
214            Err(Error::AmbiguousType)
215        }
216    }
217
218    /// Checks if the envelope has a specific type, using an envelope as the
219    /// type identifier.
220    ///
221    /// This method compares the digest of each type object with the digest of
222    /// the provided envelope to determine if the envelope has the specified
223    /// type.
224    ///
225    /// # Parameters
226    /// - `t`: The type to check for, which will be converted to an envelope
227    ///
228    /// # Returns
229    /// `true` if the envelope has the specified type, `false` otherwise
230    ///
231    /// # Examples
232    ///
233    /// ```
234    /// use bc_envelope::prelude::*;
235    ///
236    /// // Create a typed envelope
237    /// let document = Envelope::new("Contract")
238    ///     .add_type("LegalDocument")
239    ///     .add_assertion("status", "Draft");
240    ///
241    /// // Check for various types
242    /// assert!(document.has_type("LegalDocument"));
243    /// assert!(!document.has_type("Spreadsheet"));
244    ///
245    /// // Can also check with an envelope
246    /// let legal_doc_type = Envelope::new("LegalDocument");
247    /// assert!(document.has_type(legal_doc_type));
248    /// ```
249    pub fn has_type(&self, t: impl EnvelopeEncodable) -> bool {
250        let e = t.into_envelope();
251        self.types().iter().any(|x| x.digest() == e.digest())
252    }
253
254    /// Checks if the envelope has a specific type, using a Known Value as the
255    /// type identifier.
256    ///
257    /// Similar to `has_type`, but specifically for checking against
258    /// standard Known Value types from the registry.
259    ///
260    /// # Parameters
261    /// - `t`: The Known Value type to check for
262    ///
263    /// # Returns
264    /// `true` if the envelope has the specified Known Value type, `false`
265    /// otherwise
266    ///
267    /// # Examples
268    ///
269    /// ```
270    /// use bc_envelope::prelude::*;
271    ///
272    /// // Create a seed envelope
273    /// let seed_data = "seed data".to_string();
274    /// let seed = Envelope::new(seed_data).add_type(known_values::SEED_TYPE);
275    ///
276    /// // Check the type using the Known Value
277    /// assert!(seed.has_type_value(&known_values::SEED_TYPE));
278    /// assert!(!seed.has_type_value(&known_values::PRIVATE_KEY_TYPE));
279    /// ```
280    pub fn has_type_value(&self, t: &KnownValue) -> bool {
281        let type_envelope: Envelope = t.clone().to_envelope();
282        self.types()
283            .iter()
284            .any(|x| x.digest() == type_envelope.digest())
285    }
286
287    /// Verifies that the envelope has a specific Known Value type.
288    ///
289    /// This method is similar to `has_type_value` but returns a Result, making
290    /// it suitable for use in validation chains with the `?` operator.
291    ///
292    /// # Parameters
293    /// - `t`: The Known Value type to check for
294    ///
295    /// # Returns
296    /// - `Ok(())`: If the envelope has the specified type
297    /// - `Err(EnvelopeError::InvalidType)`: If the envelope does not have the
298    ///   specified type
299    ///
300    /// # Examples
301    ///
302    /// ```
303    /// use bc_envelope::prelude::*;
304    ///
305    /// // Function that processes a seed envelope
306    /// fn process_seed(envelope: &Envelope) -> EnvelopeResult<String> {
307    ///     // Verify this is a seed
308    ///     envelope.check_type_value(&known_values::SEED_TYPE)?;
309    ///
310    ///     // Extract the seed data
311    ///     let seed_data: String = envelope.subject().try_into()?;
312    ///     Ok(seed_data)
313    /// }
314    ///
315    /// // Create a seed envelope
316    /// let seed_data = "seed data".to_string();
317    /// let seed =
318    ///     Envelope::new(seed_data.clone()).add_type(known_values::SEED_TYPE);
319    ///
320    /// // Process the seed
321    /// let result = process_seed(&seed);
322    /// assert!(result.is_ok());
323    /// assert_eq!(result.unwrap(), seed_data);
324    ///
325    /// // Create a non-seed envelope
326    /// let not_a_seed = Envelope::new("Not a seed").add_type("SomethingElse");
327    ///
328    /// // Processing should fail
329    /// let result = process_seed(&not_a_seed);
330    /// assert!(result.is_err());
331    /// ```
332    pub fn check_type_value(&self, t: &KnownValue) -> Result<()> {
333        if self.has_type_value(t) {
334            Ok(())
335        } else {
336            Err(Error::InvalidType)
337        }
338    }
339
340    /// Verifies that the envelope has a specific type, using an envelope as the
341    /// type identifier.
342    ///
343    /// This method is similar to `has_type` but returns a Result,
344    /// making it suitable for use in validation chains with the `?`
345    /// operator.
346    ///
347    /// # Parameters
348    /// - `t`: The type to check for, which will be converted to an envelope
349    ///
350    /// # Returns
351    /// - `Ok(())`: If the envelope has the specified type
352    /// - `Err(EnvelopeError::InvalidType)`: If the envelope does not have the
353    ///   specified type
354    ///
355    /// # Examples
356    ///
357    /// ```
358    /// use bc_envelope::prelude::*;
359    ///
360    /// // Function that processes a person
361    /// fn process_person(envelope: &Envelope) -> EnvelopeResult<String> {
362    ///     // Verify this is a person
363    ///     envelope.check_type("Person")?;
364    ///
365    ///     // Extract the name
366    ///     let name: String = envelope.subject().try_into()?;
367    ///     Ok(name)
368    /// }
369    ///
370    /// // Create a person envelope
371    /// let person = Envelope::new("Alice").add_type("Person");
372    ///
373    /// // Process the person
374    /// let result = process_person(&person);
375    /// assert!(result.is_ok());
376    /// assert_eq!(result.unwrap(), "Alice");
377    ///
378    /// // Create a non-person envelope
379    /// let document = Envelope::new("Contract").add_type("Document");
380    ///
381    /// // Processing should fail
382    /// let result = process_person(&document);
383    /// assert!(result.is_err());
384    /// ```
385    pub fn check_type(&self, t: impl EnvelopeEncodable) -> Result<()> {
386        if self.has_type(t) {
387            Ok(())
388        } else {
389            Err(Error::InvalidType)
390        }
391    }
392}