Skip to main content

kanonak_codec/
numbers.rs

1//! The serde binding of the JSON forms of integer and decimal values
2//! (kanonak-protocol 4.11.0's `numbers-in-json`, runtime#54): the types a
3//! generated struct's fields take, so serde_json reads and writes them as
4//! `serialize` and `deserialize` do. `int` and narrower take `i32`
5//! and the rest, since serde_json already refuses a string, a fraction, or a
6//! value past their width for those.
7
8use std::fmt;
9use std::marker::PhantomData;
10
11use kanonak_canonical::{canonical_decimal, canonical_integer};
12use serde::de::{self, Deserializer, Visitor};
13use serde::{Deserialize, Serialize, Serializer};
14
15use crate::{outside_bounds, within_double_precision, xsd_number_form, CodecError};
16
17/// `integer` or one of its four sign-restricted forms, as a marker an
18/// [`Integer`] is parameterized by, so the field states which datatype it holds
19/// and its range is checked.
20pub trait IntegerDatatype {
21    /// The datatype's local name in core-xsd, by which the codec's one table
22    /// gives its bounds.
23    const LOCAL_NAME: &'static str;
24}
25
26macro_rules! integer_datatype {
27    ($name:ident, $local:literal) => {
28        #[doc = concat!("`xsd:", $local, "`, for [`Integer`].")]
29        #[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
30        pub struct $name;
31
32        impl IntegerDatatype for $name {
33            const LOCAL_NAME: &'static str = $local;
34        }
35    };
36}
37
38integer_datatype!(XsdInteger, "integer");
39integer_datatype!(NonNegativeInteger, "nonNegativeInteger");
40integer_datatype!(PositiveInteger, "positiveInteger");
41integer_datatype!(NonPositiveInteger, "nonPositiveInteger");
42integer_datatype!(NegativeInteger, "negativeInteger");
43
44fn refusal(why: impl fmt::Display) -> CodecError {
45    CodecError::Malformed(format!("{} [json-number-form]", why))
46}
47
48fn integer_of_lexical(datatype: &str, lexical: &str) -> Result<String, CodecError> {
49    canonical_integer(lexical)
50        .map_err(|_| refusal(format!("{:?} is not an xsd:{} lexical form", lexical, datatype)))
51}
52
53/// A whole JSON number within ±(2^53 − 1), as canonical digits.
54fn safe_whole(digits: String) -> Option<String> {
55    Some(digits).filter(|d| within_double_precision(d))
56}
57
58fn whole_f64(v: f64) -> Option<String> {
59    if v.fract() != 0.0 {
60        return None;
61    }
62    canonical_integer(&format!("{:.0}", v)).ok().and_then(safe_whole)
63}
64
65/// An integer of datatype `D`, held as its canonical digits, since no Rust
66/// integer holds every value. JSON carries it as a string of those digits, and
67/// it reads from a string or a whole number within ±(2^53 − 1), inside `D`'s
68/// range.
69#[derive(Clone, PartialEq, Eq, Hash, Debug)]
70pub struct Integer<D: IntegerDatatype> {
71    digits: String,
72    datatype: PhantomData<D>,
73}
74
75impl<D: IntegerDatatype> Integer<D> {
76    /// From any lexical form of the datatype, refused outside its range.
77    pub fn parse(lexical: &str) -> Result<Self, CodecError> {
78        Self::from_digits(integer_of_lexical(D::LOCAL_NAME, lexical)?)
79    }
80
81    fn from_digits(digits: String) -> Result<Self, CodecError> {
82        let (_, min, max) = xsd_number_form(D::LOCAL_NAME)
83            .expect("every IntegerDatatype names a datatype in the codec's table");
84        if outside_bounds(&digits, min, max) {
85            return Err(refusal(format!("{} is outside xsd:{}'s range", digits, D::LOCAL_NAME)));
86        }
87        Ok(Integer {
88            digits,
89            datatype: PhantomData,
90        })
91    }
92
93    /// Its canonical digits.
94    pub fn as_str(&self) -> &str {
95        &self.digits
96    }
97}
98
99impl<D: IntegerDatatype> fmt::Display for Integer<D> {
100    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
101        f.write_str(&self.digits)
102    }
103}
104
105impl<D: IntegerDatatype> Serialize for Integer<D> {
106    fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
107        serializer.serialize_str(&self.digits)
108    }
109}
110
111impl<'de, D: IntegerDatatype> Deserialize<'de> for Integer<D> {
112    fn deserialize<De: Deserializer<'de>>(deserializer: De) -> Result<Self, De::Error> {
113        deserializer.deserialize_any(WholeVisitor(D::LOCAL_NAME, Integer::<D>::from_digits))
114    }
115}
116
117/// A `long`, which JSON carries as a string of its digits. It reads from a
118/// string or a whole number within ±(2^53 − 1).
119#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
120pub struct Long(pub i64);
121
122/// An `unsignedLong`, which JSON carries as a string of its digits. It reads
123/// from a string or a whole number within ±(2^53 − 1).
124#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
125pub struct UnsignedLong(pub u64);
126
127impl Long {
128    /// From any lexical form of `xsd:long`, refused outside its width.
129    pub fn parse(lexical: &str) -> Result<Self, CodecError> {
130        Self::from_digits(integer_of_lexical("long", lexical)?)
131    }
132
133    fn from_digits(digits: String) -> Result<Self, CodecError> {
134        digits
135            .parse::<i64>()
136            .map(Long)
137            .map_err(|_| refusal(format!("{} is outside xsd:long's range", digits)))
138    }
139}
140
141impl UnsignedLong {
142    /// From any lexical form of `xsd:unsignedLong`, refused outside its width.
143    pub fn parse(lexical: &str) -> Result<Self, CodecError> {
144        Self::from_digits(integer_of_lexical("unsignedLong", lexical)?)
145    }
146
147    fn from_digits(digits: String) -> Result<Self, CodecError> {
148        digits
149            .parse::<u64>()
150            .map(UnsignedLong)
151            .map_err(|_| refusal(format!("{} is outside xsd:unsignedLong's range", digits)))
152    }
153}
154
155impl fmt::Display for Long {
156    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
157        write!(f, "{}", self.0)
158    }
159}
160
161impl fmt::Display for UnsignedLong {
162    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
163        write!(f, "{}", self.0)
164    }
165}
166
167impl Serialize for Long {
168    fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
169        serializer.serialize_str(&self.0.to_string())
170    }
171}
172
173impl Serialize for UnsignedLong {
174    fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
175        serializer.serialize_str(&self.0.to_string())
176    }
177}
178
179impl<'de> Deserialize<'de> for Long {
180    fn deserialize<De: Deserializer<'de>>(deserializer: De) -> Result<Self, De::Error> {
181        deserializer.deserialize_any(WholeVisitor("long", Long::from_digits))
182    }
183}
184
185impl<'de> Deserialize<'de> for UnsignedLong {
186    fn deserialize<De: Deserializer<'de>>(deserializer: De) -> Result<Self, De::Error> {
187        deserializer.deserialize_any(WholeVisitor("unsignedLong", UnsignedLong::from_digits))
188    }
189}
190
191/// Reads a wide integer: from a string holding a lexical form of the datatype,
192/// or from a whole JSON number within ±(2^53 − 1), then through `build`, which
193/// checks the datatype's range.
194struct WholeVisitor<T, F: FnOnce(String) -> Result<T, CodecError>>(&'static str, F);
195
196impl<'de, T, F: FnOnce(String) -> Result<T, CodecError>> Visitor<'de> for WholeVisitor<T, F> {
197    type Value = T;
198
199    fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
200        write!(
201            f,
202            "an xsd:{} as a string of its digits, or a whole number within ±(2^53 − 1) [json-number-form]",
203            self.0
204        )
205    }
206
207    fn visit_str<E: de::Error>(self, v: &str) -> Result<T, E> {
208        integer_of_lexical(self.0, v)
209            .and_then(self.1)
210            .map_err(E::custom)
211    }
212
213    fn visit_i64<E: de::Error>(self, v: i64) -> Result<T, E> {
214        let digits = safe_whole(v.to_string()).ok_or_else(|| E::custom(past_double(self.0, v)))?;
215        (self.1)(digits).map_err(E::custom)
216    }
217
218    fn visit_u64<E: de::Error>(self, v: u64) -> Result<T, E> {
219        let digits = safe_whole(v.to_string()).ok_or_else(|| E::custom(past_double(self.0, v)))?;
220        (self.1)(digits).map_err(E::custom)
221    }
222
223    fn visit_f64<E: de::Error>(self, v: f64) -> Result<T, E> {
224        let digits = whole_f64(v).ok_or_else(|| {
225            E::custom(refusal(format!(
226                "an xsd:{} is read from a string, or from a whole JSON number within ±(2^53 − 1); got the number {}",
227                self.0, v
228            )))
229        })?;
230        (self.1)(digits).map_err(E::custom)
231    }
232}
233
234fn past_double(datatype: &str, v: impl fmt::Display) -> CodecError {
235    refusal(format!(
236        "the number {} is past ±(2^53 − 1), where a JSON reader may have rounded it, so an xsd:{} past it is read from a string",
237        v, datatype
238    ))
239}
240
241/// A `decimal`, held as its canonical digits, since no Rust type in the
242/// standard library holds every value. JSON carries it as a string of those
243/// digits, and it reads from a string only, so it can't have been rounded.
244#[derive(Clone, PartialEq, Eq, Hash, Debug)]
245pub struct Decimal(String);
246
247impl Decimal {
248    /// From any lexical form of `xsd:decimal`.
249    pub fn parse(lexical: &str) -> Result<Self, CodecError> {
250        canonical_decimal(lexical)
251            .map(Decimal)
252            .map_err(|_| refusal(format!("{:?} is not an xsd:decimal lexical form", lexical)))
253    }
254
255    /// Its canonical digits.
256    pub fn as_str(&self) -> &str {
257        &self.0
258    }
259}
260
261impl fmt::Display for Decimal {
262    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
263        f.write_str(&self.0)
264    }
265}
266
267impl Serialize for Decimal {
268    fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
269        serializer.serialize_str(&self.0)
270    }
271}
272
273impl<'de> Deserialize<'de> for Decimal {
274    fn deserialize<De: Deserializer<'de>>(deserializer: De) -> Result<Self, De::Error> {
275        struct DecimalVisitor;
276
277        impl<'de> Visitor<'de> for DecimalVisitor {
278            type Value = Decimal;
279
280            fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
281                f.write_str("an xsd:decimal as a string of its digits, never a number, which may have been rounded [json-number-form]")
282            }
283
284            fn visit_str<E: de::Error>(self, v: &str) -> Result<Decimal, E> {
285                Decimal::parse(v).map_err(E::custom)
286            }
287        }
288
289        deserializer.deserialize_any(DecimalVisitor)
290    }
291}