Skip to main content

zerodds_wire_codec/
source.rs

1// SPDX-License-Identifier: Apache-2.0
2// Copyright 2026 ZeroDDS Contributors
3//! What the write direction reads a value out of.
4//!
5//! [`Source`] is the accessors a representation has to offer and
6//! nothing more. Every rule *about* a value — that a key has to name a
7//! member, that `null` is neither a value nor an absence, that a bound
8//! is enforced, that an `octet` of 300 is refused — lives in
9//! [`crate::contract`] and is asked once for every representation.
10//!
11//! ## Where the line is
12//!
13//! Two questions are the representation's and not the contract's, and
14//! both are on this trait rather than in `contract`:
15//!
16//! - **How an integer is spelled.** A JSON number is a double, so a
17//!   64-bit integer written as one says a different number than its
18//!   author typed and every reader of the file agrees on the wrong one.
19//!   That is a fact about JSON, not about IDL, so [`Source::as_integer`]
20//!   is where a representation states its own rule.
21//! - **How a value renders.** [`Source::render`] is the one form two
22//!   implementations' values can be compared in, and what "one form"
23//!   means depends on what the values are written in.
24//!
25//! Everything else — the range of an `octet`, the width of a `char`,
26//! the bound of a `sequence` — is the type's and is asked in one place.
27
28use crate::encode::ProbeError;
29use std::string::String;
30use std::vec::Vec;
31use zerodds_float128::Float128;
32
33/// A value the write direction can be executed against.
34///
35/// The accessors answer `None` where the value is not of that shape;
36/// the refusal, with its path, is [`crate::contract`]'s to build.
37pub trait Source: Sized {
38    /// A name for what this value is, for a refusal that says what was
39    /// there rather than only what was wanted.
40    fn kind(&self) -> &'static str;
41
42    /// Whether this value is the representation's explicit nothing.
43    ///
44    /// Never a value and never an absence — see
45    /// [`crate::contract::reject_null`]. A representation with no such
46    /// thing answers `false` and the rule costs it nothing.
47    fn is_null(&self) -> bool;
48
49    /// Whether this value is an aggregate with named fields.
50    ///
51    /// Asked before [`Self::entries`] so that a walk over a
52    /// hundred-member sample does not build a list to find out.
53    fn is_object(&self) -> bool;
54
55    /// `true` or `false`.
56    fn as_bool(&self) -> Option<bool>;
57
58    /// The text this value carries.
59    fn as_text(&self) -> Option<&str>;
60
61    /// The floating value this value carries.
62    fn as_float(&self) -> Option<f64>;
63
64    /// The elements of a sequence, an array or a map.
65    fn as_items(&self) -> Option<&[Self]>;
66
67    /// One named field, or `None` where the value has no such field —
68    /// including where it is not an aggregate at all.
69    fn field(&self, name: &str) -> Option<&Self>;
70
71    /// Every named field, in the order the representation holds them.
72    ///
73    /// Empty for a value that is not an aggregate, which
74    /// [`Self::is_object`] is what tells apart.
75    fn entries(&self) -> Vec<(&str, &Self)>;
76
77    /// The integer this value carries, exactly.
78    ///
79    /// A representation that cannot carry every integer exactly refuses
80    /// the spellings it cannot, here, naming what it wants instead. It
81    /// is not [`crate::contract`]'s rule to state: the range an IDL
82    /// primitive admits is the type's business and is checked in
83    /// [`crate::contract::integer_for`], which asks this first.
84    ///
85    /// # Errors
86    /// [`ProbeError::Mismatch`], naming `path`.
87    fn as_integer(&self, path: &str) -> Result<i128, ProbeError>;
88
89    /// The `long double` this value carries, exactly.
90    ///
91    /// Separate from [`Self::as_float`] for the reason
92    /// [`Self::as_integer`] is separate from a number: sixty of a binary128
93    /// significand's 113 bits do not survive a double, so a
94    /// representation whose only number is one has to accept the value
95    /// in some other form and say so here.
96    ///
97    /// # Errors
98    /// [`ProbeError::Mismatch`], naming `path`.
99    fn as_float128(&self, path: &str) -> Result<Float128, ProbeError>;
100
101    /// The one form two implementations' values can be compared in.
102    ///
103    /// A pairwise run compares what came out of B against what went
104    /// into A, and a representation usually has more than one way to
105    /// write the same value. This is the one it writes for comparison —
106    /// keys ordered, no incidental whitespace, numbers in one spelling.
107    /// [`crate::canonical::canonicalise`] is what puts a value into the
108    /// form this then renders; rendering an uncanonicalised value is
109    /// still defined and still comparable, which is what
110    /// [`crate::decode()`] needs to order a map's entries.
111    fn render(&self) -> String;
112}
113
114/// The named fields of a value that has been checked to have them.
115///
116/// A borrow that carries the check with it: [`crate::contract::object`]
117/// is the only thing that builds one, so a walk cannot ask an aggregate
118/// question of a value nobody established was an aggregate. That is what
119/// the concrete `&serde_json::Map<String, Value>` used to do, kept
120/// through the generalisation rather than traded for a bare `&V`.
121pub struct Fields<'a, V>(&'a V);
122
123impl<V> Clone for Fields<'_, V> {
124    fn clone(&self) -> Self {
125        *self
126    }
127}
128
129impl<V> Copy for Fields<'_, V> {}
130
131impl<'a, V: Source> Fields<'a, V> {
132    /// Wrap a value [`crate::contract::object`] has established is one.
133    pub(crate) const fn of(value: &'a V) -> Self {
134        Self(value)
135    }
136
137    /// The value itself, for a walk that needs it back.
138    #[must_use]
139    pub const fn value(self) -> &'a V {
140        self.0
141    }
142
143    /// One field by name.
144    #[must_use]
145    pub fn get(self, name: &str) -> Option<&'a V> {
146        self.0.field(name)
147    }
148
149    /// Every field's name, in the order the representation holds them.
150    #[must_use]
151    pub fn names(self) -> Vec<&'a str> {
152        self.0.entries().into_iter().map(|(name, _)| name).collect()
153    }
154}
155
156impl<'a, V: Source> IntoIterator for Fields<'a, V> {
157    type Item = (&'a str, &'a V);
158    type IntoIter = std::vec::IntoIter<(&'a str, &'a V)>;
159
160    fn into_iter(self) -> Self::IntoIter {
161        self.0.entries().into_iter()
162    }
163}