bc_envelope/extension/expressions/
expression.rs

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