sqlx_cloudflare_core/
value.rs1use std::fmt::{self, Display, Formatter};
2
3use sqlx_core::error::BoxDynError;
4
5#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
15#[non_exhaustive]
16pub enum TypeInfo {
17 Null,
19 Integer,
21 Real,
23 Text,
25 Blob,
27}
28
29impl Display for TypeInfo {
30 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
31 f.pad(sqlx_core::type_info::TypeInfo::name(self))
32 }
33}
34
35impl sqlx_core::type_info::TypeInfo for TypeInfo {
36 fn is_null(&self) -> bool {
37 matches!(self, TypeInfo::Null)
38 }
39
40 fn name(&self) -> &str {
41 match self {
42 TypeInfo::Null => "NULL",
43 TypeInfo::Integer => "INTEGER",
44 TypeInfo::Real => "REAL",
45 TypeInfo::Text => "TEXT",
46 TypeInfo::Blob => "BLOB",
47 }
48 }
49}
50
51#[derive(Debug, Clone, PartialEq)]
66#[non_exhaustive]
67pub enum Value {
68 Null,
70 Integer(i64),
74 Real(f64),
76 Text(String),
78 Blob(Vec<u8>),
80}
81
82pub const MAX_SAFE_INTEGER: i64 = (1 << 53) - 1;
85
86pub fn safe_integer(value: i64) -> Result<i64, BoxDynError> {
96 if (-MAX_SAFE_INTEGER..=MAX_SAFE_INTEGER).contains(&value) {
97 Ok(value)
98 } else {
99 Err(format!(
100 "{value} is outside ±(2^53 − 1), the integers a JavaScript number holds exactly: \
101 it would be stored as a different integer"
102 )
103 .into())
104 }
105}
106
107impl Value {
108 #[must_use]
115 #[allow(
116 clippy::cast_possible_truncation,
117 clippy::cast_precision_loss,
118 reason = "the range check makes both casts exact"
119 )]
120 pub fn from_number(value: f64) -> Self {
121 if value.fract() == 0.0 && value.abs() <= MAX_SAFE_INTEGER as f64 {
122 Value::Integer(value as i64)
123 } else {
124 Value::Real(value)
125 }
126 }
127
128 #[must_use]
130 pub fn type_info(&self) -> TypeInfo {
131 match self {
132 Value::Null => TypeInfo::Null,
133 Value::Integer(_) => TypeInfo::Integer,
134 Value::Real(_) => TypeInfo::Real,
135 Value::Text(_) => TypeInfo::Text,
136 Value::Blob(_) => TypeInfo::Blob,
137 }
138 }
139
140 #[must_use]
142 pub fn is_null(&self) -> bool {
143 matches!(self, Value::Null)
144 }
145
146 pub fn int(&self) -> Result<i64, BoxDynError> {
156 match *self {
157 Value::Integer(value) => Ok(value),
158 ref other => Err(mismatch("an INTEGER", other)),
159 }
160 }
161
162 #[allow(
168 clippy::cast_precision_loss,
169 reason = "an INTEGER here is within ±(2^53 − 1), so it converts exactly"
170 )]
171 pub fn real(&self) -> Result<f64, BoxDynError> {
172 match *self {
173 Value::Real(value) => Ok(value),
174 Value::Integer(value) => Ok(value as f64),
175 ref other => Err(mismatch("a REAL", other)),
176 }
177 }
178
179 pub fn text(&self) -> Result<&str, BoxDynError> {
185 match self {
186 Value::Text(value) => Ok(value),
187 other => Err(mismatch("TEXT", other)),
188 }
189 }
190
191 pub fn blob(&self) -> Result<&[u8], BoxDynError> {
197 match self {
198 Value::Blob(value) => Ok(value),
199 Value::Text(value) => Ok(value.as_bytes()),
202 other => Err(mismatch("a BLOB", other)),
203 }
204 }
205}
206
207fn mismatch(expected: &str, found: &Value) -> BoxDynError {
208 format!("expected {expected}, found {}", found.type_info()).into()
209}
210
211pub trait AsValue<'r> {
214 fn as_value(&self) -> &'r Value;
216}
217
218#[cfg(test)]
219mod tests {
220 use super::{safe_integer, Value, MAX_SAFE_INTEGER};
221
222 #[test]
223 fn whole_numbers_in_the_safe_range_are_integers() {
224 assert_eq!(Value::from_number(3.0), Value::Integer(3));
225 assert_eq!(Value::from_number(-0.0), Value::Integer(0));
226 assert_eq!(
227 Value::from_number(9_007_199_254_740_991.0),
228 Value::Integer(MAX_SAFE_INTEGER)
229 );
230 }
231
232 #[test]
233 fn everything_else_is_real() {
234 assert_eq!(Value::from_number(1.5), Value::Real(1.5));
235 assert_eq!(
237 Value::from_number(9_007_199_254_740_992.0),
238 Value::Real(9_007_199_254_740_992.0)
239 );
240 assert!(matches!(Value::from_number(f64::INFINITY), Value::Real(_)));
241 assert!(matches!(Value::from_number(f64::NAN), Value::Real(_)));
242 }
243
244 #[test]
245 fn safe_integer_accepts_exactly_the_safe_range() {
246 assert!(safe_integer(MAX_SAFE_INTEGER).is_ok());
247 assert!(safe_integer(-MAX_SAFE_INTEGER).is_ok());
248 assert!(safe_integer(MAX_SAFE_INTEGER + 1).is_err());
249 assert!(safe_integer(-MAX_SAFE_INTEGER - 1).is_err());
250 assert!(safe_integer(i64::MIN).is_err());
251 }
252
253 #[test]
254 fn accessors_accept_what_their_types_are_compatible_with() {
255 assert_eq!(Value::Integer(2).int().unwrap(), 2);
256 assert!(Value::Real(2.0).int().is_err());
257 assert!((Value::Integer(2).real().unwrap() - 2.0).abs() < f64::EPSILON);
258 assert_eq!(Value::Text("ab".into()).blob().unwrap(), b"ab");
259 assert!(Value::Blob(b"ab".to_vec()).text().is_err());
260 assert!(Value::Null.int().is_err());
261 }
262}