daml_macro/
value.rs

1/// Construct a Daml value.
2///
3/// This macro provide a concise DSL for constructing an [`DamlValue`] which can be used in the creation of contracts
4/// and the exercising of choices.
5///
6/// # Syntax
7///
8/// A Daml `record` is a map of `label` to `value`.  A `label` is always an identifier.  A `value` can be
9/// a `literal`, an `identifier`, an `expression`, a `list`, an `optional`, a `variant` or a nested `record`.
10///
11/// The macro always creates a [`DamlValue`] enum which may be of any allowed enum variant.  Typically a [`DamlRecord`]
12/// is being constructed and so a [`DamlValue::Record`] is produced.
13///
14/// Daml value pseudo-BNF grammar:
15///
16/// ``` bnf
17/// DamlValue ::=     '{' ( label ':' DamlValue ',' )* '}'  // Record
18///                 | '[' ( DamlValue ',' )* ']'            // List
19///                 | '{' '=>' variant DamlValue '}'        // Variant
20///                 | '{' '?=' DamlValue '}'                // Optional (Some)
21///                 | '{' '?!' '}'                          // Optional (None)
22///                 | identifier ('::' type)?               // String identifier
23///                 | literal ('::' type)?                  // String literal
24///                 | (expression) ('::' type)?             // DamlValue expression
25/// ```
26///
27/// Note that this syntax is not whitespace sensitive.
28///
29/// The supported optional `type` specifiers are:
30///
31/// | code   | name        | type                    | example                     |
32/// |--------|-------------|-------------------------| -----------------------------
33/// | `p`    | party       | `DamlValue::Party`      | `"Alice"::p`                |
34/// | `c`    | contract id | `DamlValue::ContractId` | `"#1:1"::c`                 |
35/// | `d`    | date        | `DamlValue::Date`       | `"2019-01-01"::d`           |
36/// | `t`    | timestamp   | `DamlValue::Timestamp`  | `"2019-01-01T01:23:45Z"::t` |
37///
38/// String literal used without a type specifier are assumed to be [`DamlValue::Text`] therefore type specifiers are
39/// only required for [`DamlValue::Party`] (`::p`) and [`DamlValue::ContractId`] (`::c`).
40///
41/// # Limitations
42///
43/// Data values passed as expressions (as opposed to simple literal values or identifiers) must be placed inside
44/// parentheses.
45///
46/// For example you may specify `my_party_name_str::p` where `my_party_name_str` is the identifier of an
47/// in-scope variable containing a &str but you may not specify `get_party_name_str()::p` where `get_party_name_str()`
48/// is a function (and therefore an expression).
49///
50/// To support such cases either use `(get_party_name_str())::p` or provide a [`DamlValue`]
51/// `DamlValue::new_party(get_party_name_str())`.
52///
53/// There is currently no support for [`DamlValue::Map`], [`DamlValue::GenMap`] or [`DamlValue::Enum`].
54///
55/// # Examples
56///
57/// ```
58/// # use daml_grpc::data::value::{DamlRecord, DamlValue, DamlVariant};
59/// # use daml_grpc::data::{DamlResult, DamlError};
60/// # use daml::macros::daml_value;
61/// # fn main() -> DamlResult<()> {
62/// let value = daml_value![
63///     // the DamlValue being created is a DamlValue::Record
64///     {
65///         // party is DamlValue::Party specified as a literal with a type suffix
66///         party: "Alice"::p,
67///
68///         // a nested DamlValue::Record
69///         trade: {
70///
71///             // a literal DamlValue::Int64
72///             trade_id: 123,
73///
74///             // counterparty is DamlValue::Party, provided as an expression (note the outer
75///             // braces here)
76///             counterparty: (DamlValue::new_party("B".to_owned() + "ob")),
77///
78///             // trader is a DamlValue::Party literal built from an expression
79///             trader: ("Ali".to_owned() + "ce")::p,
80///
81///             // trade_details is a DamlValue::Variant specifying SimpleDetails which contains
82///             // a nested DamlValue::Record)
83///             trade_details: {=>SimpleDetails {
84///
85///                     // ticker is a String without type suffix and so becomes a
86///                     // DamlValue::Text
87///                     ticker: "GOOG",
88///
89///                     // prices is an DamlValue::Optional which is Some(DamlValue::List(...))
90///                     prices: {?=[1231.54, 1234.85, 1237.92]},
91///
92///                     // description is an DamlValue::Optional which is None
93///                     description: {?!},
94///
95///                     // side is a DamlValue::Variant specifying Buy which contains
96///                     // a DamlValue::Text
97///                     side: {=>Buy "MarketOrder"}
98///                 }
99///             }
100///         }
101///     }
102/// ];
103/// # Ok(())
104/// # }
105/// ```
106/// [`DamlValue`]: ::daml_grpc::data::value::DamlValue
107/// [`DamlRecord`]: ::daml_grpc::data::value::DamlRecord
108/// [`DamlValue::Record`]: ::daml_grpc::data::value::DamlValue::Record
109/// [`DamlValue::ContractId`]: ::daml_grpc::data::value::DamlValue::ContractId
110/// [`DamlValue::Party`]: ::daml_grpc::data::value::DamlValue::Party
111/// [`DamlValue::Text`]: ::daml_grpc::data::value::DamlValue::Text
112/// [`DamlValue::Map`]: ::daml_grpc::data::value::DamlValue::Map
113/// [`DamlValue::GenMap`]: ::daml_grpc::data::value::DamlValue::GenMap
114/// [`DamlValue::Enum`]: ::daml_grpc::data::value::DamlValue::Enum
115
116#[macro_export]
117macro_rules! daml_value {
118
119    //
120    // Covers DamlValue:Unit case
121    //
122    () => {
123        DamlValue::Unit
124    };
125
126    //
127    // Covers DamlValue::List case
128    //
129    ([ $( $value:tt $( :: $type:ident )? ),* ]) => {
130        {
131            use $crate::daml_grpc::data::value::DamlValue;
132            #[allow(unused_mut)]
133            let mut list = vec![];
134            $(
135                #[allow(clippy::vec_init_then_push)]
136                list.push(daml_value!($( @priv $type )? $value));
137            )*
138            DamlValue::List(list)
139        }
140    };
141
142    //
143    // Covers DamlValue::Record case
144    //
145    ( { $( $label:ident : $value:tt $( :: $type:ident )? ),* } ) => {
146        {
147            use $crate::daml_grpc::data::value::{DamlValue, DamlRecordBuilder};
148            #[allow(unused_mut)]
149            let mut rec_builder = DamlRecordBuilder::new();
150            $(
151                rec_builder = rec_builder.add_field(stringify![$label], daml_value!($( @priv $type )? $value));
152            )*
153            DamlValue::new_record(rec_builder.build())
154        }
155    };
156
157    //
158    // Covers DamlValue::Variant case
159    //
160    ( { => $variant:ident $($value:tt)* } ) => {
161        {
162            use $crate::daml_grpc::data::value::{DamlValue, DamlVariant};
163            let val = daml_value!($($value)*);
164            let variant = DamlVariant::new(stringify!($variant), Box::new(val), None);
165            DamlValue::new_variant(variant)
166        }
167    };
168
169    //
170    // Covers DamlValue::Optional (Some) case
171    //
172    ( { ?= $($value:tt)* } ) => {
173        {
174            use $crate::daml_grpc::data::value::DamlValue;
175            let val = daml_value!($($value)*);
176            DamlValue::new_optional(Some(val))
177        }
178    };
179
180    //
181    // Covers DamlValue::Optional (None) case
182    //
183    ( { ?! } ) => {
184        {
185            use $crate::daml_grpc::data::value::DamlValue;
186            DamlValue::new_optional(None)
187        }
188    };
189
190    //
191    // Covers DamlValue::Bool, DamlValue::Text, DamlValue::Int64 & DamlValue::Numeric cases
192    //
193    ($prim:expr) => {
194        {
195            use std::convert::TryFrom;
196            $crate::daml_grpc::data::value::DamlValue::try_from($prim).expect("invalid numeric")
197        }
198    };
199
200    //
201    // Covers DamlValue::Party, DamlValue::ContractId, DamlValue::Timestamp & DamlValue::Date cases
202    //
203    ($party:tt :: p) => {
204        daml_value!(@priv p $party)
205    };
206    ($contract:tt :: c) => {
207        daml_value!(@priv c $contract)
208    };
209    ($timestamp:tt :: t) => {
210        daml_value!(@priv t $timestamp)
211    };
212    ($date:tt :: d) => {
213        daml_value!(@priv d $date)
214    };
215
216    //
217    // These cases will only be called from within the macro and so we prefix with @priv to indicate they are private
218    //
219    (@priv p $party:expr) => {
220        $crate::daml_grpc::data::value::DamlValue::new_party($party)
221    };
222    (@priv c $contract:expr) => {
223        $crate::daml_grpc::data::value::DamlValue::new_contract_id($contract)
224    };
225    (@priv t $timestamp:expr) => {
226        $crate::daml_grpc::data::value::DamlValue::new_timestamp($timestamp.parse::<$crate::chrono::DateTime<$crate::chrono::Utc>>().unwrap()) // TODO shouldn't unwrap here!
227    };
228    (@priv d $date:expr) => {
229        // TODO date parsing should be flexible and performed in DamlValue, not here!
230        $crate::daml_grpc::data::value::DamlValue::new_date($crate::chrono::Date::<$crate::chrono::Utc>::from_utc($crate::chrono::NaiveDate::parse_from_str($date, "%Y-%m-%d").unwrap(), $crate::chrono::Utc))
231    };
232}
233
234#[cfg(test)]
235mod test {
236    use crate::test_util::TestResult;
237    use crate::test_util::{make_date, make_timestamp};
238    use bigdecimal::BigDecimal;
239    use daml_grpc::data::value::DamlValue;
240    use std::convert::TryFrom;
241
242    #[test]
243    pub fn test_unit_value() -> TestResult {
244        let value: DamlValue = daml_value![];
245        assert_eq!((), value.try_unit()?);
246        Ok(())
247    }
248
249    #[test]
250    pub fn test_string_value() -> TestResult {
251        let value: DamlValue = daml_value!["Test"];
252        assert_eq!("Test", value.try_text()?);
253        Ok(())
254    }
255
256    #[test]
257    pub fn test_party_value() -> TestResult {
258        let value: DamlValue = daml_value![DamlValue::new_party("Test")];
259        assert_eq!("Test", value.try_party()?);
260        Ok(())
261    }
262
263    #[test]
264    pub fn test_party_literal() -> TestResult {
265        let value: DamlValue = daml_value!["Test"::p];
266        assert_eq!("Test", value.try_party()?);
267        Ok(())
268    }
269
270    #[test]
271    pub fn test_contract_id_value() -> TestResult {
272        let value: DamlValue = daml_value![DamlValue::new_contract_id("123")];
273        assert_eq!("123", value.try_contract_id()?);
274        Ok(())
275    }
276
277    #[test]
278    pub fn test_contract_id_literal() -> TestResult {
279        let value: DamlValue = daml_value!["123"::c];
280        assert_eq!("123", value.try_contract_id()?);
281        Ok(())
282    }
283
284    #[test]
285    pub fn test_int64_value() -> TestResult {
286        let value: DamlValue = daml_value![42];
287        assert_eq!(42, value.try_int64()?);
288        Ok(())
289    }
290
291    #[test]
292    pub fn test_bool_value() -> TestResult {
293        let value: DamlValue = daml_value![true];
294        assert!(value.try_bool()?);
295        Ok(())
296    }
297
298    #[test]
299    pub fn test_numeric_value() -> TestResult {
300        let value: DamlValue = daml_value![1.23];
301        assert_eq!(&BigDecimal::try_from(1.23).unwrap(), value.try_numeric()?);
302        Ok(())
303    }
304
305    #[test]
306    pub fn test_timestamp_value() -> TestResult {
307        let value: DamlValue = daml_value!["2019-01-02T03:45:56Z"::t];
308        assert_eq!(make_timestamp("2019-01-02T03:45:56Z")?, value.try_timestamp()?);
309        Ok(())
310    }
311
312    #[test]
313    pub fn test_timestamp_literal() -> TestResult {
314        let timestamp = make_timestamp("2019-01-02T03:45:56Z")?;
315        let value: DamlValue = daml_value![DamlValue::new_timestamp(timestamp)];
316        assert_eq!(timestamp, value.try_timestamp()?);
317        Ok(())
318    }
319
320    #[test]
321    pub fn test_date_literal() -> TestResult {
322        let value: DamlValue = daml_value!["2019-01-02"::d];
323        assert_eq!(make_date("2019-01-02")?, value.try_date()?);
324        Ok(())
325    }
326
327    #[test]
328    pub fn test_date_value() -> TestResult {
329        let date = make_date("2019-01-02")?;
330        let value: DamlValue = daml_value![DamlValue::new_date(date)];
331        assert_eq!(date, value.try_date()?);
332        Ok(())
333    }
334
335    #[test]
336    pub fn test_empty_record() -> TestResult {
337        let value: DamlValue = daml_value![{}];
338        assert_eq!(0, value.try_record()?.fields().len());
339        Ok(())
340    }
341
342    #[test]
343    pub fn test_simple_record() -> TestResult {
344        let value: DamlValue = daml_value![{
345            sender: "Alice"::p,
346            receiver: "Bob"::p,
347            count: 0
348        }];
349        assert_eq!("Alice", value.try_record()?.fields()[0].value().try_party()?);
350        assert_eq!("Bob", value.try_record()?.fields()[1].value().try_party()?);
351        assert_eq!(0, value.try_record()?.fields()[2].value().try_int64()?);
352        Ok(())
353    }
354
355    #[test]
356    pub fn test_optional_none() -> TestResult {
357        let value: DamlValue = daml_value![{?!}];
358        assert_eq!(None, value.try_optional()?);
359        Ok(())
360    }
361
362    #[test]
363    pub fn test_optional_text() -> TestResult {
364        let value: DamlValue = daml_value![{?="bar"}];
365        assert_eq!("bar", value.try_optional()?.ok_or("not ok")?.try_text()?);
366        Ok(())
367    }
368
369    #[test]
370    pub fn test_optional_record() -> TestResult {
371        let value: DamlValue = daml_value![{?={
372            sender: "Alice"::p,
373            receiver: "Bob"::p,
374            count: 0
375        }}];
376        assert_eq!("Alice", value.try_optional()?.ok_or("not ok")?.try_record()?.fields()[0].value().try_party()?);
377        assert_eq!("Bob", value.try_optional()?.ok_or("not ok")?.try_record()?.fields()[1].value().try_party()?);
378        assert_eq!(0, value.try_optional()?.ok_or("not ok")?.try_record()?.fields()[2].value().try_int64()?);
379        Ok(())
380    }
381
382    #[test]
383    pub fn test_variant_text() -> TestResult {
384        let value: DamlValue = daml_value![{=>foo "bar"}];
385        assert_eq!("bar", value.try_variant()?.value().try_text()?);
386        assert_eq!("foo", value.try_variant()?.constructor());
387        Ok(())
388    }
389
390    #[test]
391    pub fn test_variant_party_literal() -> TestResult {
392        let value: DamlValue = daml_value![{=>PartyId "Bob"::p}];
393        assert_eq!("Bob", value.try_variant()?.value().try_party()?);
394        assert_eq!("PartyId", value.try_variant()?.constructor());
395        Ok(())
396    }
397
398    #[test]
399    pub fn test_variant_record() -> TestResult {
400        let value: DamlValue = daml_value![{=>Variant1 {
401            sender: "Alice"::p,
402            receiver: "Bob"::p,
403            count: 0
404        }}];
405        assert_eq!("Alice", value.try_variant()?.value().try_record()?.fields()[0].value().try_party()?);
406        assert_eq!("Bob", value.try_variant()?.value().try_record()?.fields()[1].value().try_party()?);
407        assert_eq!(0, value.try_variant()?.value().try_record()?.fields()[2].value().try_int64()?);
408        assert_eq!("Variant1", value.try_variant()?.constructor());
409        Ok(())
410    }
411
412    #[test]
413    pub fn test_nested_variant() -> TestResult {
414        let value: DamlValue = daml_value![{
415            data: {=>SomeVariant "data from SomeVariant"}
416        }];
417        assert_eq!("data from SomeVariant", value.try_record()?.fields()[0].value().try_variant()?.value().try_text()?);
418        assert_eq!("SomeVariant", value.try_record()?.fields()[0].value().try_variant()?.constructor());
419        Ok(())
420    }
421
422    #[test]
423    pub fn test_nested_variant_record() -> TestResult {
424        let value: DamlValue = daml_value![{
425            data: {=>foo {a: "test"}}
426        }];
427        assert_eq!(
428            "test",
429            value.try_record()?.fields()[0].value().try_variant()?.value().try_record()?.fields()[0]
430                .value()
431                .try_text()?
432        );
433        Ok(())
434    }
435
436    #[test]
437    pub fn test_nested_variant_record_optional() -> TestResult {
438        let value: DamlValue = daml_value![{
439            sender: "Alice"::p,
440            receiver: "Bob"::p,
441            count: 0,
442            data: {=>foo {
443                mand_text: "test",
444                opt_int: {?= 0},
445                opt_node: {?!}
446            }}
447        }];
448        assert_eq!(
449            "test",
450            value.try_record()?.fields()[3].value().try_variant()?.value().try_record()?.fields()[0]
451                .value()
452                .try_text()?
453        );
454        assert_eq!(
455            0,
456            value.try_record()?.fields()[3].value().try_variant()?.value().try_record()?.fields()[1]
457                .value()
458                .try_optional()?
459                .ok_or("not ok")?
460                .try_int64()?
461        );
462        assert_eq!(
463            None,
464            value.try_record()?.fields()[3].value().try_variant()?.value().try_record()?.fields()[2]
465                .value()
466                .try_optional()?
467        );
468        Ok(())
469    }
470
471    #[test]
472    pub fn test_nested_record() -> TestResult {
473        let value: DamlValue = daml_value![{
474            sender: "Alice"::p,
475            receiver: "Bob"::p,
476            data: {
477                count: 0,
478                fruit: "apple",
479                contractId: "#1:1"::c
480            }
481        }];
482        assert_eq!("Alice", value.try_record()?.fields()[0].value().try_party()?);
483        assert_eq!("Bob", value.try_record()?.fields()[1].value().try_party()?);
484        assert_eq!(0, value.try_record()?.fields()[2].value().try_record()?.fields()[0].value().try_int64()?);
485        assert_eq!("apple", value.try_record()?.fields()[2].value().try_record()?.fields()[1].value().try_text()?);
486        assert_eq!(
487            "#1:1",
488            value.try_record()?.fields()[2].value().try_record()?.fields()[2].value().try_contract_id()?
489        );
490        Ok(())
491    }
492
493    #[test]
494    pub fn test_empty_list() -> TestResult {
495        let value: DamlValue = daml_value!([]);
496        assert_eq!(0, value.try_list()?.len());
497        Ok(())
498    }
499
500    #[test]
501    pub fn test_simple_list() -> TestResult {
502        let value: DamlValue = daml_value!([1, 2, 3]);
503        assert_eq!(1, value.try_list()?[0].try_int64()?);
504        assert_eq!(3, value.try_list()?.len());
505        Ok(())
506    }
507
508    #[test]
509    pub fn test_list_of_empty_records() -> TestResult {
510        let value: DamlValue = daml_value!([{}, {}]);
511        assert_eq!(2, value.try_list()?.len());
512        assert_eq!(0, value.try_list()?[1].try_record()?.fields().len());
513        Ok(())
514    }
515
516    #[test]
517    pub fn test_list_of_record() -> TestResult {
518        let value: DamlValue = daml_value!(
519            [{
520                foo: "bar",
521                bar: "foo"
522            }]
523        );
524        assert_eq!(1, value.try_list()?.len());
525        assert_eq!("foo", value.try_list()?[0].try_record()?.fields()[1].value().try_text()?);
526        Ok(())
527    }
528
529    #[test]
530    pub fn test_list_of_mixed() -> TestResult {
531        let value: DamlValue = daml_value!(
532            [{
533                foo: "bar",
534                bar: "foo"
535            },
536            "Bob"::p,
537            10
538            ]
539        );
540        assert_eq!(3, value.try_list()?.len());
541        assert_eq!("foo", value.try_list()?[0].try_record()?.fields()[1].value().try_text()?);
542        assert_eq!("Bob", value.try_list()?[1].try_party()?);
543        assert_eq!(10, value.try_list()?[2].try_int64()?);
544        Ok(())
545    }
546
547    #[test]
548    pub fn test_expressions() -> TestResult {
549        let value: DamlValue = daml_value![{
550            name: (String::from("John")),
551            age: (21 + 3)
552        }];
553        assert_eq!("John", value.try_record()?.fields()[0].value().try_text()?);
554        assert_eq!(24, value.try_record()?.fields()[1].value().try_int64()?);
555        Ok(())
556    }
557
558    #[test]
559    pub fn test_from_variables() -> TestResult {
560        let party = "John";
561        let age = 21;
562        let dob = "1999-12-31";
563        let home_address = "somewhere";
564        let value: DamlValue = daml_value![{
565            party: party::p,
566            age: age,
567            dob: dob::d,
568            sex: "Male",
569            address: home_address
570        }];
571        assert_eq!("John", value.try_record()?.fields()[0].value().try_party()?);
572        assert_eq!(21, value.try_record()?.fields()[1].value().try_int64()?);
573        assert_eq!(make_date("1999-12-31")?, value.try_record()?.fields()[2].value().try_date()?);
574        assert_eq!("Male", value.try_record()?.fields()[3].value().try_text()?);
575        assert_eq!("somewhere", value.try_record()?.fields()[4].value().try_text()?);
576        Ok(())
577    }
578}