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(¬_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}