daml_macro/
path.rs

1/// Construct a Daml data extractor function from a path expression.
2///
3/// This macro provides a concise DSL for constructing a Daml data extractor closure as required by
4/// [`DamlRecord::extract`] and [`DamlValue::extract`].  The closure produced will have the following signature:
5///
6/// `Fn(&DamlRecord) -> DamlResult<R>`
7///
8/// The type of `R` depends on the path expression provided and may either be a reference to a [`DamlValue`] or a
9/// reference to another type such as `&str`.
10///
11/// # Syntax
12///
13/// Path expressions take the following form (pseudo-regex syntax):
14///
15/// ``` txt
16/// field ( '{' '=>' variant '}' )? ( '[' index ']' )? ( '?' )?  (  '/'  ...  )*  ( '::' type )?
17/// ----- ------------------------- ------------------ --------  -  ---  ---  --  --------------
18///  (1)              (2)                   (3)          (4)     |  (5)  (6)  |        (7)
19/// ```
20///
21/// Each `field` corresponds to a labelled [`DamlRecordField`] within the [`DamlRecord`] on which the data extractor
22/// is to be executed.
23///
24/// Syntax Items:
25///
26/// 1. the `field` of the current [`DamlValue::Record`] to traverse
27/// 2. extract the [`DamlValue`] if `field` is a [`DamlValue::Variant`] and `variant` matches the constructor.
28/// 3. extract the [`DamlValue`] from list at `index` (expression) if `field` is a [`DamlValue::List`]
29/// 4. extract the [`DamlValue`] if `field` is a [`DamlValue::Optional`]
30/// 5. a separator between `field` entries
31/// 6. a repeat of items `(1)`, `(2)`, `(3)` & `(4)`.  Items `(5)` & `(6)` are repeated zero or many times.
32/// 7. an optional type specifier
33///
34/// Note that any or all of `(2)`, `(3)` & `(4)` can be applied for a single `field` and will run consecutively.  i.e.
35/// in the single path field `people{=>Persons}[0]?` will interpret `people` as a [`DamlValue::Variant`] with a
36/// constructor of `Persons` which is a [`DamlValue::List`] which has an element at index `0` which contain an
37/// [`DamlValue::Optional`] that is a [`DamlValue::Int64`].
38///
39/// Note that the path expression is not whitespace sensitive.
40///
41/// Fields which are nested [`DamlRecord`] can be traversed by chaining together multiple `field` elements delimited
42/// with a `/` character.  Nesting can be of arbitrary depth (up to the default recursive macro limit).  Attempting
43/// to access a non-existent field will return in an [`UnknownField`] error being returned.
44///
45/// List elements of `fields` which are of type [`DamlValue::List`] can be accessed by specifying a list `index`
46/// expression inside `[]` braces.  A [`ListIndexOutOfRange`] error will be returned if an attempt is made to access
47/// lists elements outside the available bounds.
48///
49/// Optional `fields` of type [`DamlValue::Optional`] can be accessed by appending a `?` character to the record
50/// `field` name or list element.  If the optional is `Some` then the embedded [`DamlValue`] is extracted and
51/// processing of the path expression continues.  If the optional is `None` then a [`MissingRequiredField`] error
52/// will be returned.
53///
54/// The final `field` may optionally have a `type` specifier by appending a `::` token followed by one
55/// of several supported `type` specifier codes.  If no `type` specifier code is provided then the expression will
56/// return a [`DamlValue`] otherwise a type appropriate to the specifier will be returned.
57///
58/// If the constructor of a [`DamlValue::Variant`] does not match then [`UnexpectedVariant`] error is returned.  The
59/// special variant value of `__` can be used to indicate that any variant is acceptable.  Attempting to access the
60/// nested [`DamlValue`] within a variant will produce an error if the type of the item does not match the actual
61/// variant type.
62///
63/// The supported `type` specifiers are:
64///
65/// | code   | name        | Rust type                 | value variant             |
66/// |--------|-------------|---------------------------|----------------------------
67/// | `c`    | contract id | `&str`                    | [`DamlValue::ContractId`] |
68/// | `u`    | unit        | `&()`                     | [`DamlValue::Unit`]       |
69/// | `p`    | party       | `&str`                    | [`DamlValue::Party`]      |
70/// | `i`    | int64       | `&i64`                    | [`DamlValue::Int64`]      |
71/// | `f`    | numeric     | `&BigInteger`             | [`DamlValue::Numeric`]    |
72/// | `t`    | text        | `&str`                    | [`DamlValue::Text`]       |
73/// | `s`    | timestamp   | `&DateTime<Utc>`          | [`DamlValue::Timestamp`]  |
74/// | `b`    | boolean     | `&bool`                   | [`DamlValue::Bool`]       |
75/// | `d`    | date        | `&Date<Utc>`              | [`DamlValue::Date`]       |
76/// | `r`    | record      | `&DamlRecord`             | [`DamlValue::Record`]     |
77/// | `l`    | list        | `&Vec<DamlValue>`         | [`DamlValue::List`]       |
78/// | `v`    | variant     | `&DamlVariant`            | [`DamlValue::Variant`]    |
79///
80/// # Limitations
81///
82/// Path expressions only support labelled [`DamlRecordField`] fields.  Accessing unlabelled fields will result in
83/// an [`UnknownField`] error being returned.
84///
85/// Accessing values from a list-of-list is not supported and therefore path expression syntax `my_list[0][1]` is not
86/// valid.  To access the sublist first extract the parent list with the path expression `my_list[0]` and then apply a
87/// second path expression to the resulting [`DamlValue`].  Note that a list containing [`DamlRecord`] which in turn
88/// contains nested lists is supported.
89///
90/// All `type` specifiers return references, even for simple copy types, and so must be dereferenced as needed.
91///
92/// There is currently no support for [`DamlValue::Map`], [`DamlValue::GenMap`] or [`DamlValue::Enum`].
93///
94/// # Examples
95///
96/// ```
97/// # use std::convert::TryFrom;
98/// # use daml_grpc::data::value::{DamlRecord, DamlValue};
99/// # use daml_grpc::data::{DamlResult, DamlError};
100/// # use daml::macros::{daml_value, daml_path};
101/// # use bigdecimal::BigDecimal;
102/// # fn main() -> DamlResult<()> {
103/// let value = daml_value![{
104///     party: "Alice"::p,
105///     trade: {
106///         trade_id: 123,
107///         counterparty: "Bob"::p,
108///         trade_details: {
109///             ticker: "GOOG",
110///             prices: [1231.54, 1234.85, 1237.92]
111///         },
112///         order_type: {?= "MarketOrder"}
113///     }
114/// }];
115/// assert_eq!("Alice", value.extract(daml_path![party::p])?);
116/// assert_eq!(123, *value.extract(daml_path![trade/trade_id::i])?);
117/// assert_eq!("Bob", value.extract(daml_path![trade/counterparty::p])?);
118/// assert_eq!("GOOG", value.extract(daml_path![trade/trade_details/ticker::t])?);
119/// assert_eq!(&BigDecimal::try_from(1234.85).unwrap(),
120///                 value.extract(daml_path![trade/trade_details/prices[1]::f])?);
121/// assert_eq!("MarketOrder", value.extract(daml_path![trade/order_type?::t])?);
122/// # Ok(())
123/// # }
124/// ```
125/// [`DamlValue::extract`]: ::daml_grpc::data::value::DamlValue::extract
126/// [`DamlRecord::extract`]: ::daml_grpc::data::value::DamlRecord::extract
127/// [`DamlValue`]: ::daml_grpc::data::value::DamlValue
128/// [`DamlRecord`]: ::daml_grpc::data::value::DamlRecord
129/// [`DamlRecordField`]: ::daml_grpc::data::value::DamlRecordField
130/// [`DamlValue::List`]: ::daml_grpc::data::value::DamlValue::List
131/// [`DamlValue::Optional`]: ::daml_grpc::data::value::DamlValue::Optional
132/// [`DamlValue::Record`]: ::daml_grpc::data::value::DamlValue::Record
133/// [`DamlValue::Variant`]: ::daml_grpc::data::value::DamlValue::Variant
134/// [`DamlValue::Int64`]: ::daml_grpc::data::value::DamlValue::Int64
135/// [`DamlValue::ContractId`]: ::daml_grpc::data::value::DamlValue::ContractId
136/// [`DamlValue::Unit`]: ::daml_grpc::data::value::DamlValue::Unit
137/// [`DamlValue::Party`]: ::daml_grpc::data::value::DamlValue::Party
138/// [`DamlValue::Numeric`]: ::daml_grpc::data::value::DamlValue::Numeric
139/// [`DamlValue::Text`]: ::daml_grpc::data::value::DamlValue::Text
140/// [`DamlValue::Timestamp`]: ::daml_grpc::data::value::DamlValue::Timestamp
141/// [`DamlValue::Bool`]: ::daml_grpc::data::value::DamlValue::Bool
142/// [`DamlValue::Date`]: ::daml_grpc::data::value::DamlValue::Date
143/// [`DamlValue::Map`]: ::daml_grpc::data::value::DamlValue::Map
144/// [`DamlValue::GenMap`]: ::daml_grpc::data::value::DamlValue::GenMap
145/// [`DamlValue::Enum`]: ::daml_grpc::data::value::DamlValue::Enum
146/// [`ListIndexOutOfRange`]: ::daml_grpc::data::DamlError::ListIndexOutOfRange
147/// [`MissingRequiredField`]: ::daml_grpc::data::DamlError::MissingRequiredField
148/// [`UnknownField`]: ::daml_grpc::data::DamlError::UnknownField
149/// [`UnexpectedVariant`]: ::daml_grpc::data::DamlError::UnexpectedVariant
150#[macro_export]
151macro_rules! daml_path {
152
153    // The order that these matching rules is critical so be very careful if attempting to modify or reformat this
154    // macro.  The structure of this macro is split into five sections:
155    //
156    // 1: final-path-element matchers (i.e. "... / field")
157    // 2: non-final-path-element matchers (i.e. "field / ...")
158    // 3: leaf value matchers
159    // 4: helper "function" matchers
160    // 5: public entry point matcher
161    //
162    // Sections 1 & 2 define several matchers to cover cases for lists, options and variants.
163    //
164    // The macro has become very complex and should be rewritten as a procedural macro.
165
166    // final path element (list + optional case)
167    ( @priv $record:ident / $path:ident $( { => $variant:ident } )? [ $index:expr ] ? $( :: $type:ident )? ) => {
168        {
169            let field_value = daml_path!(@get_record_field $record, $path);
170            let variant_value = daml_path!(@get_variant_value field_value, $($variant)? )?;
171            let list_item_value = daml_path!(@get_list_item variant_value, $index);
172            let optional_value = list_item_value.try_optional()?.ok_or(DamlError::MissingRequiredField)?;
173            daml_path!(@priv $($type)? optional_value)
174        }
175    };
176
177    // final path element (list case)
178    ( @priv $record:ident / $path:ident $( { => $variant:ident } )? [ $index:expr ] $( :: $type:ident )? ) => {
179        {
180            let field_value = daml_path!(@get_record_field $record, $path);
181            let variant_value = daml_path!(@get_variant_value field_value, $($variant)? )?;
182            let list_item_value = daml_path!(@get_list_item variant_value, $index);
183            daml_path!(@priv $($type)? list_item_value)
184        }
185    };
186
187    // final path element
188    ( @priv $record:ident / $path:ident $( { => $variant:ident } )? $( :: $type:ident )? ) => {
189        {
190            let field_value = daml_path!(@get_record_field $record, $path);
191            let variant_value = daml_path!(@get_variant_value field_value, $($variant)? )?;
192            daml_path!(@priv $($type)? variant_value)
193        }
194    };
195
196    // final path element (optional case)
197    ( @priv $record:ident / $path:ident $( { => $variant:ident } )? ? $( :: $type:ident )? ) => {
198        {
199            let field_value = daml_path!(@get_record_field $record, $path);
200            let variant_value = daml_path!(@get_variant_value field_value, $($variant)? )?;
201            let optional_value = variant_value.try_optional()?.ok_or(DamlError::MissingRequiredField)?;
202            daml_path!(@priv $($type)? optional_value)
203        }
204    };
205
206    // non-final path element (list + optional case)
207    ( @priv $record:ident / $path:ident $( { => $variant:ident } )? [ $index:expr ] ? $($rest:tt)* ) => {
208        {
209            let field_value = daml_path!(@get_record_field $record, $path);
210            let variant_value = daml_path!(@get_variant_value field_value, $($variant)? )?;
211            let list_item_value = daml_path!(@get_list_item variant_value, $index);
212            let optional_value = list_item_value.try_optional()?.ok_or(DamlError::MissingRequiredField)?;
213            let field_as_record = &(optional_value.try_record()?);
214            daml_path!( @priv field_as_record $($rest)* )
215        }
216    };
217
218    // non-final path element (list case)
219    ( @priv $record:ident / $path:ident $( { => $variant:ident } )? [ $index:expr ] $($rest:tt)* ) => {
220        {
221            let field_value = daml_path!(@get_record_field $record, $path);
222            let value_from_variant = daml_path!(@get_variant_value field_value, $($variant)? )?;
223            let list_item_value = daml_path!(@get_list_item value_from_variant, $index);
224            let field_as_record = &(list_item_value.try_record()?);
225            daml_path!( @priv field_as_record $($rest)* )
226        }
227    };
228
229    // non-final path element (optional case)
230    ( @priv $record:ident / $path:ident $( { => $variant:ident } )? ? $($rest:tt)* ) => {
231        {
232            let field_value = daml_path!(@get_record_field $record, $path);
233            let variant_value = daml_path!(@get_variant_value field_value, $($variant)? )?;
234            let optional_value = variant_value.try_optional()?.ok_or(DamlError::MissingRequiredField)?;
235            let field_as_record = &(optional_value.try_record()?);
236            daml_path!( @priv field_as_record $($rest)* )
237        }
238    };
239
240    // non-final path element (special case for nested variant to resolve parsing ambiguity)
241    ( @priv $record:ident / $path:ident { => $variant:ident } $($rest0:tt)* ) => {
242        {
243            let field_value = daml_path!(@get_record_field $record, $path);
244            let variant_value = daml_path!(@get_variant_value field_value, $variant )?;
245            let field_as_record = &(variant_value.try_record()?);
246            daml_path!( @priv field_as_record $($rest0)* )
247        }
248    };
249
250    // non-final path element
251    ( @priv $record:ident / $path:ident $( { => $variant:ident } )? $($rest1:tt)* ) => {
252        {
253            let field_value = daml_path!(@get_record_field $record, $path);
254            let variant_value = daml_path!(@get_variant_value field_value, $($variant)? )?;
255            let field_as_record = &(variant_value.try_record()?);
256            daml_path!( @priv field_as_record $($rest1)* )
257        }
258    };
259
260    // leaf cases
261    ( @priv $value:ident ) => {
262        {
263            let res: DamlResult<&DamlValue> = Ok($value);
264            res
265        }
266    };
267    ( @priv c $value:ident ) => {
268        $value.try_contract_id()
269    };
270    ( @priv u $value:ident ) => {
271        $value.try_unit_ref()
272    };
273    ( @priv p $value:ident ) => {
274        $value.try_party()
275    };
276    ( @priv i $value:ident ) => {
277        $value.try_int64_ref()
278    };
279    ( @priv f $value:ident ) => {
280        $value.try_numeric()
281    };
282    ( @priv t $value:ident ) => {
283        $value.try_text()
284    };
285    ( @priv b $value:ident ) => {
286        $value.try_bool_ref()
287    };
288    ( @priv s $value:ident ) => {
289        $value.try_timestamp_ref()
290    };
291    ( @priv d $value:ident ) => {
292        $value.try_date_ref()
293    };
294    ( @priv r $value:ident ) => {
295        $value.try_record()
296    };
297    ( @priv l $value:ident ) => {
298        $value.try_list()
299    };
300    ( @priv v $value:ident ) => {
301        $value.try_variant()
302    };
303
304    // get a named field from a record
305    ( @get_record_field $record:ident, $path:ident ) => {
306        $record.field(stringify!($path))?
307    };
308
309    // interpret the value as a list and extract the item from a given index
310    ( @get_list_item $value:ident, $index:expr ) => {
311        {
312            let field_as_list = $value.try_list()?;
313            let list_item_value: &DamlValue = field_as_list.get($index).ok_or(DamlError::ListIndexOutOfRange($index))?;
314            list_item_value
315        }
316    };
317
318    // get the value from with a variant
319    ( @get_variant_value $value:ident , $variant:ident ) => {
320        {
321            let variant = $value.try_variant()?;
322            if stringify!($variant) == "__" || variant.constructor() == stringify!($variant) {
323                Ok(variant.value())
324            } else {
325                Err(DamlError::UnexpectedVariant(stringify!($variant).to_owned(), variant.constructor().to_owned()))
326            }
327        }
328    };
329
330    // get the value from with a variant (identity case)
331    ( @get_variant_value $value:ident , ) => {
332        {
333            let res: DamlResult<_> = Ok($value);
334            res
335        }
336    };
337
338    // the public entry point to this macro
339    ( $($rest:tt)* ) => {
340        {
341            use $crate::daml_grpc::data::DamlResult;
342            use $crate::daml_grpc::data::value::DamlRecord;
343            let func: fn(&DamlRecord) -> DamlResult<&_> = | rec_ref: &DamlRecord | {
344                daml_path!( @priv rec_ref / $($rest)* )
345            };
346            func
347        }
348    };
349}
350
351#[cfg(test)]
352mod test {
353    use crate::daml_value;
354    use crate::test_util::TestResult;
355    use crate::test_util::{make_date, make_timestamp};
356    use bigdecimal::BigDecimal;
357    use daml_grpc::data::value::{DamlRecord, DamlValue, DamlVariant};
358    use daml_grpc::data::DamlError;
359    use std::convert::TryFrom;
360
361    #[test]
362    pub fn test_top_party() -> TestResult {
363        let value: DamlValue = get_test_value();
364        assert_eq!("Alice", value.extract(daml_path![sender::p])?);
365        Ok(())
366    }
367
368    #[test]
369    pub fn test_record_top_party() -> TestResult {
370        let value: DamlValue = get_test_value();
371        let record: &DamlRecord = value.try_record()?;
372        assert_eq!("Alice", record.extract(daml_path![sender::p])?);
373        Ok(())
374    }
375
376    #[test]
377    pub fn test_nested_party() -> TestResult {
378        let value: DamlValue = get_test_value();
379        assert_eq!("Sue", value.extract(daml_path![person / party::p])?);
380        Ok(())
381    }
382
383    #[test]
384    pub fn test_nested_contract() -> TestResult {
385        let value: DamlValue = get_test_value();
386        assert_eq!("#1:1", value.extract(daml_path![person / data / contractId::c])?);
387        Ok(())
388    }
389
390    #[test]
391    pub fn test_nested_value() -> TestResult {
392        let value: DamlValue = get_test_value();
393        assert_eq!(&DamlValue::new_contract_id("#1:1"), value.extract(daml_path![person / data / contractId])?);
394        Ok(())
395    }
396
397    #[test]
398    pub fn test_nested_int64() -> TestResult {
399        let value: DamlValue = get_test_value();
400        assert_eq!(0_i64, *value.extract(daml_path![person / data / count::i])?);
401        Ok(())
402    }
403
404    #[test]
405    pub fn test_nested_bool() -> TestResult {
406        let value: DamlValue = get_test_value();
407        assert!(!*value.extract(daml_path![person / data / is_true::b])?);
408        Ok(())
409    }
410
411    #[test]
412    pub fn test_nested_unit() -> TestResult {
413        let value: DamlValue = get_test_value();
414        assert_eq!((), *value.extract(daml_path![person / empty::u])?);
415        Ok(())
416    }
417
418    #[test]
419    pub fn test_top_numeric() -> TestResult {
420        let value: DamlValue = get_test_value();
421        assert_eq!(&BigDecimal::try_from(1.23).unwrap(), value.extract(daml_path![height::f])?);
422        Ok(())
423    }
424
425    #[test]
426    pub fn test_top_date() -> TestResult {
427        let value: DamlValue = get_test_value();
428        assert_eq!(&make_date("2019-01-02")?, value.extract(daml_path![today::d])?);
429        Ok(())
430    }
431
432    #[test]
433    pub fn test_top_datetime() -> TestResult {
434        let value: DamlValue = get_test_value();
435        assert_eq!(&make_timestamp("2019-01-02T03:45:56Z")?, value.extract(daml_path![right_now::s])?);
436        Ok(())
437    }
438
439    #[test]
440    pub fn test_top_optional_value() -> TestResult {
441        let value: DamlValue = get_test_value();
442        assert_eq!(&DamlValue::new_int64(123), value.extract(daml_path![opt_int?])?);
443        Ok(())
444    }
445
446    #[test]
447    pub fn test_top_variant_value() -> TestResult {
448        let value: DamlValue = get_test_value();
449        assert_eq!(&DamlValue::new_text("I'm a Foo"), value.extract(daml_path![variant_text{=>Foo}])?);
450        Ok(())
451    }
452
453    #[test]
454    pub fn test_top_variant() -> TestResult {
455        let value: DamlValue = get_test_value();
456        assert_eq!(
457            &DamlVariant::new("Foo", Box::new(DamlValue::new_text("I'm a Foo")), None),
458            value.extract(daml_path![variant_text::v])?
459        );
460        Ok(())
461    }
462
463    #[test]
464    pub fn test_nested_text() -> TestResult {
465        let value: DamlValue = get_test_value();
466        assert_eq!("apple", value.extract(daml_path![person / data / fruit::t])?);
467        Ok(())
468    }
469
470    #[test]
471    pub fn test_nested_record() -> TestResult {
472        let value: DamlValue = get_test_value();
473        assert_eq!(4, value.extract(daml_path![person / data::r])?.fields().len());
474        Ok(())
475    }
476
477    #[test]
478    pub fn test_nested_list() -> TestResult {
479        let value: DamlValue = get_test_value();
480        assert_eq!(3, value.extract(daml_path![person / stats::l])?.len());
481        Ok(())
482    }
483
484    #[test]
485    pub fn test_list_item() -> TestResult {
486        let value: DamlValue = get_test_value();
487        assert_eq!("a", value.extract(daml_path![items::l])?[0].extract(daml_path![a::t])?);
488        assert_eq!("b", value.extract(daml_path![items::l])?[1].extract(daml_path![b::t])?);
489        Ok(())
490    }
491
492    #[test]
493    pub fn test_top_list_record_item_text() -> TestResult {
494        let value: DamlValue = get_test_value();
495        assert_eq!("a", value.extract(daml_path![items[0] / a::t])?);
496        Ok(())
497    }
498
499    #[test]
500    pub fn test_top_list_leaf_item_text() -> TestResult {
501        let value: DamlValue = get_test_value();
502        assert_eq!("foo", value.extract(daml_path![simple_list[0]::t])?);
503        assert_eq!("bar", value.extract(daml_path![simple_list[1]::t])?);
504        Ok(())
505    }
506
507    #[test]
508    pub fn test_top_list_leaf_item_value() -> TestResult {
509        let value: DamlValue = get_test_value();
510        assert_eq!(&DamlValue::new_text("bar"), value.extract(daml_path![simple_list[1]])?);
511        Ok(())
512    }
513
514    #[test]
515    pub fn test_list_index_expression() -> TestResult {
516        let value: DamlValue = get_test_value();
517        assert_eq!("bar", value.extract(daml_path![simple_list[2 - 1]::t])?);
518        Ok(())
519    }
520
521    #[test]
522    pub fn test_nested_list_item_value() -> TestResult {
523        let value: DamlValue = get_test_value();
524        assert_eq!(&DamlValue::new_int64(1), value.extract(daml_path![person / stats[0]])?);
525        Ok(())
526    }
527
528    #[test]
529    pub fn test_nested_list_item_int64() -> TestResult {
530        let value: DamlValue = get_test_value();
531        assert_eq!(1, *value.extract(daml_path![person/stats[0]::i])?);
532        Ok(())
533    }
534
535    // Does not support list-of-list.  list-of-record-of-list is supported.
536    //#[test]
537    // pub fn test_list_in_list() -> TestResult {
538    //    let value: DamlValue = get_test_value();
539    //    assert_eq!(1, *value.path(daml_path![simple_list[2][0]])?);
540    //    Ok(())
541    //}
542
543    #[test]
544    pub fn test_list_in_list_of_record() -> TestResult {
545        let value: DamlValue = get_test_value();
546        assert_eq!(99, *value.extract(daml_path![items[1]/the_list[0]::i])?);
547        Ok(())
548    }
549
550    #[test]
551    pub fn test_optional_int() -> TestResult {
552        let value: DamlValue = get_test_value();
553        assert_eq!(123, *value.extract(daml_path![opt_int?::i])?);
554        Ok(())
555    }
556
557    #[test]
558    pub fn test_optional_rec() -> TestResult {
559        let value: DamlValue = get_test_value();
560        assert_eq!("cat", value.extract(daml_path![opt_rec? / pet::t])?);
561        Ok(())
562    }
563
564    #[test]
565    pub fn test_nested_optional_rec() -> TestResult {
566        let value: DamlValue = get_test_value();
567        assert!(*value.extract(daml_path![opt_rec?/is_cat?::b])?);
568        Ok(())
569    }
570
571    #[test]
572    pub fn test_list_of_optional_final() -> TestResult {
573        let value: DamlValue = get_test_value();
574        assert_eq!(1, *value.extract(daml_path![list_of_opt[0]?::i])?);
575        Ok(())
576    }
577
578    #[test]
579    pub fn test_list_of_optional_non_final() -> TestResult {
580        let value: DamlValue = get_test_value();
581        assert_eq!("a", value.extract(daml_path![list_of_opt_rec[0]? / a::t])?);
582        Ok(())
583    }
584
585    #[test]
586    pub fn test_top_named_variant_text() -> TestResult {
587        let value: DamlValue = get_test_value();
588        assert_eq!("I'm a Foo", value.extract(daml_path![variant_text{=>Foo}::t])?);
589        Ok(())
590    }
591
592    #[test]
593    pub fn test_top_named_variant_value() -> TestResult {
594        let value: DamlValue = get_test_value();
595        assert_eq!(&DamlValue::new_text("I'm a Foo"), value.extract(daml_path![variant_text{=>Foo}])?);
596        Ok(())
597    }
598
599    #[test]
600    pub fn test_top_any_variant_text() -> TestResult {
601        let value: DamlValue = get_test_value();
602        assert_eq!("I'm a Foo", value.extract(daml_path![variant_text{=>__}::t])?);
603        Ok(())
604    }
605
606    #[test]
607    pub fn test_top_named_variant_optional_int() -> TestResult {
608        let value: DamlValue = get_test_value();
609        assert_eq!(999, *value.extract(daml_path![variant_opt_int{=>Foo}?::i])?);
610        Ok(())
611    }
612
613    #[test]
614    pub fn test_top_named_variant_list() -> TestResult {
615        let value: DamlValue = get_test_value();
616        assert_eq!("One", value.extract(daml_path![variant_list{=>Foo}[0]::t])?);
617        Ok(())
618    }
619
620    #[test]
621    pub fn test_top_named_variant_list_optional() -> TestResult {
622        let value: DamlValue = get_test_value();
623        assert_eq!("Two", value.extract(daml_path![variant_list_opt{=>Foo}[1]?::t])?);
624        Ok(())
625    }
626
627    #[test]
628    pub fn test_nested_named_variant_int() -> TestResult {
629        let value: DamlValue = get_test_value();
630        assert_eq!(4, *value.extract(daml_path![other{=>Cat}/paw_count::i])?);
631        Ok(())
632    }
633
634    #[test]
635    pub fn test_nested_any_variant_int() -> TestResult {
636        let value: DamlValue = get_test_value();
637        assert_eq!(4, *value.extract(daml_path![other{=>__}/paw_count::i])?);
638        Ok(())
639    }
640
641    #[test]
642    pub fn test_nested_named_variant_optional_int() -> TestResult {
643        let value: DamlValue = get_test_value();
644        assert_eq!("Red", value.extract(daml_path![other_opt{=>Fruit}?/color::t])?);
645        Ok(())
646    }
647
648    #[test]
649    pub fn test_nested_named_variant_list() -> TestResult {
650        let value: DamlValue = get_test_value();
651        assert_eq!("Blue", value.extract(daml_path![other_var_list{=>Foo}[0]/color::t])?);
652        Ok(())
653    }
654
655    #[test]
656    pub fn test_nested_named_variant_list_optional() -> TestResult {
657        let value: DamlValue = get_test_value();
658        assert_eq!("Green", value.extract(daml_path![other_var_list_opt{=>Foo}[0]?/color::t])?);
659        Ok(())
660    }
661
662    #[test]
663    pub fn test_top_no_result_function() {
664        let value: DamlValue = get_test_value();
665        assert_eq!("Alice", value.extract(daml_path![sender::p]).expect("should not fail"));
666    }
667
668    #[test]
669    pub fn test_unknown_field() {
670        let value: DamlValue = get_test_value();
671        let result = value.extract(daml_path![unknown]);
672        match result {
673            Err(DamlError::UnknownField(s)) => assert_eq!("unknown", s),
674            _ => panic!("expected failure"),
675        }
676    }
677
678    #[test]
679    pub fn test_nested_unknown_field() {
680        let value: DamlValue = get_test_value();
681        let result = value.extract(daml_path![person / unknown]);
682        match result {
683            Err(DamlError::UnknownField(s)) => assert_eq!("unknown", s),
684            _ => panic!("expected failure"),
685        }
686    }
687
688    #[test]
689    pub fn test_wrong_type_field() {
690        let value: DamlValue = get_test_value();
691        let result = value.extract(daml_path![sender::i]);
692        match result {
693            Err(DamlError::UnexpectedType(expected, actual)) => {
694                assert_eq!("Int64", expected);
695                assert_eq!("Party", actual);
696            },
697            _ => panic!("expected failure"),
698        }
699    }
700
701    #[test]
702    pub fn test_list_index_out_of_range() {
703        let value: DamlValue = get_test_value();
704        let result = value.extract(daml_path![items[99]::i]);
705        match result {
706            Err(DamlError::ListIndexOutOfRange(idx)) => assert_eq!(99, idx),
707            _ => panic!("expected failure"),
708        }
709    }
710
711    #[test]
712    pub fn test_bad_variant() {
713        let value: DamlValue = get_test_value();
714        let result = value.extract(daml_path![variant_text{=>Bar}::t]);
715        match result {
716            Err(DamlError::UnexpectedVariant(expected, actual)) => {
717                assert_eq!("Bar", expected);
718                assert_eq!("Foo", actual);
719            },
720            _ => panic!("expected failure"),
721        }
722    }
723
724    fn get_test_value() -> DamlValue {
725        daml_value![{
726            sender: "Alice"::p,
727            receiver: "Bob"::p,
728            person: {
729                party: "Sue"::p,
730                data: {
731                    count: 0,
732                    fruit: "apple",
733                    contractId: "#1:1"::c,
734                    is_true: false
735                },
736                stats: [1, 2, 3],
737                empty: ()
738            },
739            height: 1.23,
740            items: [{a: "a"}, {b: "b", the_list: [99, 98, 101]}],
741            simple_list: ["foo", "bar", ["text in nested list"]],
742            today: "2019-01-02"::d,
743            right_now: "2019-01-02T03:45:56Z"::t,
744            opt_int: {?= 123},
745            opt_rec: {?= {
746                pet: "cat",
747                is_cat: {?= true},
748                food: ["ham", "eggs"]
749            }},
750            list_of_opt: [{?=1}, {?=2}, {?=3}],
751            opt_list: {?=[1,2,3]},
752            list_of_opt_rec: [
753                {?= {a: "a"}},
754                {?= {b: "b"}},
755                {?= {c: "c"}}
756            ],
757            variant_text: {=>Foo "I'm a Foo"},
758            variant_opt_int: {=>Foo {?= 999}},
759            variant_list: {=>Foo ["One", "Two"]},
760            variant_list_opt: {=>Foo [{?="One"}, {?="Two"}]},
761            other: {=>Cat {
762                paw_count: 4
763            }},
764            other_opt: {=>Fruit {?= {
765                color: "Red"
766            }}},
767            other_var_list: {=>Foo [{color: "Blue"}]},
768            other_var_list_opt: {=>Foo [{?= {color: "Green"}}]}
769        }]
770    }
771}