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}