salvo_oapi/openapi/schema/
number.rs1use serde::de::{self, Visitor};
2use serde::{Deserialize, Deserializer, Serialize, Serializer};
3
4#[derive(Clone, Debug)]
20pub enum Number {
21 Int(isize),
23 UInt(usize),
25 Float(f64),
27}
28
29impl Eq for Number {}
30
31impl PartialEq for Number {
32 fn eq(&self, other: &Self) -> bool {
33 match (self, other) {
34 (Self::Int(left), Self::Int(right)) => left == right,
35 (Self::UInt(left), Self::UInt(right)) => left == right,
36 (Self::Float(left), Self::Float(right)) => left == right,
37 _ => false,
38 }
39 }
40}
41
42impl Serialize for Number {
43 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
44 where
45 S: Serializer,
46 {
47 match self {
48 Self::Int(value) => serializer.serialize_i64(*value as i64),
49 Self::UInt(value) => serializer.serialize_u64(*value as u64),
50 Self::Float(value) => {
51 if value.fract() == 0.0 && value.is_finite() {
53 if *value < 0.0 {
54 serializer.serialize_i64(*value as i64)
55 } else {
56 serializer.serialize_u64(*value as u64)
57 }
58 } else {
59 serializer.serialize_f64(*value)
60 }
61 }
62 }
63 }
64}
65
66struct NumberVisitor;
67
68impl<'de> Visitor<'de> for NumberVisitor {
69 type Value = Number;
70
71 fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
72 formatter.write_str("a number (integer or float)")
73 }
74
75 fn visit_i64<E>(self, v: i64) -> Result<Self::Value, E>
76 where
77 E: de::Error,
78 {
79 Ok(Number::Int(v as isize))
80 }
81
82 fn visit_u64<E>(self, v: u64) -> Result<Self::Value, E>
83 where
84 E: de::Error,
85 {
86 Ok(Number::UInt(v as usize))
87 }
88
89 fn visit_f64<E>(self, v: f64) -> Result<Self::Value, E>
90 where
91 E: de::Error,
92 {
93 Ok(Number::Float(v))
94 }
95}
96
97impl<'de> Deserialize<'de> for Number {
98 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
99 where
100 D: Deserializer<'de>,
101 {
102 deserializer.deserialize_any(NumberVisitor)
103 }
104}
105
106macro_rules! impl_from_for_number {
107 ( $( $ty:ident => $pat:ident $( as $as:ident )? ),* ) => {
108 $(
109 impl From<$ty> for Number {
110 fn from(value: $ty) -> Self {
111 Self::$pat(value $( as $as )?)
112 }
113 }
114 )*
115 };
116}
117
118#[rustfmt::skip]
119impl_from_for_number!(
120 f32 => Float as f64, f64 => Float,
121 i8 => Int as isize, i16 => Int as isize, i32 => Int as isize, i64 => Int as isize,
122 u8 => UInt as usize, u16 => UInt as usize, u32 => UInt as usize, u64 => UInt as usize,
123 isize => Int, usize => UInt
124);
125
126#[cfg(test)]
127mod tests {
128 use super::*;
129
130 #[test]
131 fn test_serialize_int() {
132 let n = Number::Int(42);
133 assert_eq!(serde_json::to_string(&n).unwrap(), "42");
134 }
135
136 #[test]
137 fn test_serialize_negative_int() {
138 let n = Number::Int(-5);
139 assert_eq!(serde_json::to_string(&n).unwrap(), "-5");
140 }
141
142 #[test]
143 fn test_serialize_uint() {
144 let n = Number::UInt(100);
145 assert_eq!(serde_json::to_string(&n).unwrap(), "100");
146 }
147
148 #[test]
149 #[allow(clippy::approx_constant)]
150 fn test_serialize_float() {
151 let n = Number::Float(3.14);
152 assert_eq!(serde_json::to_string(&n).unwrap(), "3.14");
153 }
154
155 #[test]
156 fn test_serialize_whole_float_as_integer() {
157 let n = Number::Float(10.0);
158 assert_eq!(serde_json::to_string(&n).unwrap(), "10");
159 }
160
161 #[test]
162 fn test_serialize_negative_whole_float() {
163 let n = Number::Float(-3.0);
164 assert_eq!(serde_json::to_string(&n).unwrap(), "-3");
165 }
166
167 #[test]
168 fn test_from_i32() {
169 let n: Number = 42i32.into();
170 assert_eq!(n, Number::Int(42));
171 }
172
173 #[test]
174 fn test_from_u64() {
175 let n: Number = 100u64.into();
176 assert_eq!(n, Number::UInt(100));
177 }
178
179 #[test]
180 fn test_from_f64() {
181 let n: Number = 2.5f64.into();
182 assert_eq!(n, Number::Float(2.5));
183 }
184
185 #[test]
186 fn test_deserialize_int() {
187 let n: Number = serde_json::from_str("42").unwrap();
188 assert_eq!(n, Number::UInt(42));
189 }
190
191 #[test]
192 fn test_deserialize_negative_int() {
193 let n: Number = serde_json::from_str("-5").unwrap();
194 assert_eq!(n, Number::Int(-5));
195 }
196
197 #[test]
198 #[allow(clippy::approx_constant)]
199 fn test_deserialize_float() {
200 let n: Number = serde_json::from_str("3.14").unwrap();
201 assert_eq!(n, Number::Float(3.14));
202 }
203
204 #[test]
205 fn test_equality() {
206 assert_eq!(Number::Int(1), Number::Int(1));
207 assert_eq!(Number::UInt(1), Number::UInt(1));
208 assert_eq!(Number::Float(1.5), Number::Float(1.5));
209 assert_ne!(Number::Int(1), Number::UInt(1));
210 }
211}