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(
128        self,
129        parameter: impl Into<Parameter>,
130        value: impl EnvelopeEncodable,
131    ) -> Self;
132
133    /// Adds a parameter to the expression if the value is not `None`.
134    ///
135    /// If the value is `Some`, this creates an assertion on the expression
136    /// envelope with the parameter as the predicate and the value as the
137    /// object. If the value is `None`, the parameter is not added.
138    ///
139    /// # Parameters
140    ///
141    /// * `parameter` - The parameter identifier
142    /// * `value` - An optional value for the parameter
143    ///
144    /// # Returns
145    ///
146    /// A new instance with the parameter added if the value is Some
147    ///
148    /// # Examples
149    ///
150    /// ```
151    /// use bc_envelope::prelude::*;
152    ///
153    /// let expression = Expression::new(functions::ADD)
154    ///     .with_parameter(parameters::LHS, 2)
155    ///     .with_optional_parameter(parameters::RHS, Some(3))
156    ///     .with_optional_parameter("note", None::<&str>);
157    /// ```
158    fn with_optional_parameter(
159        self,
160        parameter: impl Into<Parameter>,
161        value: Option<impl EnvelopeEncodable>,
162    ) -> Self;
163
164    //
165    // Parsing
166    //
167
168    /// Returns the function identifier of the expression.
169    ///
170    /// # Returns
171    ///
172    /// A reference to the Function of this expression
173    fn function(&self) -> &Function;
174
175    /// Returns the envelope that represents this expression.
176    ///
177    /// # Returns
178    ///
179    /// A reference to the Envelope of this expression
180    fn expression_envelope(&self) -> &Envelope;
181
182    /// Returns the argument (object) for the given parameter.
183    ///
184    /// # Parameters
185    ///
186    /// * `param` - The parameter to look up
187    ///
188    /// # Returns
189    ///
190    /// The argument envelope for the parameter, or an error if not found
191    ///
192    /// # Errors
193    ///
194    /// Returns an error if no matching parameter is found or if multiple
195    /// parameters match.
196    fn object_for_parameter(
197        &self,
198        param: impl Into<Parameter>,
199    ) -> anyhow::Result<Envelope>;
200
201    /// Returns all arguments (objects) for the given parameter.
202    ///
203    /// This method handles the case where a parameter appears multiple times.
204    ///
205    /// # Parameters
206    ///
207    /// * `param` - The parameter to look up
208    ///
209    /// # Returns
210    ///
211    /// A vector of all matching argument envelopes, or an empty vector if none
212    /// are found
213    fn objects_for_parameter(
214        &self,
215        param: impl Into<Parameter>,
216    ) -> Vec<Envelope>;
217
218    /// Returns the argument for the given parameter, decoded as the given type.
219    ///
220    /// # Parameters
221    ///
222    /// * `param` - The parameter to look up
223    ///
224    /// # Returns
225    ///
226    /// The argument for the parameter, decoded as type T
227    ///
228    /// # Errors
229    ///
230    /// Returns an error if:
231    /// - No matching parameter is found
232    /// - Multiple parameters match
233    /// - The argument cannot be decoded as type T
234    fn extract_object_for_parameter<T>(
235        &self,
236        param: impl Into<Parameter>,
237    ) -> Result<T>
238    where
239        T: TryFrom<CBOR, Error = dcbor::Error> + 'static;
240
241    /// Returns the argument for the given parameter, decoded as the given type,
242    /// or `None` if there is no matching parameter.
243    ///
244    /// # Parameters
245    ///
246    /// * `param` - The parameter to look up
247    ///
248    /// # Returns
249    ///
250    /// Some(T) if the parameter is found and can be decoded, or None if not
251    /// found
252    ///
253    /// # Errors
254    ///
255    /// Returns an error if:
256    /// - Multiple parameters match
257    /// - The argument cannot be decoded as type T
258    fn extract_optional_object_for_parameter<
259        T: TryFrom<CBOR, Error = dcbor::Error> + 'static,
260    >(
261        &self,
262        param: impl Into<Parameter>,
263    ) -> Result<Option<T>>;
264
265    /// Returns an array of arguments for the given parameter, decoded as the
266    /// given type.
267    ///
268    /// This method handles the case where a parameter appears multiple times.
269    ///
270    /// # Parameters
271    ///
272    /// * `param` - The parameter to look up
273    ///
274    /// # Returns
275    ///
276    /// A vector of all matching arguments, decoded as type T
277    ///
278    /// # Errors
279    ///
280    /// Returns an error if any of the arguments cannot be decoded as type T
281    fn extract_objects_for_parameter<T>(
282        &self,
283        param: impl Into<Parameter>,
284    ) -> Result<Vec<T>>
285    where
286        T: TryFrom<CBOR, Error = dcbor::Error> + 'static;
287}
288
289/// Implementation of ExpressionBehavior for Expression.
290impl ExpressionBehavior for Expression {
291    /// Adds a parameter to the expression.
292    fn with_parameter(
293        mut self,
294        parameter: impl Into<Parameter>,
295        value: impl EnvelopeEncodable,
296    ) -> Self {
297        let assertion =
298            Envelope::new_assertion(parameter.into(), value.into_envelope());
299        self.envelope =
300            self.envelope.add_assertion_envelope(assertion).unwrap();
301        self
302    }
303
304    /// Adds a parameter to the expression if the value is not None.
305    fn with_optional_parameter(
306        self,
307        parameter: impl Into<Parameter>,
308        value: Option<impl EnvelopeEncodable>,
309    ) -> Self {
310        if let Some(value) = value {
311            return self.with_parameter(parameter, value);
312        }
313        self
314    }
315
316    /// Returns the function of the expression.
317    fn function(&self) -> &Function { &self.function }
318
319    /// Returns the envelope representing the expression.
320    fn expression_envelope(&self) -> &Envelope { &self.envelope }
321
322    /// Returns the argument for the given parameter.
323    fn object_for_parameter(
324        &self,
325        param: impl Into<Parameter>,
326    ) -> anyhow::Result<Envelope> {
327        self.envelope.object_for_predicate(param.into())
328    }
329
330    /// Returns all arguments for the given parameter.
331    fn objects_for_parameter(
332        &self,
333        param: impl Into<Parameter>,
334    ) -> Vec<Envelope> {
335        self.envelope.objects_for_predicate(param.into())
336    }
337
338    /// Returns the argument for the given parameter, decoded as the given type.
339    fn extract_object_for_parameter<T>(
340        &self,
341        param: impl Into<Parameter>,
342    ) -> Result<T>
343    where
344        T: TryFrom<CBOR, Error = dcbor::Error> + 'static,
345    {
346        self.envelope.extract_object_for_predicate(param.into())
347    }
348
349    /// Returns the argument for the given parameter, decoded as the given type,
350    /// or None if there is no matching parameter.
351    fn extract_optional_object_for_parameter<
352        T: TryFrom<CBOR, Error = dcbor::Error> + 'static,
353    >(
354        &self,
355        param: impl Into<Parameter>,
356    ) -> Result<Option<T>> {
357        self.envelope
358            .extract_optional_object_for_predicate(param.into())
359    }
360
361    /// Returns an array of arguments for the given parameter, decoded as the
362    /// given type.
363    fn extract_objects_for_parameter<T>(
364        &self,
365        param: impl Into<Parameter>,
366    ) -> Result<Vec<T>>
367    where
368        T: TryFrom<CBOR, Error = dcbor::Error> + 'static,
369    {
370        self.envelope.extract_objects_for_predicate(param.into())
371    }
372}
373
374/// Allows converting an Expression to an Envelope.
375///
376/// This simply returns the envelope that represents the expression.
377impl From<Expression> for Envelope {
378    fn from(expression: Expression) -> Self { expression.envelope }
379}
380
381/// Allows converting an Envelope to an Expression.
382///
383/// This extracts the function from the envelope's subject and creates
384/// an Expression with that function and the envelope.
385///
386/// # Errors
387///
388/// Returns an error if the envelope's subject cannot be extracted as a
389/// Function.
390impl TryFrom<Envelope> for Expression {
391    type Error = dcbor::Error;
392
393    fn try_from(envelope: Envelope) -> dcbor::Result<Self> {
394        let function = envelope.extract_subject()?;
395        Ok(Self { function, envelope })
396    }
397}
398
399/// Allows converting an Envelope and optional expected function to an
400/// Expression.
401///
402/// This is similar to `TryFrom<Envelope>`, but it also checks that the function
403/// matches the expected function, if provided.
404///
405/// # Errors
406///
407/// Returns an error if:
408/// - The envelope's subject cannot be extracted as a Function
409/// - The expected function is provided and doesn't match the extracted function
410impl TryFrom<(Envelope, Option<&Function>)> for Expression {
411    type Error = dcbor::Error;
412
413    fn try_from(
414        (envelope, expected_function): (Envelope, Option<&Function>),
415    ) -> dcbor::Result<Self> {
416        let expression = Expression::try_from(envelope)?;
417        if let Some(expected_function) = expected_function {
418            if expression.function() != expected_function {
419                return Err(format!(
420                    "Expected function {:?}, but found {:?}",
421                    expected_function,
422                    expression.function()
423                )
424                .into());
425            }
426        }
427        Ok(expression)
428    }
429}
430
431/// A trait for converting types to Expression.
432///
433/// This trait provides convenience methods for converting a type to
434/// an Expression, either by consuming it or by cloning it.
435pub trait IntoExpression {
436    /// Converts this object into an Expression, consuming it.
437    fn into_expression(self) -> Expression;
438
439    /// Creates an Expression from this object, without consuming it.
440    fn to_expression(&self) -> Expression;
441}
442
443/// Implementation of IntoExpression for any type that can be converted to
444/// Expression.
445///
446/// This allows any type that implements `Into<Expression>` to be used with the
447/// convenience methods provided by the IntoExpression trait.
448impl<T: Into<Expression> + Clone> IntoExpression for T {
449    /// Converts this object into an Expression, consuming it.
450    fn into_expression(self) -> Expression { self.into() }
451
452    /// Creates an Expression from this object, without consuming it.
453    fn to_expression(&self) -> Expression { self.clone().into() }
454}
455
456#[cfg(test)]
457mod tests {
458    use indoc::indoc;
459
460    use super::*;
461    use crate::{functions, parameters};
462
463    #[test]
464    fn test_expression_1() -> anyhow::Result<()> {
465        crate::register_tags();
466
467        let expression = Expression::new(functions::ADD)
468            .with_parameter(parameters::LHS, 2)
469            .with_parameter(parameters::RHS, 3);
470
471        let envelope: Envelope = expression.clone().into();
472
473        #[rustfmt::skip]
474        let expected = indoc! {r#"
475            «add» [
476                ❰lhs❱: 2
477                ❰rhs❱: 3
478            ]
479        "#}.trim();
480        assert_eq!(envelope.format(), expected);
481
482        let parsed_expression = Expression::try_from(envelope)?;
483
484        assert_eq!(
485            parsed_expression
486                .extract_object_for_parameter::<i32>(parameters::LHS)?,
487            2
488        );
489        assert_eq!(
490            parsed_expression
491                .extract_object_for_parameter::<i32>(parameters::RHS)?,
492            3
493        );
494
495        assert_eq!(parsed_expression.function(), expression.function());
496        assert_eq!(
497            parsed_expression.expression_envelope(),
498            expression.expression_envelope()
499        );
500        assert_eq!(expression, parsed_expression);
501
502        Ok(())
503    }
504
505    #[test]
506    fn test_expression_2() -> anyhow::Result<()> {
507        crate::register_tags();
508
509        let expression = Expression::new("foo")
510            .with_parameter("bar", "baz")
511            .with_optional_parameter("qux", None::<&str>);
512
513        let envelope: Envelope = expression.clone().into();
514
515        #[rustfmt::skip]
516        let expected = indoc! {r#"
517            «"foo"» [
518                ❰"bar"❱: "baz"
519            ]
520        "#}.trim();
521        assert_eq!(envelope.format(), expected);
522
523        let parsed_expression = Expression::try_from(envelope)?;
524
525        assert_eq!(
526            parsed_expression.extract_object_for_parameter::<String>("bar")?,
527            "baz"
528        );
529        assert_eq!(
530            parsed_expression
531                .extract_optional_object_for_parameter::<i32>("qux")?,
532            None
533        );
534
535        assert_eq!(parsed_expression.function(), expression.function());
536        assert_eq!(
537            parsed_expression.expression_envelope(),
538            expression.expression_envelope()
539        );
540        assert_eq!(expression, parsed_expression);
541
542        Ok(())
543    }
544}