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