Skip to main content

canton_daml/
primitives.rs

1//! The Daml primitive runtime types that generated bindings use.
2
3use std::collections::BTreeMap;
4use std::fmt;
5use std::marker::PhantomData;
6
7/// A Daml `Party` identifier.
8///
9/// **Validation is asymmetric, the same way [`Numeric`]'s is.** A party id that
10/// came off the wire is taken as-is by [`Party::new`] — the participant is the
11/// authority on what it issued, and rejecting one it vouched for would break
12/// decoding of a payload the ledger considers valid. A party id a *caller*
13/// supplies goes through [`Party::parse`] (or `str::parse`), which refuses the
14/// ones the ledger will refuse anyway.
15///
16/// That matters because the failure is otherwise mute. An empty party id — the
17/// shape `std::env::var` hands back for `PARTY=`, since a set-but-empty
18/// variable reads as a value — reaches the participant, is rejected as
19/// `PermissionDenied`, and the error names nothing at all.
20#[derive(
21    Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, serde::Serialize, serde::Deserialize,
22)]
23#[serde(transparent)]
24pub struct Party(pub(crate) String);
25
26/// The longest party id Canton issues.
27const PARTY_MAX_LEN: usize = 255;
28
29/// The characters a Canton party id is built from: `<hint>::<fingerprint>`,
30/// where the hint is caller-chosen and the fingerprint is hex.
31const fn is_party_char(c: char) -> bool {
32    c.is_ascii_alphanumeric() || matches!(c, ':' | '-' | '_' | ' ')
33}
34
35impl Party {
36    /// Wrap a party-id string **without validating it** — for values that came
37    /// off the wire, where the participant has already vouched for them.
38    /// Prefer [`Party::parse`] for a value a caller supplied.
39    #[must_use]
40    pub fn new(id: impl Into<String>) -> Self {
41        Self(id.into())
42    }
43
44    /// Wrap a party-id string a caller supplied, refusing what the participant
45    /// would refuse.
46    ///
47    /// # Errors
48    /// Returns [`PartyParseError`] when the id is empty, longer than 255
49    /// characters, or contains a character outside `[A-Za-z0-9:_\- ]`.
50    pub fn parse(id: impl Into<String>) -> Result<Self, PartyParseError> {
51        let id = id.into();
52        if id.is_empty() {
53            return Err(PartyParseError::Empty);
54        }
55        if id.chars().count() > PARTY_MAX_LEN {
56            return Err(PartyParseError::TooLong {
57                length: id.chars().count(),
58            });
59        }
60        if let Some(c) = id.chars().find(|c| !is_party_char(*c)) {
61            return Err(PartyParseError::UnexpectedCharacter { character: c });
62        }
63        Ok(Self(id))
64    }
65
66    /// The party id.
67    #[must_use]
68    pub fn as_str(&self) -> &str {
69        &self.0
70    }
71
72    /// Consume the wrapper, yielding the party id.
73    #[must_use]
74    pub fn into_string(self) -> String {
75        self.0
76    }
77}
78
79impl fmt::Display for Party {
80    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
81        f.write_str(&self.0)
82    }
83}
84
85impl AsRef<str> for Party {
86    fn as_ref(&self) -> &str {
87        &self.0
88    }
89}
90
91impl From<String> for Party {
92    fn from(id: String) -> Self {
93        Self(id)
94    }
95}
96
97impl From<&str> for Party {
98    fn from(id: &str) -> Self {
99        Self::new(id)
100    }
101}
102
103impl From<Party> for String {
104    fn from(party: Party) -> Self {
105        party.0
106    }
107}
108
109/// A typed contract id: a handle to a contract of template/type `T`.
110///
111/// `T` is a compile-time tag only — it carries no runtime data (the wire form
112/// is the id string), so `ContractId` is `Clone`/`Eq`/`Hash`/`Ord` regardless
113/// of `T` (all manual impls: a derive would wrongly bound `T`).
114pub struct ContractId<T> {
115    id: String,
116    _marker: PhantomData<fn() -> T>,
117}
118
119impl<T> ContractId<T> {
120    /// Wrap a contract-id string.
121    #[must_use]
122    pub fn new(id: impl Into<String>) -> Self {
123        Self {
124            id: id.into(),
125            _marker: PhantomData,
126        }
127    }
128
129    /// The underlying contract-id string.
130    #[must_use]
131    pub fn as_str(&self) -> &str {
132        &self.id
133    }
134
135    /// Re-tag the id as a handle to a different type `U` — the id string is
136    /// unchanged. The intended use is exercising an **interface** choice on a
137    /// contract read back as its concrete template:
138    /// `cid.retag::<Holding>()` turns a `ContractId<Amulet>` into the
139    /// `ContractId<Holding>` that `exercise_command` needs. The cast is not
140    /// checked against the template's `implements` list, so exercising a
141    /// mis-tagged id fails at the ledger, not at compile time.
142    #[must_use]
143    pub fn retag<U>(self) -> ContractId<U> {
144        ContractId::new(self.id)
145    }
146}
147
148impl<T> Clone for ContractId<T> {
149    fn clone(&self) -> Self {
150        Self::new(self.id.clone())
151    }
152}
153
154impl<T> fmt::Debug for ContractId<T> {
155    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
156        f.debug_tuple("ContractId").field(&self.id).finish()
157    }
158}
159
160impl<T> PartialEq for ContractId<T> {
161    fn eq(&self, other: &Self) -> bool {
162        self.id == other.id
163    }
164}
165
166impl<T> Eq for ContractId<T> {}
167
168impl<T> std::hash::Hash for ContractId<T> {
169    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
170        self.id.hash(state);
171    }
172}
173
174impl<T> PartialOrd for ContractId<T> {
175    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
176        Some(self.cmp(other))
177    }
178}
179
180impl<T> Ord for ContractId<T> {
181    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
182        self.id.cmp(&other.id)
183    }
184}
185
186/// A fixed-scale decimal (`Numeric`), stored as its decimal string — the wire
187/// representation (Daml numerics are transmitted as text to avoid binary-float
188/// rounding).
189///
190/// **Comparison is numeric, not textual**: the ledger echoes values at the
191/// type's full scale (submit `"1.5"` on a `Numeric 10`, read back
192/// `"1.5000000000"`), so `==`/`Ord`/`Hash` compare the canonical decimal value
193/// — `Numeric("1.5") == Numeric("1.5000000000")`. A string that does not parse
194/// as a decimal falls back to plain string comparison (and never equals a
195/// valid decimal).
196///
197/// LF-JSON: **emitted as a string** (what the Ledger API produces); on input
198/// **also accepts a JSON number** (the spec allows it — high-precision values
199/// should still use the string form, since a JSON number literal is already
200/// `f64`-lossy on the wire).
201///
202/// **Validation is asymmetric on purpose.** [`Numeric::parse`] / [`str::parse`]
203/// reject anything that is not a decimal literal, so a caller's typo fails at
204/// construction. Values arriving from the ledger — deserialization and the gRPC
205/// codec — are taken as-is via [`Numeric::from_wire`]: the participant is the
206/// authority on the format, and rejecting an unfamiliar-but-harmless spelling
207/// would break decoding of a payload the ledger considers valid. A value that
208/// is not a decimal literal simply falls out of canonical comparison and never
209/// equals a valid one.
210#[derive(Clone, Debug)]
211pub struct Numeric(pub(crate) String);
212
213impl Numeric {
214    /// Parse a decimal string (`-?digits[.digits]`; a leading `+` is accepted),
215    /// rejecting anything the ledger would reject — use this over raw
216    /// construction so a typo fails here instead of at command submission.
217    ///
218    /// # Errors
219    /// Returns [`NumericParseError`] when the input is not a decimal literal.
220    pub fn parse(text: impl Into<String>) -> Result<Self, NumericParseError> {
221        let text = text.into();
222        if canonical_decimal(&text).is_some() {
223            Ok(Self(text))
224        } else {
225            Err(NumericParseError { input: text })
226        }
227    }
228
229    /// Wrap a decimal string **without validating it** — for values that came
230    /// off the wire, where the ledger has already vouched for the format.
231    /// Prefer [`Numeric::parse`] for values a caller typed.
232    #[must_use]
233    pub fn from_wire(text: impl Into<String>) -> Self {
234        Self(text.into())
235    }
236
237    /// The decimal string, as stored (the wire form, not canonicalised).
238    #[must_use]
239    pub fn as_str(&self) -> &str {
240        &self.0
241    }
242
243    /// The canonical form used for comparison (sign-normalised, no leading or
244    /// trailing zeros), or `None` when the content is not a decimal literal.
245    fn canonical(&self) -> Option<String> {
246        canonical_decimal(&self.0)
247    }
248}
249
250/// A string that is not a Canton party id.
251///
252/// Deliberately does not echo the rejected value: a party id names a real
253/// participant of the ledger, and this error travels wherever the application
254/// logs. The reason is enough to fix the input.
255#[derive(Clone, Debug, PartialEq, Eq)]
256#[non_exhaustive]
257pub enum PartyParseError {
258    /// The id was empty — most often a set-but-empty environment variable,
259    /// which `std::env::var` reports as a value.
260    Empty,
261    /// Longer than Canton issues.
262    TooLong {
263        /// The rejected length.
264        length: usize,
265    },
266    /// A character Canton does not use in a party id.
267    UnexpectedCharacter {
268        /// The first offending character.
269        character: char,
270    },
271}
272
273// Written by hand rather than derived: this crate is the runtime every
274// generated binding depends on, and it does not carry `thiserror`.
275impl fmt::Display for PartyParseError {
276    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
277        match self {
278            Self::Empty => f.write_str("a party id cannot be empty"),
279            Self::TooLong { length } => write!(
280                f,
281                "a party id cannot exceed {PARTY_MAX_LEN} characters (got {length})"
282            ),
283            Self::UnexpectedCharacter { character } => write!(
284                f,
285                "`{character}` is not valid in a party id \
286                 (expected letters, digits, `:`, `-`, `_`, space)"
287            ),
288        }
289    }
290}
291
292impl std::error::Error for PartyParseError {}
293
294impl std::str::FromStr for Party {
295    type Err = PartyParseError;
296    fn from_str(id: &str) -> Result<Self, Self::Err> {
297        Self::parse(id)
298    }
299}
300
301/// A string that is not a Daml `Numeric` (a decimal literal).
302#[derive(Clone, Debug, PartialEq, Eq)]
303pub struct NumericParseError {
304    input: String,
305}
306
307impl NumericParseError {
308    /// The rejected input.
309    #[must_use]
310    pub fn input(&self) -> &str {
311        &self.input
312    }
313}
314
315impl fmt::Display for NumericParseError {
316    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
317        write!(
318            f,
319            "`{}` is not a decimal literal (expected -?digits[.digits])",
320            self.input
321        )
322    }
323}
324
325impl std::error::Error for NumericParseError {}
326
327impl std::str::FromStr for Numeric {
328    type Err = NumericParseError;
329    fn from_str(text: &str) -> Result<Self, Self::Err> {
330        Self::parse(text)
331    }
332}
333
334impl fmt::Display for Numeric {
335    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
336        f.write_str(&self.0)
337    }
338}
339
340impl TryFrom<&str> for Numeric {
341    type Error = NumericParseError;
342    fn try_from(text: &str) -> Result<Self, Self::Error> {
343        Self::parse(text)
344    }
345}
346
347/// Canonicalise a decimal literal: strip an explicit `+`, leading integer
348/// zeros, trailing fraction zeros, and normalise `-0` to `0`. `None` if the
349/// input is not `[+-]?digits[.digits]`.
350fn canonical_decimal(raw: &str) -> Option<String> {
351    let (negative, digits) = match raw.strip_prefix('-') {
352        Some(rest) => (true, rest),
353        None => (false, raw.strip_prefix('+').unwrap_or(raw)),
354    };
355    let (int, frac) = digits.split_once('.').unwrap_or((digits, ""));
356    // A trailing dot with no digits (`42.`) is a legal spelling of `42`; a
357    // second dot lands in `frac` and fails the digit check below.
358    let valid = !int.is_empty()
359        && int.bytes().all(|b| b.is_ascii_digit())
360        && frac.bytes().all(|b| b.is_ascii_digit());
361    if !valid {
362        return None;
363    }
364    let int = int.trim_start_matches('0');
365    let int = if int.is_empty() { "0" } else { int };
366    let frac = frac.trim_end_matches('0');
367    let mut canonical = String::with_capacity(raw.len() + 1);
368    if negative && !(int == "0" && frac.is_empty()) {
369        canonical.push('-');
370    }
371    canonical.push_str(int);
372    if !frac.is_empty() {
373        canonical.push('.');
374        canonical.push_str(frac);
375    }
376    Some(canonical)
377}
378
379impl PartialEq for Numeric {
380    fn eq(&self, other: &Self) -> bool {
381        match (self.canonical(), other.canonical()) {
382            (Some(a), Some(b)) => a == b,
383            (None, None) => self.0 == other.0,
384            _ => false,
385        }
386    }
387}
388
389impl Eq for Numeric {}
390
391impl std::hash::Hash for Numeric {
392    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
393        // Hash the same key equality compares, so Eq and Hash agree.
394        match self.canonical() {
395            Some(canonical) => canonical.hash(state),
396            None => self.0.hash(state),
397        }
398    }
399}
400
401impl PartialOrd for Numeric {
402    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
403        Some(self.cmp(other))
404    }
405}
406
407impl Ord for Numeric {
408    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
409        use std::cmp::Ordering;
410        let (Some(a), Some(b)) = (self.canonical(), other.canonical()) else {
411            // Non-decimal content: fall back to string order (valid decimals
412            // sort before invalid strings, deterministically).
413            return match (self.canonical(), other.canonical()) {
414                (Some(_), None) => Ordering::Less,
415                (None, Some(_)) => Ordering::Greater,
416                _ => self.0.cmp(&other.0),
417            };
418        };
419        let (neg_a, mag_a) = a
420            .strip_prefix('-')
421            .map_or((false, a.as_str()), |m| (true, m));
422        let (neg_b, mag_b) = b
423            .strip_prefix('-')
424            .map_or((false, b.as_str()), |m| (true, m));
425        match (neg_a, neg_b) {
426            (false, true) => Ordering::Greater,
427            (true, false) => Ordering::Less,
428            (negative, _) => {
429                let ordering = magnitude_cmp(mag_a, mag_b);
430                if negative {
431                    ordering.reverse()
432                } else {
433                    ordering
434                }
435            }
436        }
437    }
438}
439
440/// Compare two canonical non-negative decimals numerically: integer parts by
441/// length then lexicographically, then fraction parts lexicographically
442/// (canonical fractions carry no trailing zeros, so shorter-is-prefix means
443/// smaller).
444fn magnitude_cmp(a: &str, b: &str) -> std::cmp::Ordering {
445    let (int_a, frac_a) = a.split_once('.').unwrap_or((a, ""));
446    let (int_b, frac_b) = b.split_once('.').unwrap_or((b, ""));
447    int_a
448        .len()
449        .cmp(&int_b.len())
450        .then_with(|| int_a.cmp(int_b))
451        .then_with(|| frac_a.cmp(frac_b))
452}
453
454impl serde::Serialize for Numeric {
455    fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
456        serializer.serialize_str(&self.0)
457    }
458}
459
460impl<'de> serde::Deserialize<'de> for Numeric {
461    fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
462        struct NumericVisitor;
463        impl serde::de::Visitor<'_> for NumericVisitor {
464            type Value = Numeric;
465            fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
466                f.write_str("a Daml Numeric as a decimal string or a JSON number")
467            }
468            fn visit_str<E: serde::de::Error>(self, value: &str) -> Result<Numeric, E> {
469                Ok(Numeric(value.to_string()))
470            }
471            fn visit_i64<E: serde::de::Error>(self, value: i64) -> Result<Numeric, E> {
472                Ok(Numeric(value.to_string()))
473            }
474            fn visit_u64<E: serde::de::Error>(self, value: u64) -> Result<Numeric, E> {
475                Ok(Numeric(value.to_string()))
476            }
477            fn visit_f64<E: serde::de::Error>(self, value: f64) -> Result<Numeric, E> {
478                Ok(Numeric(value.to_string()))
479            }
480        }
481        deserializer.deserialize_any(NumericVisitor)
482    }
483}
484
485/// A Daml `Int64`.
486///
487/// LF-JSON encodes `Int64` **as a string** (`encodeInt64AsString`, to survive
488/// JavaScript's 53-bit number precision), and accepts a string *or* a number on
489/// input. So this serializes as a string (matching the Ledger API's output) and
490/// deserializes from either form. The gRPC (`Value`) form is a plain `Int64`.
491#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
492pub struct Int64(pub i64);
493
494impl From<i64> for Int64 {
495    fn from(value: i64) -> Self {
496        Self(value)
497    }
498}
499
500impl From<Int64> for i64 {
501    fn from(value: Int64) -> Self {
502        value.0
503    }
504}
505
506impl fmt::Display for Int64 {
507    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
508        write!(f, "{}", self.0)
509    }
510}
511
512impl std::str::FromStr for Int64 {
513    type Err = std::num::ParseIntError;
514    fn from_str(text: &str) -> Result<Self, Self::Err> {
515        text.parse().map(Self)
516    }
517}
518
519impl serde::Serialize for Int64 {
520    fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
521        serializer.serialize_str(&self.0.to_string())
522    }
523}
524
525impl<'de> serde::Deserialize<'de> for Int64 {
526    fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
527        struct Int64Visitor;
528        impl serde::de::Visitor<'_> for Int64Visitor {
529            type Value = Int64;
530            fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
531                f.write_str("an Int64 as a JSON number or a string")
532            }
533            fn visit_i64<E: serde::de::Error>(self, value: i64) -> Result<Int64, E> {
534                Ok(Int64(value))
535            }
536            fn visit_u64<E: serde::de::Error>(self, value: u64) -> Result<Int64, E> {
537                i64::try_from(value)
538                    .map(Int64)
539                    .map_err(serde::de::Error::custom)
540            }
541            fn visit_str<E: serde::de::Error>(self, value: &str) -> Result<Int64, E> {
542                value
543                    .parse::<i64>()
544                    .map(Int64)
545                    .map_err(serde::de::Error::custom)
546            }
547        }
548        deserializer.deserialize_any(Int64Visitor)
549    }
550}
551
552/// A Daml `Timestamp` — microseconds since the Unix epoch (UTC).
553///
554/// Convert to and from [`time::OffsetDateTime`] rather than doing epoch
555/// arithmetic by hand:
556///
557/// ```
558/// use canton_daml::Timestamp;
559/// use time::macros::datetime;
560///
561/// let at = Timestamp::from_datetime(datetime!(2026-07-31 12:00 UTC)).unwrap();
562/// assert_eq!(at.to_datetime().unwrap().year(), 2026);
563/// ```
564#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
565pub struct Timestamp(pub i64);
566
567impl Timestamp {
568    /// Microseconds since the Unix epoch.
569    #[must_use]
570    pub fn micros(self) -> i64 {
571        self.0
572    }
573
574    /// The instant as a UTC date-time, or `None` when the microsecond count is
575    /// outside the range `time` can represent.
576    #[must_use]
577    pub fn to_datetime(self) -> Option<time::OffsetDateTime> {
578        time::OffsetDateTime::from_unix_timestamp_nanos(i128::from(self.0) * 1_000).ok()
579    }
580
581    /// The timestamp for a date-time, truncated toward the past (so
582    /// sub-microsecond digits never move an instant forward), or `None` when
583    /// the instant is outside the microsecond range a Daml `Timestamp` can
584    /// hold. Never saturates: a silent clamp would turn an out-of-range instant
585    /// into a plausible-looking wrong one.
586    #[must_use]
587    pub fn from_datetime(at: time::OffsetDateTime) -> Option<Self> {
588        let micros = at.unix_timestamp_nanos().div_euclid(1_000);
589        i64::try_from(micros).ok().map(Self)
590    }
591}
592
593impl From<Timestamp> for i64 {
594    fn from(at: Timestamp) -> Self {
595        at.0
596    }
597}
598
599/// A Daml `Date` — days since the Unix epoch.
600///
601/// Convert to and from [`time::Date`] rather than counting days by hand.
602#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
603pub struct Date(pub i32);
604
605impl Date {
606    /// Days since the Unix epoch.
607    #[must_use]
608    pub fn days(self) -> i32 {
609        self.0
610    }
611
612    /// The calendar date, or `None` when the day count is outside the range
613    /// `time` can represent.
614    #[must_use]
615    pub fn to_date(self) -> Option<time::Date> {
616        UNIX_EPOCH_DATE.checked_add(time::Duration::days(i64::from(self.0)))
617    }
618
619    /// The Daml date for a calendar date, or `None` when the day count is
620    /// outside the range a Daml `Date` can hold.
621    ///
622    /// No `time::Date` reaches that bound: the year range is ±9999 here (and
623    /// ±999999 under `time`'s `large-dates`), against the ~5.9 million years an
624    /// `i32` day count spans. The `Option` is kept because [`Date::to_date`]
625    /// genuinely needs one — a `Date` decoded from the wire can name a day
626    /// `time` cannot represent — and a pair of conversions that disagree on
627    /// fallibility invites the caller to assume the wrong one is total.
628    #[must_use]
629    pub fn from_date(date: time::Date) -> Option<Self> {
630        i32::try_from((date - UNIX_EPOCH_DATE).whole_days())
631            .ok()
632            .map(Self)
633    }
634}
635
636impl From<Date> for i32 {
637    fn from(date: Date) -> Self {
638        date.0
639    }
640}
641
642/// A Daml `TextMap` — a map keyed by `Text`.
643pub type TextMap<V> = BTreeMap<String, V>;
644
645/// A Daml `GenMap` — a map with an arbitrary key type (key/value pairs on the
646/// wire; the LF-JSON form is an array of `[key, value]` pairs).
647///
648/// Entry order is a wire detail, not part of the value: two maps with the same
649/// entries compare equal regardless of the order the ledger returned them in.
650#[derive(Clone, Debug, Eq, serde::Serialize, serde::Deserialize)]
651#[serde(transparent)]
652pub struct GenMap<K, V>(pub(crate) Vec<(K, V)>);
653
654// Hand-written so the bound is on nothing: an empty map exists for every key
655// and value type. `#[derive(Default)]` would require `K: Default, V: Default`,
656// which no Daml primitive satisfies — `GenMap::<Party, Numeric>::new()` would
657// not compile.
658impl<K, V> Default for GenMap<K, V> {
659    fn default() -> Self {
660        Self(Vec::new())
661    }
662}
663
664impl<K, V> GenMap<K, V> {
665    /// An empty map.
666    #[must_use]
667    pub fn new() -> Self {
668        Self(Vec::new())
669    }
670
671    /// The entries, in wire order.
672    pub fn iter(&self) -> std::slice::Iter<'_, (K, V)> {
673        self.0.iter()
674    }
675
676    /// The number of entries.
677    #[must_use]
678    pub fn len(&self) -> usize {
679        self.0.len()
680    }
681
682    /// Whether the map has no entries.
683    #[must_use]
684    pub fn is_empty(&self) -> bool {
685        self.0.is_empty()
686    }
687
688    /// Append an entry. Daml forbids duplicate keys; this does not check for
689    /// them, so build maps from distinct keys.
690    pub fn insert(&mut self, key: K, value: V) {
691        self.0.push((key, value));
692    }
693}
694
695impl<K, V> From<Vec<(K, V)>> for GenMap<K, V> {
696    fn from(entries: Vec<(K, V)>) -> Self {
697        Self(entries)
698    }
699}
700
701impl<K, V> FromIterator<(K, V)> for GenMap<K, V> {
702    fn from_iter<I: IntoIterator<Item = (K, V)>>(entries: I) -> Self {
703        Self(entries.into_iter().collect())
704    }
705}
706
707impl<K, V> IntoIterator for GenMap<K, V> {
708    type Item = (K, V);
709    type IntoIter = std::vec::IntoIter<(K, V)>;
710    fn into_iter(self) -> Self::IntoIter {
711        self.0.into_iter()
712    }
713}
714
715impl<'a, K, V> IntoIterator for &'a GenMap<K, V> {
716    type Item = &'a (K, V);
717    type IntoIter = std::slice::Iter<'a, (K, V)>;
718    fn into_iter(self) -> Self::IntoIter {
719        self.0.iter()
720    }
721}
722
723/// Entry order is a wire detail: equality compares the entries as a *multiset*,
724/// so a map read back from the ledger in a different order still equals the one
725/// submitted — while a repeated entry still has to appear the same number of
726/// times on both sides (a plain "every entry occurs in the other" test would be
727/// asymmetric: `[a, a]` would equal `[a, b]` but not the reverse).
728impl<K: PartialEq, V: PartialEq> PartialEq for GenMap<K, V> {
729    fn eq(&self, other: &Self) -> bool {
730        if self.0.len() != other.0.len() {
731            return false;
732        }
733        // O(n²), but a GenMap is a handful of entries and `K` is only
734        // `PartialEq` — there is no ordering or hashing to exploit.
735        let mut matched = vec![false; other.0.len()];
736        self.0.iter().all(|entry| {
737            other
738                .0
739                .iter()
740                .enumerate()
741                .position(|(index, candidate)| !matched[index] && candidate == entry)
742                .is_some_and(|index| {
743                    matched[index] = true;
744                    true
745                })
746        })
747    }
748}
749
750/// 1970-01-01, the anchor for [`Date`]'s day count.
751const UNIX_EPOCH_DATE: time::Date = time::macros::date!(1970 - 01 - 01);
752
753/// The LF-JSON `Date` format: `YYYY-MM-DD`.
754const DATE_FORMAT: &[time::format_description::FormatItem<'static>] =
755    time::macros::format_description!("[year]-[month]-[day]");
756
757// `ContractId`, `Timestamp`, and `Date` need manual serde: the first is generic
758// over a phantom tag, and the latter two render as LF-JSON ISO strings, not as
759// their raw integers.
760
761impl<T> serde::Serialize for ContractId<T> {
762    fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
763        serializer.serialize_str(&self.id)
764    }
765}
766
767impl<'de, T> serde::Deserialize<'de> for ContractId<T> {
768    fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
769        Ok(Self::new(String::deserialize(deserializer)?))
770    }
771}
772
773impl serde::Serialize for Timestamp {
774    fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
775        let datetime = time::OffsetDateTime::from_unix_timestamp_nanos(i128::from(self.0) * 1_000)
776            .map_err(serde::ser::Error::custom)?;
777        let text = datetime
778            .format(&time::format_description::well_known::Rfc3339)
779            .map_err(serde::ser::Error::custom)?;
780        serializer.serialize_str(&text)
781    }
782}
783
784impl<'de> serde::Deserialize<'de> for Timestamp {
785    fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
786        let text = String::deserialize(deserializer)?;
787        let datetime =
788            time::OffsetDateTime::parse(&text, &time::format_description::well_known::Rfc3339)
789                .map_err(serde::de::Error::custom)?;
790        // Euclidean division floors toward negative infinity, so sub-microsecond
791        // instants before the epoch truncate consistently (plain `/` would round
792        // them toward zero, i.e. *forward* in time).
793        let micros = datetime.unix_timestamp_nanos().div_euclid(1_000);
794        Ok(Self(
795            i64::try_from(micros).map_err(serde::de::Error::custom)?,
796        ))
797    }
798}
799
800impl serde::Serialize for Date {
801    fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
802        let date = UNIX_EPOCH_DATE
803            .checked_add(time::Duration::days(i64::from(self.0)))
804            .ok_or_else(|| serde::ser::Error::custom("date out of range"))?;
805        let text = date
806            .format(DATE_FORMAT)
807            .map_err(serde::ser::Error::custom)?;
808        serializer.serialize_str(&text)
809    }
810}
811
812impl<'de> serde::Deserialize<'de> for Date {
813    fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
814        let text = String::deserialize(deserializer)?;
815        let date = time::Date::parse(&text, DATE_FORMAT).map_err(serde::de::Error::custom)?;
816        let days = (date - UNIX_EPOCH_DATE).whole_days();
817        Ok(Self(i32::try_from(days).map_err(serde::de::Error::custom)?))
818    }
819}
820
821/// A Daml `Optional` **nested inside another `Optional`**. Same runtime meaning
822/// as [`Option`], but its LF-JSON encoding is the nested-optional *list* form
823/// (`None → []`, `Some(x) → [x]`) instead of `null`/value.
824///
825/// The Daml-LF JSON spec keeps the top-level Optional as `null`/value, and uses
826/// the list form for an Optional **directly** inside another, so `Some None`
827/// (encoded `[]`) is distinguishable from `None` (encoded `null`).
828///
829/// "Directly" is the whole rule: nesting resets as soon as anything else
830/// intervenes. In `Optional (List (Optional Text))` the inner Optional is a
831/// plain `null`/value again, because the `List` broke the chain — the same for
832/// a `TextMap`, a `GenMap`, or a record field. Only `Optional (Optional a)`
833/// puts the inner one in list form. Codegen applies exactly that rule
834/// (`map.rs` recurses through `Optional` and nothing else), so wrap a layer in
835/// `NestedOpt` only where its parent is itself an Optional.
836///
837/// The gRPC (`Value`) encoding is identical to `Option` (a proto `Optional`),
838/// so only the JSON form differs.
839#[derive(Clone, Debug, PartialEq, Eq, Default)]
840pub struct NestedOpt<T>(pub Option<T>);
841
842impl<T> NestedOpt<T> {
843    /// Wrap an [`Option`].
844    #[must_use]
845    pub fn new(value: Option<T>) -> Self {
846        Self(value)
847    }
848
849    /// Unwrap to the [`Option`].
850    #[must_use]
851    pub fn into_inner(self) -> Option<T> {
852        self.0
853    }
854}
855
856impl<T> From<Option<T>> for NestedOpt<T> {
857    fn from(value: Option<T>) -> Self {
858        Self(value)
859    }
860}
861
862impl<T> From<NestedOpt<T>> for Option<T> {
863    fn from(value: NestedOpt<T>) -> Self {
864        value.0
865    }
866}
867
868impl<T: serde::Serialize> serde::Serialize for NestedOpt<T> {
869    fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
870        // Nested-optional rule: None → [], Some(x) → [x]. If `T` is itself a
871        // `NestedOpt`, its own serde recurses, giving `[[]]` / `[[x]]`, etc.
872        match &self.0 {
873            None => serializer.collect_seq(core::iter::empty::<&T>()),
874            Some(value) => serializer.collect_seq(core::iter::once(value)),
875        }
876    }
877}
878
879impl<'de, T: serde::Deserialize<'de>> serde::Deserialize<'de> for NestedOpt<T> {
880    fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
881        let mut items = Vec::<T>::deserialize(deserializer)?;
882        match items.len() {
883            0 => Ok(Self(None)),
884            1 => Ok(Self(Some(items.remove(0)))),
885            n => Err(serde::de::Error::custom(format!(
886                "nested Optional expects a 0- or 1-element array, got {n}"
887            ))),
888        }
889    }
890}
891
892/// The Daml `Unit` type. Its LF-JSON form is the **empty object** `{}` — not
893/// JSON `null`, which is what Rust's `()` serializes to — so `Unit` needs its
894/// own type with a manual serde impl. This is what a nullary variant
895/// constructor and a `Unit`-typed field map to, so their JSON matches the
896/// Ledger API's `{"tag": <ctor>, "value": {}}` form. The gRPC (`Value`) form is
897/// the proto `Unit`.
898#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
899pub struct Unit;
900
901impl serde::Serialize for Unit {
902    fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
903        // An empty map serializes as `{}`.
904        use serde::ser::SerializeMap;
905        serializer.serialize_map(Some(0))?.end()
906    }
907}
908
909impl<'de> serde::Deserialize<'de> for Unit {
910    fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
911        /// Accepts the LF-JSON `{}` (and tolerates `null`), ignoring any content.
912        struct UnitVisitor;
913        impl<'de> serde::de::Visitor<'de> for UnitVisitor {
914            type Value = Unit;
915            fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
916                f.write_str("the empty object `{}` (Daml Unit)")
917            }
918            fn visit_map<A: serde::de::MapAccess<'de>>(self, mut map: A) -> Result<Unit, A::Error> {
919                while map
920                    .next_entry::<serde::de::IgnoredAny, serde::de::IgnoredAny>()?
921                    .is_some()
922                {}
923                Ok(Unit)
924            }
925            fn visit_unit<E: serde::de::Error>(self) -> Result<Unit, E> {
926                Ok(Unit)
927            }
928        }
929        deserializer.deserialize_any(UnitVisitor)
930    }
931}