bc_envelope/extension/expressions/expression.rs
1use dcbor::prelude::*;
2
3use crate::{Envelope, EnvelopeEncodable, Function, Parameter, Result};
4
5/// An expression in a Gordian Envelope.
6///
7/// An expression consists of a function (the subject of the envelope) and zero
8/// or more parameters (as assertions on the envelope). It represents a
9/// computation or function call that can be evaluated.
10///
11/// Expressions form the foundation for Gordian Envelope's ability to represent
12/// computations, queries, and function calls within the envelope structure.
13///
14/// Expressions are documented in [BCR-2023-012: Envelope
15/// Expressions](https://github.com/BlockchainCommons/Research/blob/master/papers/bcr-2023-012-envelope-expression.md).
16///
17/// # Examples
18///
19/// A simple addition expression:
20///
21/// ```
22/// use bc_envelope::prelude::*;
23///
24/// // Create an expression that adds 2 and 3
25/// let expression = Expression::new(functions::ADD)
26/// .with_parameter(parameters::LHS, 2)
27/// .with_parameter(parameters::RHS, 3);
28/// ```
29///
30/// A more complex expression:
31///
32/// ```ignore
33/// use bc_envelope::prelude::*;
34///
35/// // Create a verify signature expression with a public key, signature, and digest
36/// let expression = Expression::new("verifySignature")
37/// .with_parameter("key", public_key)
38/// .with_parameter("sig", signature)
39/// .with_parameter("digest", message_digest);
40/// ```
41#[derive(Debug, Clone, PartialEq)]
42pub struct Expression {
43 /// The function being called in this expression
44 function: Function,
45 /// The envelope representing this expression
46 envelope: Envelope,
47}
48
49impl Expression {
50 /// Creates a new expression with the given function.
51 ///
52 /// The function becomes the subject of the expression envelope.
53 ///
54 /// # Parameters
55 ///
56 /// * `function` - The function identifier for this expression
57 ///
58 /// # Returns
59 ///
60 /// A new Expression instance
61 ///
62 /// # Examples
63 ///
64 /// ```
65 /// use bc_envelope::prelude::*;
66 ///
67 /// // Create a new expression with the ADD function
68 /// let expression = Expression::new(functions::ADD);
69 ///
70 /// // Create a new expression with a named function
71 /// let expression = Expression::new("verifySignature");
72 /// ```
73 pub fn new(function: impl Into<Function>) -> Self {
74 let function = function.into();
75 Self {
76 function: function.clone(),
77 envelope: Envelope::new(function),
78 }
79 }
80}
81
82/// Implements Display for Expression.
83///
84/// Outputs the formatted envelope representation of the expression.
85impl std::fmt::Display for Expression {
86 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
87 write!(f, "{:?}", self.envelope.format())
88 }
89}
90
91/// Behavior for working with expressions.
92///
93/// This trait defines methods for composing expressions by adding parameters,
94/// and for parsing and extracting values from expression parameters.
95///
96/// ExpressionBehavior is implemented by Expression and other types that
97/// represent expressions in Gordian Envelope.
98pub trait ExpressionBehavior {
99 //
100 // Composition
101 //
102
103 /// Adds a parameter to the expression.
104 ///
105 /// This creates an assertion on the expression envelope with the parameter
106 /// as the predicate and the value as the object.
107 ///
108 /// # Parameters
109 ///
110 /// * `parameter` - The parameter identifier
111 /// * `value` - The value for the parameter
112 ///
113 /// # Returns
114 ///
115 /// A new instance with the parameter added
116 ///
117 /// # Examples
118 ///
119 /// ```
120 /// use bc_envelope::prelude::*;
121 ///
122 /// let expression = Expression::new(functions::ADD)
123 /// .with_parameter(parameters::LHS, 2)
124 /// .with_parameter(parameters::RHS, 3);
125 /// ```
126 fn with_parameter(
127 self,
128 parameter: impl Into<Parameter>,
129 value: impl EnvelopeEncodable,
130 ) -> Self;
131
132 /// Adds a parameter to the expression if the value is not `None`.
133 ///
134 /// If the value is `Some`, this creates an assertion on the expression
135 /// envelope with the parameter as the predicate and the value as the
136 /// object. If the value is `None`, the parameter is not added.
137 ///
138 /// # Parameters
139 ///
140 /// * `parameter` - The parameter identifier
141 /// * `value` - An optional value for the parameter
142 ///
143 /// # Returns
144 ///
145 /// A new instance with the parameter added if the value is Some
146 ///
147 /// # Examples
148 ///
149 /// ```
150 /// use bc_envelope::prelude::*;
151 ///
152 /// let expression = Expression::new(functions::ADD)
153 /// .with_parameter(parameters::LHS, 2)
154 /// .with_optional_parameter(parameters::RHS, Some(3))
155 /// .with_optional_parameter("note", None::<&str>);
156 /// ```
157 fn with_optional_parameter(
158 self,
159 parameter: impl Into<Parameter>,
160 value: Option<impl EnvelopeEncodable>,
161 ) -> Self;
162
163 //
164 // Parsing
165 //
166
167 /// Returns the function identifier of the expression.
168 ///
169 /// # Returns
170 ///
171 /// A reference to the Function of this expression
172 fn function(&self) -> &Function;
173
174 /// Returns the envelope that represents this expression.
175 ///
176 /// # Returns
177 ///
178 /// A reference to the Envelope of this expression
179 fn expression_envelope(&self) -> &Envelope;
180
181 /// Returns the argument (object) for the given parameter.
182 ///
183 /// # Parameters
184 ///
185 /// * `param` - The parameter to look up
186 ///
187 /// # Returns
188 ///
189 /// The argument envelope for the parameter, or an error if not found
190 ///
191 /// # Errors
192 ///
193 /// Returns an error if no matching parameter is found or if multiple
194 /// parameters match.
195 fn object_for_parameter(
196 &self,
197 param: impl Into<Parameter>,
198 ) -> Result<Envelope>;
199
200 /// Returns all arguments (objects) for the given parameter.
201 ///
202 /// This method handles the case where a parameter appears multiple times.
203 ///
204 /// # Parameters
205 ///
206 /// * `param` - The parameter to look up
207 ///
208 /// # Returns
209 ///
210 /// A vector of all matching argument envelopes, or an empty vector if none
211 /// are found
212 fn objects_for_parameter(
213 &self,
214 param: impl Into<Parameter>,
215 ) -> Vec<Envelope>;
216
217 /// Returns the argument for the given parameter, decoded as the given type.
218 ///
219 /// # Parameters
220 ///
221 /// * `param` - The parameter to look up
222 ///
223 /// # Returns
224 ///
225 /// The argument for the parameter, decoded as type T
226 ///
227 /// # Errors
228 ///
229 /// Returns an error if:
230 /// - No matching parameter is found
231 /// - Multiple parameters match
232 /// - The argument cannot be decoded as type T
233 fn extract_object_for_parameter<T>(
234 &self,
235 param: impl Into<Parameter>,
236 ) -> Result<T>
237 where
238 T: TryFrom<CBOR, Error = dcbor::Error> + 'static;
239
240 /// Returns the argument for the given parameter, decoded as the given type,
241 /// or `None` if there is no matching parameter.
242 ///
243 /// # Parameters
244 ///
245 /// * `param` - The parameter to look up
246 ///
247 /// # Returns
248 ///
249 /// Some(T) if the parameter is found and can be decoded, or None if not
250 /// found
251 ///
252 /// # Errors
253 ///
254 /// Returns an error if:
255 /// - Multiple parameters match
256 /// - The argument cannot be decoded as type T
257 fn extract_optional_object_for_parameter<
258 T: TryFrom<CBOR, Error = dcbor::Error> + 'static,
259 >(
260 &self,
261 param: impl Into<Parameter>,
262 ) -> Result<Option<T>>;
263
264 /// Returns an array of arguments for the given parameter, decoded as the
265 /// given type.
266 ///
267 /// This method handles the case where a parameter appears multiple times.
268 ///
269 /// # Parameters
270 ///
271 /// * `param` - The parameter to look up
272 ///
273 /// # Returns
274 ///
275 /// A vector of all matching arguments, decoded as type T
276 ///
277 /// # Errors
278 ///
279 /// Returns an error if any of the arguments cannot be decoded as type T
280 fn extract_objects_for_parameter<T>(
281 &self,
282 param: impl Into<Parameter>,
283 ) -> Result<Vec<T>>
284 where
285 T: TryFrom<CBOR, Error = dcbor::Error> + 'static;
286}
287
288/// Implementation of ExpressionBehavior for Expression.
289impl ExpressionBehavior for Expression {
290 /// Adds a parameter to the expression.
291 fn with_parameter(
292 mut self,
293 parameter: impl Into<Parameter>,
294 value: impl EnvelopeEncodable,
295 ) -> Self {
296 let assertion =
297 Envelope::new_assertion(parameter.into(), value.into_envelope());
298 self.envelope =
299 self.envelope.add_assertion_envelope(assertion).unwrap();
300 self
301 }
302
303 /// Adds a parameter to the expression if the value is not None.
304 fn with_optional_parameter(
305 self,
306 parameter: impl Into<Parameter>,
307 value: Option<impl EnvelopeEncodable>,
308 ) -> Self {
309 if let Some(value) = value {
310 return self.with_parameter(parameter, value);
311 }
312 self
313 }
314
315 /// Returns the function of the expression.
316 fn function(&self) -> &Function { &self.function }
317
318 /// Returns the envelope representing the expression.
319 fn expression_envelope(&self) -> &Envelope { &self.envelope }
320
321 /// Returns the argument for the given parameter.
322 fn object_for_parameter(
323 &self,
324 param: impl Into<Parameter>,
325 ) -> Result<Envelope> {
326 self.envelope.object_for_predicate(param.into())
327 }
328
329 /// Returns all arguments for the given parameter.
330 fn objects_for_parameter(
331 &self,
332 param: impl Into<Parameter>,
333 ) -> Vec<Envelope> {
334 self.envelope.objects_for_predicate(param.into())
335 }
336
337 /// Returns the argument for the given parameter, decoded as the given type.
338 fn extract_object_for_parameter<T>(
339 &self,
340 param: impl Into<Parameter>,
341 ) -> Result<T>
342 where
343 T: TryFrom<CBOR, Error = dcbor::Error> + 'static,
344 {
345 self.envelope.extract_object_for_predicate(param.into())
346 }
347
348 /// Returns the argument for the given parameter, decoded as the given type,
349 /// or None if there is no matching parameter.
350 fn extract_optional_object_for_parameter<
351 T: TryFrom<CBOR, Error = dcbor::Error> + 'static,
352 >(
353 &self,
354 param: impl Into<Parameter>,
355 ) -> Result<Option<T>> {
356 self.envelope
357 .extract_optional_object_for_predicate(param.into())
358 }
359
360 /// Returns an array of arguments for the given parameter, decoded as the
361 /// given type.
362 fn extract_objects_for_parameter<T>(
363 &self,
364 param: impl Into<Parameter>,
365 ) -> Result<Vec<T>>
366 where
367 T: TryFrom<CBOR, Error = dcbor::Error> + 'static,
368 {
369 self.envelope.extract_objects_for_predicate(param.into())
370 }
371}
372
373/// Allows converting an Expression to an Envelope.
374///
375/// This simply returns the envelope that represents the expression.
376impl From<Expression> for Envelope {
377 fn from(expression: Expression) -> Self { expression.envelope }
378}
379
380/// Allows converting an Envelope to an Expression.
381///
382/// This extracts the function from the envelope's subject and creates
383/// an Expression with that function and the envelope.
384///
385/// # Errors
386///
387/// Returns an error if the envelope's subject cannot be extracted as a
388/// Function.
389impl TryFrom<Envelope> for Expression {
390 type Error = dcbor::Error;
391
392 fn try_from(envelope: Envelope) -> dcbor::Result<Self> {
393 let function = envelope.extract_subject().map_err(|e| e.to_string())?;
394 Ok(Self { function, envelope })
395 }
396}
397
398/// Allows converting an Envelope and optional expected function to an
399/// Expression.
400///
401/// This is similar to `TryFrom<Envelope>`, but it also checks that the function
402/// matches the expected function, if provided.
403///
404/// # Errors
405///
406/// Returns an error if:
407/// - The envelope's subject cannot be extracted as a Function
408/// - The expected function is provided and doesn't match the extracted function
409impl TryFrom<(Envelope, Option<&Function>)> for Expression {
410 type Error = dcbor::Error;
411
412 fn try_from(
413 (envelope, expected_function): (Envelope, Option<&Function>),
414 ) -> dcbor::Result<Self> {
415 let expression = Expression::try_from(envelope)?;
416 if let Some(expected_function) = expected_function {
417 if expression.function() != expected_function {
418 return Err(format!(
419 "Expected function {:?}, but found {:?}",
420 expected_function,
421 expression.function()
422 )
423 .into());
424 }
425 }
426 Ok(expression)
427 }
428}
429
430/// A trait for converting types to Expression.
431///
432/// This trait provides convenience methods for converting a type to
433/// an Expression, either by consuming it or by cloning it.
434pub trait IntoExpression {
435 /// Converts this object into an Expression, consuming it.
436 fn into_expression(self) -> Expression;
437
438 /// Creates an Expression from this object, without consuming it.
439 fn to_expression(&self) -> Expression;
440}
441
442/// Implementation of IntoExpression for any type that can be converted to
443/// Expression.
444///
445/// This allows any type that implements `Into<Expression>` to be used with the
446/// convenience methods provided by the IntoExpression trait.
447impl<T: Into<Expression> + Clone> IntoExpression for T {
448 /// Converts this object into an Expression, consuming it.
449 fn into_expression(self) -> Expression { self.into() }
450
451 /// Creates an Expression from this object, without consuming it.
452 fn to_expression(&self) -> Expression { self.clone().into() }
453}
454
455#[cfg(test)]
456mod tests {
457 use indoc::indoc;
458
459 use super::*;
460 use crate::{functions, parameters};
461
462 #[test]
463 fn test_expression_1() -> Result<()> {
464 crate::register_tags();
465
466 let expression = Expression::new(functions::ADD)
467 .with_parameter(parameters::LHS, 2)
468 .with_parameter(parameters::RHS, 3);
469
470 let envelope: Envelope = expression.clone().into();
471
472 #[rustfmt::skip]
473 let expected = indoc! {r#"
474 «add» [
475 ❰lhs❱: 2
476 ❰rhs❱: 3
477 ]
478 "#}.trim();
479 assert_eq!(envelope.format(), expected);
480
481 let parsed_expression = Expression::try_from(envelope)?;
482
483 assert_eq!(
484 parsed_expression
485 .extract_object_for_parameter::<i32>(parameters::LHS)?,
486 2
487 );
488 assert_eq!(
489 parsed_expression
490 .extract_object_for_parameter::<i32>(parameters::RHS)?,
491 3
492 );
493
494 assert_eq!(parsed_expression.function(), expression.function());
495 assert_eq!(
496 parsed_expression.expression_envelope(),
497 expression.expression_envelope()
498 );
499 assert_eq!(expression, parsed_expression);
500
501 Ok(())
502 }
503
504 #[test]
505 fn test_expression_2() -> Result<()> {
506 crate::register_tags();
507
508 let expression = Expression::new("foo")
509 .with_parameter("bar", "baz")
510 .with_optional_parameter("qux", None::<&str>);
511
512 let envelope: Envelope = expression.clone().into();
513
514 #[rustfmt::skip]
515 let expected = indoc! {r#"
516 «"foo"» [
517 ❰"bar"❱: "baz"
518 ]
519 "#}.trim();
520 assert_eq!(envelope.format(), expected);
521
522 let parsed_expression = Expression::try_from(envelope)?;
523
524 assert_eq!(
525 parsed_expression.extract_object_for_parameter::<String>("bar")?,
526 "baz"
527 );
528 assert_eq!(
529 parsed_expression
530 .extract_optional_object_for_parameter::<i32>("qux")?,
531 None
532 );
533
534 assert_eq!(parsed_expression.function(), expression.function());
535 assert_eq!(
536 parsed_expression.expression_envelope(),
537 expression.expression_envelope()
538 );
539 assert_eq!(expression, parsed_expression);
540
541 Ok(())
542 }
543}