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}