1use std::collections::BTreeMap;
6
7use canton_proto::com::daml::ledger::api::v2 as pb;
8
9use crate::primitives::{
10 ContractId, Date, GenMap, Int64, NestedOpt, Numeric, Party, Timestamp, Unit,
11};
12
13#[derive(Clone, Debug, PartialEq, Eq)]
19pub struct ValueError {
20 path: Vec<String>,
22 message: String,
23}
24
25impl ValueError {
26 #[must_use]
30 pub fn new(message: impl Into<String>) -> Self {
31 Self {
32 path: Vec::new(),
33 message: message.into(),
34 }
35 }
36
37 #[must_use]
40 pub fn at(mut self, field: impl Into<String>) -> Self {
41 self.path.insert(0, field.into());
42 self
43 }
44
45 #[must_use]
47 pub fn message(&self) -> &str {
48 &self.message
49 }
50
51 #[must_use]
54 pub fn path(&self) -> &[String] {
55 &self.path
56 }
57}
58
59impl std::fmt::Display for ValueError {
60 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
61 if self.path.is_empty() {
62 write!(f, "value conversion error: {}", self.message)
63 } else {
64 write!(
65 f,
66 "value conversion error at `{}`: {}",
67 self.path.join("."),
68 self.message
69 )
70 }
71 }
72}
73
74impl std::error::Error for ValueError {}
75
76impl From<ValueError> for canton_core::Error {
80 fn from(error: ValueError) -> Self {
81 canton_core::Error::Payload(Box::new(error))
82 }
83}
84
85pub trait ToValue {
87 fn to_value(&self) -> pb::Value;
89}
90
91pub trait FromValue: Sized {
93 fn from_value(value: &pb::Value) -> Result<Self, ValueError>;
98}
99
100fn wrap(sum: pb::value::Sum) -> pb::Value {
101 pb::Value { sum: Some(sum) }
102}
103
104fn sum(value: &pb::Value) -> Result<&pb::value::Sum, ValueError> {
105 value
106 .sum
107 .as_ref()
108 .ok_or_else(|| ValueError::new("empty Value (no sum set)"))
109}
110
111fn kind(sum: &pb::value::Sum) -> &'static str {
123 use pb::value::Sum;
124 match sum {
125 Sum::Unit(()) => "Unit",
126 Sum::Bool(_) => "Bool",
127 Sum::Int64(_) => "Int64",
128 Sum::Date(_) => "Date",
129 Sum::Timestamp(_) => "Timestamp",
130 Sum::Numeric(_) => "Numeric",
131 Sum::Party(_) => "Party",
132 Sum::Text(_) => "Text",
133 Sum::ContractId(_) => "ContractId",
134 Sum::Optional(_) => "Optional",
135 Sum::List(_) => "List",
136 Sum::TextMap(_) => "TextMap",
137 Sum::GenMap(_) => "GenMap",
138 Sum::Record(_) => "Record",
139 Sum::Variant(_) => "Variant",
140 Sum::Enum(_) => "Enum",
141 }
142}
143
144fn mismatch(expected: &str, got: &pb::value::Sum) -> ValueError {
145 ValueError::new(format!("expected {expected}, got {}", kind(got)))
146}
147
148#[must_use]
153pub fn record_fields(fields: Vec<(&str, pb::Value)>) -> pb::Record {
154 pb::Record {
155 record_id: None,
156 fields: fields
157 .into_iter()
158 .map(|(label, value)| pb::RecordField {
159 label: label.to_string(),
160 value: Some(value),
161 })
162 .collect(),
163 }
164}
165
166#[must_use]
169pub fn record(fields: Vec<(&str, pb::Value)>) -> pb::Value {
170 wrap(pb::value::Sum::Record(record_fields(fields)))
171}
172
173pub fn find_field<'a>(
188 value: &'a pb::Value,
189 index: usize,
190 label: &str,
191) -> Result<Option<&'a pb::Value>, ValueError> {
192 match sum(value)? {
193 pb::value::Sum::Record(record) => {
194 if let Some(field) = record
195 .fields
196 .iter()
197 .find(|field| !field.label.is_empty() && field.label == label)
198 {
199 return Ok(field.value.as_ref());
200 }
201 if let Some(field) = record.fields.get(index)
202 && field.label.is_empty()
203 {
204 return Ok(field.value.as_ref());
205 }
206 Ok(None)
207 }
208 other => Err(mismatch("Record", other)),
209 }
210}
211
212pub fn required_field<'a>(
218 value: &'a pb::Value,
219 index: usize,
220 label: &str,
221) -> Result<&'a pb::Value, ValueError> {
222 find_field(value, index, label)?
223 .ok_or_else(|| ValueError::new(format!("record has no field `{label}` (index {index})")))
224}
225
226pub fn optional_field<T: FromValue + AbsentField>(
235 value: &pb::Value,
236 index: usize,
237 label: &str,
238) -> Result<T, ValueError> {
239 match find_field(value, index, label)? {
240 Some(present) => T::from_value(present),
241 None => Ok(T::absent()),
242 }
243}
244
245pub trait AbsentField {
248 fn absent() -> Self;
250}
251
252impl<T> AbsentField for Option<T> {
253 fn absent() -> Self {
254 None
255 }
256}
257
258impl<T> AbsentField for NestedOpt<T> {
259 fn absent() -> Self {
260 NestedOpt(None)
261 }
262}
263
264pub fn from_record<T: FromValue>(record: &pb::Record) -> Result<T, ValueError> {
272 let value = pb::Value {
273 sum: Some(pb::value::Sum::Record(record.clone())),
274 };
275 T::from_value(&value)
276}
277
278#[must_use]
282pub fn unit_value() -> pb::Value {
283 wrap(pb::value::Sum::Unit(()))
284}
285
286#[must_use]
288pub fn enum_value(constructor: &str) -> pb::Value {
289 wrap(pb::value::Sum::Enum(pb::Enum {
290 enum_id: None,
291 constructor: constructor.to_string(),
292 }))
293}
294
295pub fn enum_constructor(value: &pb::Value) -> Result<&str, ValueError> {
300 match sum(value)? {
301 pb::value::Sum::Enum(enumeration) => Ok(enumeration.constructor.as_str()),
302 other => Err(mismatch("Enum", other)),
303 }
304}
305
306#[must_use]
308pub fn variant_value(constructor: &str, value: pb::Value) -> pb::Value {
309 wrap(pb::value::Sum::Variant(Box::new(pb::Variant {
310 variant_id: None,
311 constructor: constructor.to_string(),
312 value: Some(Box::new(value)),
313 })))
314}
315
316pub fn variant_parts(value: &pb::Value) -> Result<(&str, &pb::Value), ValueError> {
321 match sum(value)? {
322 pb::value::Sum::Variant(variant) => {
323 let payload = variant
324 .value
325 .as_deref()
326 .ok_or_else(|| ValueError::new("variant carries no value"))?;
327 Ok((variant.constructor.as_str(), payload))
328 }
329 other => Err(mismatch("Variant", other)),
330 }
331}
332
333#[must_use]
336pub fn unexpected_constructor(type_name: &str, got: &str) -> ValueError {
337 ValueError::new(format!("`{type_name}` has no constructor `{got}`"))
338}
339
340impl ToValue for bool {
343 fn to_value(&self) -> pb::Value {
344 wrap(pb::value::Sum::Bool(*self))
345 }
346}
347impl FromValue for bool {
348 fn from_value(value: &pb::Value) -> Result<Self, ValueError> {
349 match sum(value)? {
350 pb::value::Sum::Bool(b) => Ok(*b),
351 other => Err(mismatch("Bool", other)),
352 }
353 }
354}
355
356impl ToValue for i64 {
361 fn to_value(&self) -> pb::Value {
362 wrap(pb::value::Sum::Int64(*self))
363 }
364}
365impl FromValue for i64 {
366 fn from_value(value: &pb::Value) -> Result<Self, ValueError> {
367 match sum(value)? {
368 pb::value::Sum::Int64(n) => Ok(*n),
369 other => Err(mismatch("Int64", other)),
370 }
371 }
372}
373
374impl ToValue for Int64 {
375 fn to_value(&self) -> pb::Value {
376 wrap(pb::value::Sum::Int64(self.0))
377 }
378}
379impl FromValue for Int64 {
380 fn from_value(value: &pb::Value) -> Result<Self, ValueError> {
381 match sum(value)? {
382 pb::value::Sum::Int64(n) => Ok(Int64(*n)),
383 other => Err(mismatch("Int64", other)),
384 }
385 }
386}
387
388impl ToValue for String {
389 fn to_value(&self) -> pb::Value {
390 wrap(pb::value::Sum::Text(self.clone()))
391 }
392}
393impl FromValue for String {
394 fn from_value(value: &pb::Value) -> Result<Self, ValueError> {
395 match sum(value)? {
396 pb::value::Sum::Text(s) => Ok(s.clone()),
397 other => Err(mismatch("Text", other)),
398 }
399 }
400}
401
402impl ToValue for () {
406 fn to_value(&self) -> pb::Value {
407 wrap(pb::value::Sum::Unit(()))
408 }
409}
410impl FromValue for () {
411 fn from_value(value: &pb::Value) -> Result<Self, ValueError> {
412 match sum(value)? {
413 pb::value::Sum::Unit(()) => Ok(()),
414 other => Err(mismatch("Unit", other)),
415 }
416 }
417}
418
419impl ToValue for Unit {
420 fn to_value(&self) -> pb::Value {
421 unit_value()
422 }
423}
424impl FromValue for Unit {
425 fn from_value(value: &pb::Value) -> Result<Self, ValueError> {
426 match sum(value)? {
427 pb::value::Sum::Unit(()) => Ok(Unit),
428 other => Err(mismatch("Unit", other)),
429 }
430 }
431}
432
433impl ToValue for Party {
434 fn to_value(&self) -> pb::Value {
435 wrap(pb::value::Sum::Party(self.0.clone()))
436 }
437}
438impl FromValue for Party {
439 fn from_value(value: &pb::Value) -> Result<Self, ValueError> {
440 match sum(value)? {
441 pb::value::Sum::Party(p) => Ok(Party(p.clone())),
442 other => Err(mismatch("Party", other)),
443 }
444 }
445}
446
447impl ToValue for Numeric {
448 fn to_value(&self) -> pb::Value {
449 wrap(pb::value::Sum::Numeric(self.0.clone()))
450 }
451}
452impl FromValue for Numeric {
453 fn from_value(value: &pb::Value) -> Result<Self, ValueError> {
454 match sum(value)? {
455 pb::value::Sum::Numeric(n) => Ok(Numeric::from_wire(n.clone())),
456 other => Err(mismatch("Numeric", other)),
457 }
458 }
459}
460
461impl ToValue for Timestamp {
462 fn to_value(&self) -> pb::Value {
463 wrap(pb::value::Sum::Timestamp(self.0))
464 }
465}
466impl FromValue for Timestamp {
467 fn from_value(value: &pb::Value) -> Result<Self, ValueError> {
468 match sum(value)? {
469 pb::value::Sum::Timestamp(t) => Ok(Timestamp(*t)),
470 other => Err(mismatch("Timestamp", other)),
471 }
472 }
473}
474
475impl ToValue for Date {
476 fn to_value(&self) -> pb::Value {
477 wrap(pb::value::Sum::Date(self.0))
478 }
479}
480impl FromValue for Date {
481 fn from_value(value: &pb::Value) -> Result<Self, ValueError> {
482 match sum(value)? {
483 pb::value::Sum::Date(d) => Ok(Date(*d)),
484 other => Err(mismatch("Date", other)),
485 }
486 }
487}
488
489impl<T> ToValue for ContractId<T> {
490 fn to_value(&self) -> pb::Value {
491 wrap(pb::value::Sum::ContractId(self.as_str().to_string()))
492 }
493}
494impl<T> FromValue for ContractId<T> {
495 fn from_value(value: &pb::Value) -> Result<Self, ValueError> {
496 match sum(value)? {
497 pb::value::Sum::ContractId(id) => Ok(ContractId::new(id.clone())),
498 other => Err(mismatch("ContractId", other)),
499 }
500 }
501}
502
503impl<T: ToValue> ToValue for Box<T> {
508 fn to_value(&self) -> pb::Value {
509 T::to_value(self)
510 }
511}
512impl<T: FromValue> FromValue for Box<T> {
513 fn from_value(value: &pb::Value) -> Result<Self, ValueError> {
514 T::from_value(value).map(Box::new)
515 }
516}
517
518impl<T: ToValue> ToValue for Option<T> {
519 fn to_value(&self) -> pb::Value {
520 wrap(pb::value::Sum::Optional(Box::new(pb::Optional {
521 value: self.as_ref().map(|inner| Box::new(inner.to_value())),
522 })))
523 }
524}
525impl<T: FromValue> FromValue for Option<T> {
526 fn from_value(value: &pb::Value) -> Result<Self, ValueError> {
527 match sum(value)? {
528 pb::value::Sum::Optional(opt) => match &opt.value {
529 Some(inner) => Ok(Some(T::from_value(inner)?)),
530 None => Ok(None),
531 },
532 other => Err(mismatch("Optional", other)),
533 }
534 }
535}
536
537impl<T: ToValue> ToValue for NestedOpt<T> {
540 fn to_value(&self) -> pb::Value {
541 wrap(pb::value::Sum::Optional(Box::new(pb::Optional {
542 value: self.0.as_ref().map(|inner| Box::new(inner.to_value())),
543 })))
544 }
545}
546impl<T: FromValue> FromValue for NestedOpt<T> {
547 fn from_value(value: &pb::Value) -> Result<Self, ValueError> {
548 match sum(value)? {
549 pb::value::Sum::Optional(opt) => match &opt.value {
550 Some(inner) => Ok(NestedOpt(Some(T::from_value(inner)?))),
551 None => Ok(NestedOpt(None)),
552 },
553 other => Err(mismatch("Optional", other)),
554 }
555 }
556}
557
558impl<T: ToValue> ToValue for Vec<T> {
559 fn to_value(&self) -> pb::Value {
560 wrap(pb::value::Sum::List(pb::List {
561 elements: self.iter().map(ToValue::to_value).collect(),
562 }))
563 }
564}
565impl<T: FromValue> FromValue for Vec<T> {
566 fn from_value(value: &pb::Value) -> Result<Self, ValueError> {
567 match sum(value)? {
568 pb::value::Sum::List(list) => list
572 .elements
573 .iter()
574 .enumerate()
575 .map(|(index, element)| {
576 T::from_value(element).map_err(|error| error.at(index.to_string()))
577 })
578 .collect(),
579 other => Err(mismatch("List", other)),
580 }
581 }
582}
583
584impl<V: ToValue> ToValue for BTreeMap<String, V> {
585 fn to_value(&self) -> pb::Value {
586 wrap(pb::value::Sum::TextMap(pb::TextMap {
587 entries: self
588 .iter()
589 .map(|(key, value)| pb::text_map::Entry {
590 key: key.clone(),
591 value: Some(value.to_value()),
592 })
593 .collect(),
594 }))
595 }
596}
597impl<V: FromValue> FromValue for BTreeMap<String, V> {
598 fn from_value(value: &pb::Value) -> Result<Self, ValueError> {
599 match sum(value)? {
600 pb::value::Sum::TextMap(map) => map
601 .entries
602 .iter()
603 .map(|entry| {
604 let inner = entry.value.as_ref().ok_or_else(|| {
605 ValueError::new("TextMap entry has no value").at(entry.key.clone())
606 })?;
607 let value =
609 V::from_value(inner).map_err(|error| error.at(entry.key.clone()))?;
610 Ok((entry.key.clone(), value))
611 })
612 .collect(),
613 other => Err(mismatch("TextMap", other)),
614 }
615 }
616}
617
618impl<K: ToValue, V: ToValue> ToValue for GenMap<K, V> {
619 fn to_value(&self) -> pb::Value {
620 wrap(pb::value::Sum::GenMap(pb::GenMap {
621 entries: self
622 .0
623 .iter()
624 .map(|(key, value)| pb::gen_map::Entry {
625 key: Some(key.to_value()),
626 value: Some(value.to_value()),
627 })
628 .collect(),
629 }))
630 }
631}
632impl<K: FromValue, V: FromValue> FromValue for GenMap<K, V> {
633 fn from_value(value: &pb::Value) -> Result<Self, ValueError> {
634 match sum(value)? {
635 pb::value::Sum::GenMap(map) => {
636 let entries = map
637 .entries
638 .iter()
639 .enumerate()
640 .map(|(index, entry)| {
641 let at = |part: &str| {
646 let part = part.to_string();
647 move |error: ValueError| error.at(part).at(index.to_string())
648 };
649 let key = entry
650 .key
651 .as_ref()
652 .ok_or_else(|| ValueError::new("GenMap entry has no key"))
653 .map_err(at("key"))?;
654 let value = entry
655 .value
656 .as_ref()
657 .ok_or_else(|| ValueError::new("GenMap entry has no value"))
658 .map_err(at("value"))?;
659 Ok((
660 K::from_value(key).map_err(at("key"))?,
661 V::from_value(value).map_err(at("value"))?,
662 ))
663 })
664 .collect::<Result<Vec<_>, ValueError>>()?;
665 Ok(GenMap(entries))
666 }
667 other => Err(mismatch("GenMap", other)),
668 }
669 }
670}