1use arrow::datatypes::ArrowPrimitiveType;
7use std::ops::Bound;
8
9#[derive(Debug, Clone, PartialEq)]
13pub enum Literal {
14 Null,
15 Integer(i128),
16 Float(f64),
17 String(String),
18 Boolean(bool),
19 Struct(Vec<(String, Box<Literal>)>),
21 }
23
24macro_rules! impl_from_for_literal {
25 ($variant:ident, $($t:ty),*) => {
26 $(
27 impl From<$t> for Literal {
28 fn from(v: $t) -> Self {
29 Literal::$variant(v.into())
30 }
31 }
32 )*
33 };
34}
35
36impl_from_for_literal!(Integer, i8, i16, i32, i64, i128, u8, u16, u32, u64);
37impl_from_for_literal!(Float, f32, f64);
38
39impl From<&str> for Literal {
40 fn from(v: &str) -> Self {
41 Literal::String(v.to_string())
42 }
43}
44
45impl From<bool> for Literal {
46 fn from(v: bool) -> Self {
47 Literal::Boolean(v)
48 }
49}
50
51#[derive(Debug, Clone, PartialEq)]
53pub enum LiteralCastError {
54 TypeMismatch {
56 expected: &'static str,
57 got: &'static str,
58 },
59 OutOfRange { target: &'static str, value: i128 },
61 FloatOutOfRange { target: &'static str, value: f64 },
63}
64
65impl std::fmt::Display for LiteralCastError {
66 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
67 match self {
68 LiteralCastError::TypeMismatch { expected, got } => {
69 write!(f, "expected {}, got {}", expected, got)
70 }
71 LiteralCastError::OutOfRange { target, value } => {
72 write!(f, "value {} out of range for {}", value, target)
73 }
74 LiteralCastError::FloatOutOfRange { target, value } => {
75 write!(f, "value {} out of range for {}", value, target)
76 }
77 }
78 }
79}
80
81impl std::error::Error for LiteralCastError {}
82
83pub trait FromLiteral: Sized {
85 fn from_literal(lit: &Literal) -> Result<Self, LiteralCastError>;
86}
87
88macro_rules! impl_from_literal_int {
89 ($($ty:ty),* $(,)?) => {
90 $(
91 impl FromLiteral for $ty {
92 fn from_literal(lit: &Literal) -> Result<Self, LiteralCastError> {
93 match lit {
94 Literal::Integer(i) => <$ty>::try_from(*i).map_err(|_| {
95 LiteralCastError::OutOfRange {
96 target: std::any::type_name::<$ty>(),
97 value: *i,
98 }
99 }),
100 Literal::Float(_) => Err(LiteralCastError::TypeMismatch {
101 expected: "integer",
102 got: "float",
103 }),
104 Literal::Boolean(_) => Err(LiteralCastError::TypeMismatch {
105 expected: "integer",
106 got: "boolean",
107 }),
108 Literal::String(_) => Err(LiteralCastError::TypeMismatch {
109 expected: "integer",
110 got: "string",
111 }),
112 Literal::Struct(_) => Err(LiteralCastError::TypeMismatch {
113 expected: "integer",
114 got: "struct",
115 }),
116 Literal::Null => Err(LiteralCastError::TypeMismatch {
117 expected: "integer",
118 got: "null",
119 }),
120 }
121 }
122 }
123 )*
124 };
125}
126
127impl_from_literal_int!(i8, i16, i32, i64, i128, isize, u8, u16, u32, u64, usize);
128
129impl FromLiteral for f32 {
130 fn from_literal(lit: &Literal) -> Result<Self, LiteralCastError> {
131 let value = match lit {
132 Literal::Float(f) => *f,
133 Literal::Integer(i) => *i as f64,
134 Literal::Boolean(_) => {
135 return Err(LiteralCastError::TypeMismatch {
136 expected: "float",
137 got: "boolean",
138 });
139 }
140 Literal::String(_) => {
141 return Err(LiteralCastError::TypeMismatch {
142 expected: "float",
143 got: "string",
144 });
145 }
146 Literal::Struct(_) => {
147 return Err(LiteralCastError::TypeMismatch {
148 expected: "float",
149 got: "struct",
150 });
151 }
152 Literal::Null => {
153 return Err(LiteralCastError::TypeMismatch {
154 expected: "float",
155 got: "null",
156 });
157 }
158 };
159 let cast = value as f32;
160 if value.is_finite() && !cast.is_finite() {
161 return Err(LiteralCastError::FloatOutOfRange {
162 target: "f32",
163 value,
164 });
165 }
166 Ok(cast)
167 }
168}
169
170impl FromLiteral for bool {
171 fn from_literal(lit: &Literal) -> Result<Self, LiteralCastError> {
172 match lit {
173 Literal::Boolean(b) => Ok(*b),
174 Literal::Integer(i) => match *i {
175 0 => Ok(false),
176 1 => Ok(true),
177 value => Err(LiteralCastError::OutOfRange {
178 target: "bool",
179 value,
180 }),
181 },
182 Literal::Float(_) => Err(LiteralCastError::TypeMismatch {
183 expected: "bool",
184 got: "float",
185 }),
186 Literal::String(s) => {
187 let normalized = s.trim().to_ascii_lowercase();
188 match normalized.as_str() {
189 "true" | "t" | "1" => Ok(true),
190 "false" | "f" | "0" => Ok(false),
191 _ => Err(LiteralCastError::TypeMismatch {
192 expected: "bool",
193 got: "string",
194 }),
195 }
196 }
197 Literal::Struct(_) => Err(LiteralCastError::TypeMismatch {
198 expected: "bool",
199 got: "struct",
200 }),
201 Literal::Null => Err(LiteralCastError::TypeMismatch {
202 expected: "bool",
203 got: "null",
204 }),
205 }
206 }
207}
208
209impl FromLiteral for f64 {
210 fn from_literal(lit: &Literal) -> Result<Self, LiteralCastError> {
211 match lit {
212 Literal::Float(f) => Ok(*f),
213 Literal::Integer(i) => Ok(*i as f64),
214 Literal::Boolean(_) => Err(LiteralCastError::TypeMismatch {
215 expected: "float",
216 got: "boolean",
217 }),
218 Literal::String(_) => Err(LiteralCastError::TypeMismatch {
219 expected: "float",
220 got: "string",
221 }),
222 Literal::Struct(_) => Err(LiteralCastError::TypeMismatch {
223 expected: "float",
224 got: "struct",
225 }),
226 Literal::Null => Err(LiteralCastError::TypeMismatch {
227 expected: "float",
228 got: "null",
229 }),
230 }
231 }
232}
233
234fn literal_type_name(lit: &Literal) -> &'static str {
235 match lit {
236 Literal::Integer(_) => "integer",
237 Literal::Float(_) => "float",
238 Literal::String(_) => "string",
239 Literal::Boolean(_) => "boolean",
240 Literal::Null => "null",
241 Literal::Struct(_) => "struct",
242 }
243}
244
245pub fn literal_to_string(lit: &Literal) -> Result<String, LiteralCastError> {
247 match lit {
248 Literal::String(s) => Ok(s.clone()),
249 Literal::Null => Err(LiteralCastError::TypeMismatch {
250 expected: "string",
251 got: "null",
252 }),
253 _ => Err(LiteralCastError::TypeMismatch {
254 expected: "string",
255 got: literal_type_name(lit),
256 }),
257 }
258}
259
260pub fn literal_to_native<T>(lit: &Literal) -> Result<T, LiteralCastError>
262where
263 T: FromLiteral + Copy + 'static,
264{
265 T::from_literal(lit)
266}
267
268pub fn bound_to_native<T>(bound: &Bound<Literal>) -> Result<Bound<T::Native>, LiteralCastError>
275where
276 T: ArrowPrimitiveType,
277 T::Native: FromLiteral + Copy,
278{
279 Ok(match bound {
280 Bound::Unbounded => Bound::Unbounded,
281 Bound::Included(l) => Bound::Included(literal_to_native::<T::Native>(l)?),
282 Bound::Excluded(l) => Bound::Excluded(literal_to_native::<T::Native>(l)?),
283 })
284}
285
286#[cfg(test)]
287mod tests {
288 use super::*;
289
290 #[test]
291 fn boolean_literal_roundtrip() {
292 let lit = Literal::from(true);
293 assert_eq!(lit, Literal::Boolean(true));
294 assert!(literal_to_native::<bool>(&lit).unwrap());
295 assert!(!literal_to_native::<bool>(&Literal::Boolean(false)).unwrap());
296 }
297
298 #[test]
299 fn boolean_literal_rejects_integer_cast() {
300 let lit = Literal::Boolean(true);
301 let err = literal_to_native::<i32>(&lit).unwrap_err();
302 assert!(matches!(
303 err,
304 LiteralCastError::TypeMismatch {
305 expected: "integer",
306 got: "boolean",
307 }
308 ));
309 }
310}