cml_core/
lib.rs

1// This recently introduced lint does not play well with the derivative crate.
2// We have both Ord and PartialOrd derive automatically by derivative's proc macros
3// but clippy sees these as hand implementations.
4// Putting this allow locally where it's found did not seem to supress it,
5// likely due to the structure of how the proc macro derives the code.
6// Doing what is suggested by this lint would just result in us actually doing
7// hand implementations of the PartialOrd (an maybe PartialEq) when there's no need,
8// possibly impacting PartialOrd performance on top of being unnecessary and occuring in generated code.
9// Possibly the derivative crate could get updated to suppress this lint
10// from within their proc macros itself. Issue: https://github.com/mcarton/rust-derivative/issues/115
11#![allow(clippy::non_canonical_partial_ord_impl)]
12
13pub use error::*;
14
15pub mod error;
16pub mod network;
17pub mod ordered_hash_map;
18pub mod serialization;
19
20use crate::serialization::{fit_sz, Deserialize, Serialize};
21
22extern crate derivative;
23use derivative::Derivative;
24
25use cbor_event::{de::Deserializer, se::Serializer};
26use std::io::{BufRead, Seek, Write};
27
28pub type Epoch = u64;
29
30pub type Slot = u64;
31
32pub type TransactionIndex = u16;
33
34pub type CertificateIndex = u64;
35
36#[derive(Clone, Debug, Derivative)]
37#[derivative(
38    Eq,
39    PartialEq,
40    Ord = "feature_allow_slow_enum",
41    PartialOrd = "feature_allow_slow_enum",
42    Hash
43)]
44pub enum Int {
45    Uint {
46        value: u64,
47        #[derivative(
48            PartialEq = "ignore",
49            Ord = "ignore",
50            PartialOrd = "ignore",
51            Hash = "ignore"
52        )]
53        encoding: Option<cbor_event::Sz>,
54    },
55    Nint {
56        value: u64,
57        #[derivative(
58            PartialEq = "ignore",
59            Ord = "ignore",
60            PartialOrd = "ignore",
61            Hash = "ignore"
62        )]
63        encoding: Option<cbor_event::Sz>,
64    },
65}
66
67#[derive(Clone, Debug)]
68pub enum IntError {
69    Bounds(std::num::TryFromIntError),
70    Parsing(std::num::ParseIntError),
71}
72
73impl Int {
74    pub fn new_uint(value: u64) -> Self {
75        Self::Uint {
76            value,
77            encoding: None,
78        }
79    }
80
81    /// * `value` - Value as encoded in CBOR - note: a negative `x` here would be `|x + 1|` due to CBOR's `nint` encoding e.g. to represent -5, pass in 4.
82    pub fn new_nint(value: u64) -> Self {
83        Self::Nint {
84            value,
85            encoding: None,
86        }
87    }
88
89    pub fn encoding(&self) -> &Option<cbor_event::Sz> {
90        match self {
91            Self::Uint { encoding, .. } => encoding,
92            Self::Nint { encoding, .. } => encoding,
93        }
94    }
95}
96
97impl std::fmt::Display for Int {
98    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
99        match self {
100            Self::Uint { value, .. } => write!(f, "{value}"),
101            // need to cast to avoid potential overflow when value == u64::max
102            Self::Nint { value, .. } => write!(f, "-{}", (*value as i128) + 1),
103        }
104    }
105}
106
107impl std::str::FromStr for Int {
108    type Err = IntError;
109
110    fn from_str(s: &str) -> Result<Self, Self::Err> {
111        use std::convert::TryFrom;
112        let x = i128::from_str(s).map_err(IntError::Parsing)?;
113        Self::try_from(x).map_err(IntError::Bounds)
114    }
115}
116
117// serde has no proper support for the full spectrum
118// of int values - notably i64 doesn't cover the lower half of negatives
119// and i128 support is not fully supported by serde
120impl serde::Serialize for Int {
121    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
122    where
123        S: serde::Serializer,
124    {
125        serializer.serialize_str(&self.to_string())
126    }
127}
128
129impl<'de> serde::de::Deserialize<'de> for Int {
130    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
131    where
132        D: serde::de::Deserializer<'de>,
133    {
134        use std::str::FromStr;
135        let s = <String as serde::de::Deserialize>::deserialize(deserializer)?;
136        Self::from_str(&s).map_err(|_e| {
137            serde::de::Error::invalid_value(
138                serde::de::Unexpected::Str(&s),
139                &"invalid int (as string)",
140            )
141        })
142    }
143}
144
145impl schemars::JsonSchema for Int {
146    fn schema_name() -> String {
147        String::from("Int")
148    }
149
150    fn json_schema(gen: &mut schemars::gen::SchemaGenerator) -> schemars::schema::Schema {
151        String::json_schema(gen)
152    }
153
154    fn is_referenceable() -> bool {
155        String::is_referenceable()
156    }
157}
158
159impl From<u64> for Int {
160    fn from(x: u64) -> Self {
161        Self::Uint {
162            value: x,
163            encoding: None,
164        }
165    }
166}
167
168impl From<i64> for Int {
169    fn from(x: i64) -> Self {
170        if x >= 0 {
171            Self::Uint {
172                value: x as u64,
173                encoding: None,
174            }
175        } else {
176            Self::Nint {
177                value: (x + 1).unsigned_abs(),
178                encoding: None,
179            }
180        }
181    }
182}
183
184impl std::convert::TryFrom<i128> for Int {
185    type Error = std::num::TryFromIntError;
186
187    fn try_from(x: i128) -> Result<Self, Self::Error> {
188        if x >= 0 {
189            u64::try_from(x).map(|x| Self::Uint {
190                value: x,
191                encoding: None,
192            })
193        } else {
194            u64::try_from((x + 1).abs()).map(|x| Self::Nint {
195                value: x,
196                encoding: None,
197            })
198        }
199    }
200}
201
202impl From<&Int> for i128 {
203    fn from(val: &Int) -> Self {
204        match val {
205            Int::Uint { value, .. } => (*value).into(),
206            Int::Nint { value, .. } => -((*value as i128) + 1),
207        }
208    }
209}
210
211impl Serialize for Int {
212    fn serialize<'se, W: Write>(
213        &self,
214        serializer: &'se mut Serializer<W>,
215        force_canonical: bool,
216    ) -> cbor_event::Result<&'se mut Serializer<W>> {
217        match self {
218            Self::Uint { value, encoding } => serializer
219                .write_unsigned_integer_sz(*value, fit_sz(*value, *encoding, force_canonical)),
220            Self::Nint { value, encoding } => serializer.write_negative_integer_sz(
221                -((*value as i128) + 1),
222                fit_sz(*value, *encoding, force_canonical),
223            ),
224        }
225    }
226}
227
228impl Deserialize for Int {
229    fn deserialize<R: BufRead + Seek>(raw: &mut Deserializer<R>) -> Result<Self, DeserializeError> {
230        (|| -> Result<_, DeserializeError> {
231            match raw.cbor_type()? {
232                cbor_event::Type::UnsignedInteger => raw
233                    .unsigned_integer_sz()
234                    .map(|(x, enc)| Self::Uint {
235                        value: x,
236                        encoding: Some(enc),
237                    })
238                    .map_err(std::convert::Into::into),
239                cbor_event::Type::NegativeInteger => raw
240                    .negative_integer_sz()
241                    .map(|(x, enc)| Self::Nint {
242                        value: (-1 - x) as u64,
243                        encoding: Some(enc),
244                    })
245                    .map_err(std::convert::Into::into),
246                _ => Err(DeserializeFailure::NoVariantMatched.into()),
247            }
248        })()
249        .map_err(|e| e.annotate("Int"))
250    }
251}
252
253#[cfg(test)]
254mod tests {
255    use super::*;
256
257    #[test]
258    fn int_uint_min() {
259        let bytes = [0x00];
260        let x = Int::from_cbor_bytes(&bytes).unwrap();
261        assert_eq!(bytes, x.to_cbor_bytes().as_slice());
262        assert_eq!(Into::<i128>::into(&x), u64::MIN as i128);
263        assert_eq!(x.to_string(), "0");
264    }
265
266    #[test]
267    fn int_uint_max() {
268        let bytes = [0x1B, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF];
269        let x = Int::from_cbor_bytes(&bytes).unwrap();
270        assert_eq!(bytes, x.to_cbor_bytes().as_slice());
271        assert_eq!(Into::<i128>::into(&x), u64::MAX as i128);
272        assert_eq!(x.to_string(), "18446744073709551615");
273    }
274
275    #[test]
276    fn int_nint_min() {
277        let bytes = [0x3B, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF];
278        let x = Int::from_cbor_bytes(&bytes).unwrap();
279        assert_eq!(bytes, x.to_cbor_bytes().as_slice());
280        assert_eq!(Into::<i128>::into(&x), -((u64::MAX as i128) + 1));
281        assert_eq!(x.to_string(), "-18446744073709551616");
282    }
283
284    #[test]
285    fn int_nint_max() {
286        let bytes = [0x20];
287        let x = Int::from_cbor_bytes(&bytes).unwrap();
288        assert_eq!(bytes, x.to_cbor_bytes().as_slice());
289        assert_eq!(Into::<i128>::into(&x), -1i128);
290        assert_eq!(x.to_string(), "-1");
291        let y = Int::from(-1i64);
292        assert_eq!(x.to_canonical_cbor_bytes(), y.to_canonical_cbor_bytes());
293    }
294}