bc_envelope/extension/
types.rs

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