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