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}