devicerail_protocol/
wire_integer.rs1use serde::{Deserialize, Deserializer, Serializer};
2use serde_json::Value;
3
4pub const MAX_SAFE_INTEGER: u64 = 9_007_199_254_740_991;
7
8pub fn json_integer_as_u32(value: &Value) -> Option<u32> {
16 if let Some(integer) = value.as_u64() {
17 return u32::try_from(integer).ok();
18 }
19 let float = value.as_f64()?;
20 if float.is_finite() && float >= 0.0 && float <= f64::from(u32::MAX) && float.fract() == 0.0 {
21 let integer = float as u32;
22 (f64::from(integer) == float).then_some(integer)
23 } else {
24 None
25 }
26}
27
28pub fn json_integer_as_i32(value: &Value) -> Option<i32> {
31 if let Some(integer) = value.as_i64() {
32 return i32::try_from(integer).ok();
33 }
34 if let Some(integer) = value.as_u64() {
35 return i32::try_from(integer).ok();
36 }
37 let float = value.as_f64()?;
38 if float.is_finite()
39 && float >= f64::from(i32::MIN)
40 && float <= f64::from(i32::MAX)
41 && float.fract() == 0.0
42 {
43 let integer = float as i32;
44 (f64::from(integer) == float).then_some(integer)
45 } else {
46 None
47 }
48}
49
50pub fn serialize_js_safe_u64<S>(value: &u64, serializer: S) -> Result<S::Ok, S::Error>
51where
52 S: Serializer,
53{
54 if *value <= MAX_SAFE_INTEGER {
55 serializer.serialize_u64(*value)
56 } else {
57 Err(serde::ser::Error::custom(format!(
58 "integer exceeds the cross-language safe limit {MAX_SAFE_INTEGER}"
59 )))
60 }
61}
62
63pub fn deserialize_js_safe_u64<'de, D>(deserializer: D) -> Result<u64, D::Error>
64where
65 D: Deserializer<'de>,
66{
67 let value = u64::deserialize(deserializer)?;
68 if value <= MAX_SAFE_INTEGER {
69 Ok(value)
70 } else {
71 Err(serde::de::Error::custom(format!(
72 "integer exceeds the cross-language safe limit {MAX_SAFE_INTEGER}"
73 )))
74 }
75}
76
77pub fn serialize_optional_js_safe_u64<S>(
78 value: &Option<u64>,
79 serializer: S,
80) -> Result<S::Ok, S::Error>
81where
82 S: Serializer,
83{
84 match value {
85 Some(value) if *value <= MAX_SAFE_INTEGER => serializer.serialize_some(value),
86 Some(_) => Err(serde::ser::Error::custom(format!(
87 "integer exceeds the cross-language safe limit {MAX_SAFE_INTEGER}"
88 ))),
89 None => serializer.serialize_none(),
90 }
91}
92
93pub fn deserialize_optional_js_safe_u64<'de, D>(deserializer: D) -> Result<Option<u64>, D::Error>
94where
95 D: Deserializer<'de>,
96{
97 let value = Option::<u64>::deserialize(deserializer)?;
98 match value {
99 Some(value) if value > MAX_SAFE_INTEGER => Err(serde::de::Error::custom(format!(
100 "integer exceeds the cross-language safe limit {MAX_SAFE_INTEGER}"
101 ))),
102 value => Ok(value),
103 }
104}
105
106#[cfg(test)]
107mod tests {
108 use serde::{Deserialize, Serialize};
109 use serde_json::{Value, json};
110
111 use super::{MAX_SAFE_INTEGER, json_integer_as_i32, json_integer_as_u32};
112
113 #[derive(Debug, Deserialize, Serialize)]
114 struct WireInteger {
115 #[serde(
116 serialize_with = "super::serialize_js_safe_u64",
117 deserialize_with = "super::deserialize_js_safe_u64"
118 )]
119 value: u64,
120 }
121
122 #[test]
123 fn wire_integer_accepts_the_limit_and_rejects_larger_values() {
124 let valid: WireInteger =
125 serde_json::from_value(json!({ "value": MAX_SAFE_INTEGER })).expect("safe integer");
126 assert_eq!(valid.value, MAX_SAFE_INTEGER);
127 assert!(
128 serde_json::from_value::<WireInteger>(json!({ "value": MAX_SAFE_INTEGER + 1 }))
129 .is_err()
130 );
131 assert!(
132 serde_json::to_value(WireInteger {
133 value: MAX_SAFE_INTEGER + 1,
134 })
135 .is_err()
136 );
137 }
138
139 #[test]
140 fn json_schema_integer_forms_convert_without_widening_the_range() {
141 for representation in ["1", "1.0", "1e0", "-0.0"] {
142 let value: Value = serde_json::from_str(representation).expect("JSON number");
143 assert_eq!(
144 json_integer_as_u32(&value),
145 Some(if representation == "-0.0" { 0 } else { 1 })
146 );
147 }
148 for representation in ["-1", "-1.0", "-1e0"] {
149 let value: Value = serde_json::from_str(representation).expect("JSON number");
150 assert_eq!(json_integer_as_i32(&value), Some(-1));
151 }
152
153 for representation in ["1.5", "-1", "4294967296", "4294967296.0"] {
154 let value: Value = serde_json::from_str(representation).expect("JSON number");
155 assert_eq!(json_integer_as_u32(&value), None);
156 }
157 for representation in ["1.5", "2147483648", "-2147483649", "2147483648.0"] {
158 let value: Value = serde_json::from_str(representation).expect("JSON number");
159 assert_eq!(json_integer_as_i32(&value), None);
160 }
161 assert_eq!(json_integer_as_u32(&json!("1")), None);
162 assert_eq!(json_integer_as_i32(&Value::Null), None);
163 }
164}