use std::collections::BTreeMap;
use canton_proto::com::daml::ledger::api::v2 as pb;
use crate::primitives::{
ContractId, Date, GenMap, Int64, NestedOpt, Numeric, Party, Timestamp, Unit,
};
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct ValueError {
path: Vec<String>,
message: String,
}
impl ValueError {
#[must_use]
pub fn new(message: impl Into<String>) -> Self {
Self {
path: Vec::new(),
message: message.into(),
}
}
#[must_use]
pub fn at(mut self, field: impl Into<String>) -> Self {
self.path.insert(0, field.into());
self
}
#[must_use]
pub fn message(&self) -> &str {
&self.message
}
#[must_use]
pub fn path(&self) -> &[String] {
&self.path
}
}
impl std::fmt::Display for ValueError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
if self.path.is_empty() {
write!(f, "value conversion error: {}", self.message)
} else {
write!(
f,
"value conversion error at `{}`: {}",
self.path.join("."),
self.message
)
}
}
}
impl std::error::Error for ValueError {}
impl From<ValueError> for canton_core::Error {
fn from(error: ValueError) -> Self {
canton_core::Error::Payload(Box::new(error))
}
}
pub trait ToValue {
fn to_value(&self) -> pb::Value;
}
pub trait FromValue: Sized {
fn from_value(value: &pb::Value) -> Result<Self, ValueError>;
}
fn wrap(sum: pb::value::Sum) -> pb::Value {
pb::Value { sum: Some(sum) }
}
fn sum(value: &pb::Value) -> Result<&pb::value::Sum, ValueError> {
value
.sum
.as_ref()
.ok_or_else(|| ValueError::new("empty Value (no sum set)"))
}
fn kind(sum: &pb::value::Sum) -> &'static str {
use pb::value::Sum;
match sum {
Sum::Unit(()) => "Unit",
Sum::Bool(_) => "Bool",
Sum::Int64(_) => "Int64",
Sum::Date(_) => "Date",
Sum::Timestamp(_) => "Timestamp",
Sum::Numeric(_) => "Numeric",
Sum::Party(_) => "Party",
Sum::Text(_) => "Text",
Sum::ContractId(_) => "ContractId",
Sum::Optional(_) => "Optional",
Sum::List(_) => "List",
Sum::TextMap(_) => "TextMap",
Sum::GenMap(_) => "GenMap",
Sum::Record(_) => "Record",
Sum::Variant(_) => "Variant",
Sum::Enum(_) => "Enum",
}
}
fn mismatch(expected: &str, got: &pb::value::Sum) -> ValueError {
ValueError::new(format!("expected {expected}, got {}", kind(got)))
}
#[must_use]
pub fn record_fields(fields: Vec<(&str, pb::Value)>) -> pb::Record {
pb::Record {
record_id: None,
fields: fields
.into_iter()
.map(|(label, value)| pb::RecordField {
label: label.to_string(),
value: Some(value),
})
.collect(),
}
}
#[must_use]
pub fn record(fields: Vec<(&str, pb::Value)>) -> pb::Value {
wrap(pb::value::Sum::Record(record_fields(fields)))
}
pub fn find_field<'a>(
value: &'a pb::Value,
index: usize,
label: &str,
) -> Result<Option<&'a pb::Value>, ValueError> {
match sum(value)? {
pb::value::Sum::Record(record) => {
if let Some(field) = record
.fields
.iter()
.find(|field| !field.label.is_empty() && field.label == label)
{
return Ok(field.value.as_ref());
}
if let Some(field) = record.fields.get(index)
&& field.label.is_empty()
{
return Ok(field.value.as_ref());
}
Ok(None)
}
other => Err(mismatch("Record", other)),
}
}
pub fn required_field<'a>(
value: &'a pb::Value,
index: usize,
label: &str,
) -> Result<&'a pb::Value, ValueError> {
find_field(value, index, label)?
.ok_or_else(|| ValueError::new(format!("record has no field `{label}` (index {index})")))
}
pub fn optional_field<T: FromValue + AbsentField>(
value: &pb::Value,
index: usize,
label: &str,
) -> Result<T, ValueError> {
match find_field(value, index, label)? {
Some(present) => T::from_value(present),
None => Ok(T::absent()),
}
}
pub trait AbsentField {
fn absent() -> Self;
}
impl<T> AbsentField for Option<T> {
fn absent() -> Self {
None
}
}
impl<T> AbsentField for NestedOpt<T> {
fn absent() -> Self {
NestedOpt(None)
}
}
pub fn from_record<T: FromValue>(record: &pb::Record) -> Result<T, ValueError> {
let value = pb::Value {
sum: Some(pb::value::Sum::Record(record.clone())),
};
T::from_value(&value)
}
#[must_use]
pub fn unit_value() -> pb::Value {
wrap(pb::value::Sum::Unit(()))
}
#[must_use]
pub fn enum_value(constructor: &str) -> pb::Value {
wrap(pb::value::Sum::Enum(pb::Enum {
enum_id: None,
constructor: constructor.to_string(),
}))
}
pub fn enum_constructor(value: &pb::Value) -> Result<&str, ValueError> {
match sum(value)? {
pb::value::Sum::Enum(enumeration) => Ok(enumeration.constructor.as_str()),
other => Err(mismatch("Enum", other)),
}
}
#[must_use]
pub fn variant_value(constructor: &str, value: pb::Value) -> pb::Value {
wrap(pb::value::Sum::Variant(Box::new(pb::Variant {
variant_id: None,
constructor: constructor.to_string(),
value: Some(Box::new(value)),
})))
}
pub fn variant_parts(value: &pb::Value) -> Result<(&str, &pb::Value), ValueError> {
match sum(value)? {
pb::value::Sum::Variant(variant) => {
let payload = variant
.value
.as_deref()
.ok_or_else(|| ValueError::new("variant carries no value"))?;
Ok((variant.constructor.as_str(), payload))
}
other => Err(mismatch("Variant", other)),
}
}
#[must_use]
pub fn unexpected_constructor(type_name: &str, got: &str) -> ValueError {
ValueError::new(format!("`{type_name}` has no constructor `{got}`"))
}
impl ToValue for bool {
fn to_value(&self) -> pb::Value {
wrap(pb::value::Sum::Bool(*self))
}
}
impl FromValue for bool {
fn from_value(value: &pb::Value) -> Result<Self, ValueError> {
match sum(value)? {
pb::value::Sum::Bool(b) => Ok(*b),
other => Err(mismatch("Bool", other)),
}
}
}
impl ToValue for i64 {
fn to_value(&self) -> pb::Value {
wrap(pb::value::Sum::Int64(*self))
}
}
impl FromValue for i64 {
fn from_value(value: &pb::Value) -> Result<Self, ValueError> {
match sum(value)? {
pb::value::Sum::Int64(n) => Ok(*n),
other => Err(mismatch("Int64", other)),
}
}
}
impl ToValue for Int64 {
fn to_value(&self) -> pb::Value {
wrap(pb::value::Sum::Int64(self.0))
}
}
impl FromValue for Int64 {
fn from_value(value: &pb::Value) -> Result<Self, ValueError> {
match sum(value)? {
pb::value::Sum::Int64(n) => Ok(Int64(*n)),
other => Err(mismatch("Int64", other)),
}
}
}
impl ToValue for String {
fn to_value(&self) -> pb::Value {
wrap(pb::value::Sum::Text(self.clone()))
}
}
impl FromValue for String {
fn from_value(value: &pb::Value) -> Result<Self, ValueError> {
match sum(value)? {
pb::value::Sum::Text(s) => Ok(s.clone()),
other => Err(mismatch("Text", other)),
}
}
}
impl ToValue for () {
fn to_value(&self) -> pb::Value {
wrap(pb::value::Sum::Unit(()))
}
}
impl FromValue for () {
fn from_value(value: &pb::Value) -> Result<Self, ValueError> {
match sum(value)? {
pb::value::Sum::Unit(()) => Ok(()),
other => Err(mismatch("Unit", other)),
}
}
}
impl ToValue for Unit {
fn to_value(&self) -> pb::Value {
unit_value()
}
}
impl FromValue for Unit {
fn from_value(value: &pb::Value) -> Result<Self, ValueError> {
match sum(value)? {
pb::value::Sum::Unit(()) => Ok(Unit),
other => Err(mismatch("Unit", other)),
}
}
}
impl ToValue for Party {
fn to_value(&self) -> pb::Value {
wrap(pb::value::Sum::Party(self.0.clone()))
}
}
impl FromValue for Party {
fn from_value(value: &pb::Value) -> Result<Self, ValueError> {
match sum(value)? {
pb::value::Sum::Party(p) => Ok(Party(p.clone())),
other => Err(mismatch("Party", other)),
}
}
}
impl ToValue for Numeric {
fn to_value(&self) -> pb::Value {
wrap(pb::value::Sum::Numeric(self.0.clone()))
}
}
impl FromValue for Numeric {
fn from_value(value: &pb::Value) -> Result<Self, ValueError> {
match sum(value)? {
pb::value::Sum::Numeric(n) => Ok(Numeric::from_wire(n.clone())),
other => Err(mismatch("Numeric", other)),
}
}
}
impl ToValue for Timestamp {
fn to_value(&self) -> pb::Value {
wrap(pb::value::Sum::Timestamp(self.0))
}
}
impl FromValue for Timestamp {
fn from_value(value: &pb::Value) -> Result<Self, ValueError> {
match sum(value)? {
pb::value::Sum::Timestamp(t) => Ok(Timestamp(*t)),
other => Err(mismatch("Timestamp", other)),
}
}
}
impl ToValue for Date {
fn to_value(&self) -> pb::Value {
wrap(pb::value::Sum::Date(self.0))
}
}
impl FromValue for Date {
fn from_value(value: &pb::Value) -> Result<Self, ValueError> {
match sum(value)? {
pb::value::Sum::Date(d) => Ok(Date(*d)),
other => Err(mismatch("Date", other)),
}
}
}
impl<T> ToValue for ContractId<T> {
fn to_value(&self) -> pb::Value {
wrap(pb::value::Sum::ContractId(self.as_str().to_string()))
}
}
impl<T> FromValue for ContractId<T> {
fn from_value(value: &pb::Value) -> Result<Self, ValueError> {
match sum(value)? {
pb::value::Sum::ContractId(id) => Ok(ContractId::new(id.clone())),
other => Err(mismatch("ContractId", other)),
}
}
}
impl<T: ToValue> ToValue for Box<T> {
fn to_value(&self) -> pb::Value {
T::to_value(self)
}
}
impl<T: FromValue> FromValue for Box<T> {
fn from_value(value: &pb::Value) -> Result<Self, ValueError> {
T::from_value(value).map(Box::new)
}
}
impl<T: ToValue> ToValue for Option<T> {
fn to_value(&self) -> pb::Value {
wrap(pb::value::Sum::Optional(Box::new(pb::Optional {
value: self.as_ref().map(|inner| Box::new(inner.to_value())),
})))
}
}
impl<T: FromValue> FromValue for Option<T> {
fn from_value(value: &pb::Value) -> Result<Self, ValueError> {
match sum(value)? {
pb::value::Sum::Optional(opt) => match &opt.value {
Some(inner) => Ok(Some(T::from_value(inner)?)),
None => Ok(None),
},
other => Err(mismatch("Optional", other)),
}
}
}
impl<T: ToValue> ToValue for NestedOpt<T> {
fn to_value(&self) -> pb::Value {
wrap(pb::value::Sum::Optional(Box::new(pb::Optional {
value: self.0.as_ref().map(|inner| Box::new(inner.to_value())),
})))
}
}
impl<T: FromValue> FromValue for NestedOpt<T> {
fn from_value(value: &pb::Value) -> Result<Self, ValueError> {
match sum(value)? {
pb::value::Sum::Optional(opt) => match &opt.value {
Some(inner) => Ok(NestedOpt(Some(T::from_value(inner)?))),
None => Ok(NestedOpt(None)),
},
other => Err(mismatch("Optional", other)),
}
}
}
impl<T: ToValue> ToValue for Vec<T> {
fn to_value(&self) -> pb::Value {
wrap(pb::value::Sum::List(pb::List {
elements: self.iter().map(ToValue::to_value).collect(),
}))
}
}
impl<T: FromValue> FromValue for Vec<T> {
fn from_value(value: &pb::Value) -> Result<Self, ValueError> {
match sum(value)? {
pb::value::Sum::List(list) => list
.elements
.iter()
.enumerate()
.map(|(index, element)| {
T::from_value(element).map_err(|error| error.at(index.to_string()))
})
.collect(),
other => Err(mismatch("List", other)),
}
}
}
impl<V: ToValue> ToValue for BTreeMap<String, V> {
fn to_value(&self) -> pb::Value {
wrap(pb::value::Sum::TextMap(pb::TextMap {
entries: self
.iter()
.map(|(key, value)| pb::text_map::Entry {
key: key.clone(),
value: Some(value.to_value()),
})
.collect(),
}))
}
}
impl<V: FromValue> FromValue for BTreeMap<String, V> {
fn from_value(value: &pb::Value) -> Result<Self, ValueError> {
match sum(value)? {
pb::value::Sum::TextMap(map) => map
.entries
.iter()
.map(|entry| {
let inner = entry.value.as_ref().ok_or_else(|| {
ValueError::new("TextMap entry has no value").at(entry.key.clone())
})?;
let value =
V::from_value(inner).map_err(|error| error.at(entry.key.clone()))?;
Ok((entry.key.clone(), value))
})
.collect(),
other => Err(mismatch("TextMap", other)),
}
}
}
impl<K: ToValue, V: ToValue> ToValue for GenMap<K, V> {
fn to_value(&self) -> pb::Value {
wrap(pb::value::Sum::GenMap(pb::GenMap {
entries: self
.0
.iter()
.map(|(key, value)| pb::gen_map::Entry {
key: Some(key.to_value()),
value: Some(value.to_value()),
})
.collect(),
}))
}
}
impl<K: FromValue, V: FromValue> FromValue for GenMap<K, V> {
fn from_value(value: &pb::Value) -> Result<Self, ValueError> {
match sum(value)? {
pb::value::Sum::GenMap(map) => {
let entries = map
.entries
.iter()
.enumerate()
.map(|(index, entry)| {
let at = |part: &str| {
let part = part.to_string();
move |error: ValueError| error.at(part).at(index.to_string())
};
let key = entry
.key
.as_ref()
.ok_or_else(|| ValueError::new("GenMap entry has no key"))
.map_err(at("key"))?;
let value = entry
.value
.as_ref()
.ok_or_else(|| ValueError::new("GenMap entry has no value"))
.map_err(at("value"))?;
Ok((
K::from_value(key).map_err(at("key"))?,
V::from_value(value).map_err(at("value"))?,
))
})
.collect::<Result<Vec<_>, ValueError>>()?;
Ok(GenMap(entries))
}
other => Err(mismatch("GenMap", other)),
}
}
}