aws_smithy_types/
primitive.rs

1/*
2 * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3 * SPDX-License-Identifier: Apache-2.0
4 */
5
6//! Utilities for formatting and parsing primitives
7//!
8//! Smithy protocols have specific behavior for serializing
9//! & deserializing floats, specifically:
10//! - NaN should be serialized as `NaN`
11//! - Positive infinity should be serialized as `Infinity`
12//! - Negative infinity should be serialized as `-Infinity`
13//!
14//! This module defines the [`Parse`] trait which
15//! enables parsing primitive values (numbers & booleans) that follow
16//! these rules and [`Encoder`], a struct that enables
17//! allocation-free serialization.
18//!
19//! # Examples
20//! ## Parsing
21//! ```rust
22//! use aws_smithy_types::primitive::Parse;
23//! let parsed = f64::parse_smithy_primitive("123.4").expect("valid float");
24//! ```
25//!
26//! ## Encoding
27//! ```
28//! use aws_smithy_types::primitive::Encoder;
29//! assert_eq!("123.4", Encoder::from(123.4).encode());
30//! assert_eq!("Infinity", Encoder::from(f64::INFINITY).encode());
31//! assert_eq!("true", Encoder::from(true).encode());
32//! ```
33use crate::primitive::private::Sealed;
34use std::error::Error;
35use std::fmt;
36use std::str::FromStr;
37
38/// An error during primitive parsing
39#[non_exhaustive]
40#[derive(Debug)]
41pub struct PrimitiveParseError(&'static str);
42
43impl fmt::Display for PrimitiveParseError {
44    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
45        write!(f, "failed to parse input as {}", self.0)
46    }
47}
48impl Error for PrimitiveParseError {}
49
50/// Sealed trait for custom parsing of primitive types
51pub trait Parse: Sealed {
52    /// Parses a Smithy primitive from a string.
53    fn parse_smithy_primitive(input: &str) -> Result<Self, PrimitiveParseError>
54    where
55        Self: Sized;
56}
57
58mod private {
59    pub trait Sealed {}
60    impl Sealed for i8 {}
61    impl Sealed for i16 {}
62    impl Sealed for i32 {}
63    impl Sealed for i64 {}
64    impl Sealed for f32 {}
65    impl Sealed for f64 {}
66    impl Sealed for u64 {}
67    impl Sealed for bool {}
68}
69
70macro_rules! parse_from_str {
71    ($t: ty) => {
72        impl Parse for $t {
73            fn parse_smithy_primitive(input: &str) -> Result<Self, PrimitiveParseError> {
74                FromStr::from_str(input).map_err(|_| PrimitiveParseError(stringify!($t)))
75            }
76        }
77    };
78}
79
80parse_from_str!(bool);
81parse_from_str!(i8);
82parse_from_str!(i16);
83parse_from_str!(i32);
84parse_from_str!(i64);
85
86impl Parse for f32 {
87    fn parse_smithy_primitive(input: &str) -> Result<Self, PrimitiveParseError> {
88        float::parse_f32(input).map_err(|_| PrimitiveParseError("f32"))
89    }
90}
91
92impl Parse for f64 {
93    fn parse_smithy_primitive(input: &str) -> Result<Self, PrimitiveParseError> {
94        float::parse_f64(input).map_err(|_| PrimitiveParseError("f64"))
95    }
96}
97
98enum Inner {
99    /// Boolean
100    Bool(bool),
101    /// 8-bit signed integer
102    I8(i8, itoa::Buffer),
103    /// 16-bit signed integer
104    I16(i16, itoa::Buffer),
105    /// 32-bit signed integer
106    I32(i32, itoa::Buffer),
107    /// 64-bit signed integer
108    I64(i64, itoa::Buffer),
109    /// 64-bit unsigned integer
110    U64(u64, itoa::Buffer),
111    /// 32-bit IEEE 754 single-precision floating-point number
112    F32(f32, ryu::Buffer),
113    /// 64-bit IEEE 754 double-precision floating-point number
114    F64(f64, ryu::Buffer),
115}
116
117impl fmt::Debug for Inner {
118    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
119        match self {
120            Self::Bool(v) => write!(f, "Bool({})", v),
121            Self::I8(v, _) => write!(f, "I8({})", v),
122            Self::I16(v, _) => write!(f, "I16({})", v),
123            Self::I32(v, _) => write!(f, "I32({})", v),
124            Self::I64(v, _) => write!(f, "I64({})", v),
125            Self::U64(v, _) => write!(f, "U64({})", v),
126            Self::F32(v, _) => write!(f, "F32({})", v),
127            Self::F64(v, _) => write!(f, "F64({})", v),
128        }
129    }
130}
131
132/// Primitive Type Encoder
133///
134/// Encodes primitive types in Smithy's specified format. For floating-point numbers,
135/// Smithy requires that NaN and Infinity values be specially encoded.
136///
137/// This type implements `From<T>` for all Smithy primitive types.
138#[non_exhaustive]
139#[derive(Debug)]
140pub struct Encoder(Inner);
141
142impl Encoder {
143    /// Encodes a Smithy primitive as a string.
144    pub fn encode(&mut self) -> &str {
145        match &mut self.0 {
146            Inner::Bool(true) => "true",
147            Inner::Bool(false) => "false",
148            Inner::I8(v, buf) => buf.format(*v),
149            Inner::I16(v, buf) => buf.format(*v),
150            Inner::I32(v, buf) => buf.format(*v),
151            Inner::I64(v, buf) => buf.format(*v),
152            Inner::U64(v, buf) => buf.format(*v),
153            Inner::F32(v, buf) => {
154                if v.is_nan() {
155                    float::NAN
156                } else if *v == f32::INFINITY {
157                    float::INFINITY
158                } else if *v == f32::NEG_INFINITY {
159                    float::NEG_INFINITY
160                } else {
161                    buf.format_finite(*v)
162                }
163            }
164            Inner::F64(v, buf) => {
165                if v.is_nan() {
166                    float::NAN
167                } else if *v == f64::INFINITY {
168                    float::INFINITY
169                } else if *v == f64::NEG_INFINITY {
170                    float::NEG_INFINITY
171                } else {
172                    buf.format_finite(*v)
173                }
174            }
175        }
176    }
177}
178
179impl From<bool> for Encoder {
180    fn from(input: bool) -> Self {
181        Self(Inner::Bool(input))
182    }
183}
184
185impl From<i8> for Encoder {
186    fn from(input: i8) -> Self {
187        Self(Inner::I8(input, itoa::Buffer::new()))
188    }
189}
190
191impl From<i16> for Encoder {
192    fn from(input: i16) -> Self {
193        Self(Inner::I16(input, itoa::Buffer::new()))
194    }
195}
196
197impl From<i32> for Encoder {
198    fn from(input: i32) -> Self {
199        Self(Inner::I32(input, itoa::Buffer::new()))
200    }
201}
202
203impl From<i64> for Encoder {
204    fn from(input: i64) -> Self {
205        Self(Inner::I64(input, itoa::Buffer::new()))
206    }
207}
208
209impl From<u64> for Encoder {
210    fn from(input: u64) -> Self {
211        Self(Inner::U64(input, itoa::Buffer::new()))
212    }
213}
214
215impl From<f32> for Encoder {
216    fn from(input: f32) -> Self {
217        Self(Inner::F32(input, ryu::Buffer::new()))
218    }
219}
220
221impl From<f64> for Encoder {
222    fn from(input: f64) -> Self {
223        Self(Inner::F64(input, ryu::Buffer::new()))
224    }
225}
226
227mod float {
228    use std::num::ParseFloatError;
229
230    /// Smithy encoded value for `f64::INFINITY`
231    pub(crate) const INFINITY: &str = "Infinity";
232
233    /// Smithy encoded value for `f64::NEG_INFINITY`
234    pub(crate) const NEG_INFINITY: &str = "-Infinity";
235
236    /// Smithy encoded value for `f64::NAN`
237    pub(crate) const NAN: &str = "NaN";
238
239    /// Parses a Smithy encoded primitive string into an `f32`.
240    pub(crate) fn parse_f32(data: &str) -> Result<f32, ParseFloatError> {
241        match data {
242            INFINITY => Ok(f32::INFINITY),
243            NEG_INFINITY => Ok(f32::NEG_INFINITY),
244            NAN => Ok(f32::NAN),
245            other => other.parse::<f32>(),
246        }
247    }
248
249    /// Parses a Smithy encoded primitive string into an `f64`.
250    pub(crate) fn parse_f64(data: &str) -> Result<f64, ParseFloatError> {
251        match data {
252            INFINITY => Ok(f64::INFINITY),
253            NEG_INFINITY => Ok(f64::NEG_INFINITY),
254            NAN => Ok(f64::NAN),
255            other => other.parse::<f64>(),
256        }
257    }
258}
259
260#[cfg(test)]
261mod test {
262    use crate::primitive::{Encoder, Parse};
263
264    #[test]
265    fn bool_format() {
266        assert_eq!(Encoder::from(true).encode(), "true");
267        assert_eq!(Encoder::from(false).encode(), "false");
268        let err = bool::parse_smithy_primitive("not a boolean").expect_err("should fail");
269        assert_eq!(err.0, "bool");
270        assert!(bool::parse_smithy_primitive("true").unwrap());
271        assert!(!bool::parse_smithy_primitive("false").unwrap());
272    }
273
274    #[test]
275    fn float_format() {
276        assert_eq!(Encoder::from(55_f64).encode(), "55.0");
277        assert_eq!(Encoder::from(f64::INFINITY).encode(), "Infinity");
278        assert_eq!(Encoder::from(f32::INFINITY).encode(), "Infinity");
279        assert_eq!(Encoder::from(f32::NEG_INFINITY).encode(), "-Infinity");
280        assert_eq!(Encoder::from(f64::NEG_INFINITY).encode(), "-Infinity");
281        assert_eq!(Encoder::from(f32::NAN).encode(), "NaN");
282        assert_eq!(Encoder::from(f64::NAN).encode(), "NaN");
283    }
284
285    #[test]
286    fn float_parse() {
287        assert_eq!(f64::parse_smithy_primitive("1234.5").unwrap(), 1234.5);
288        assert!(f64::parse_smithy_primitive("NaN").unwrap().is_nan());
289        assert_eq!(
290            f64::parse_smithy_primitive("Infinity").unwrap(),
291            f64::INFINITY
292        );
293        assert_eq!(
294            f64::parse_smithy_primitive("-Infinity").unwrap(),
295            f64::NEG_INFINITY
296        );
297        assert_eq!(f32::parse_smithy_primitive("1234.5").unwrap(), 1234.5);
298        assert!(f32::parse_smithy_primitive("NaN").unwrap().is_nan());
299        assert_eq!(
300            f32::parse_smithy_primitive("Infinity").unwrap(),
301            f32::INFINITY
302        );
303        assert_eq!(
304            f32::parse_smithy_primitive("-Infinity").unwrap(),
305            f32::NEG_INFINITY
306        );
307    }
308}