1use std::collections::BTreeMap;
2use std::str::FromStr;
3
4use chrono::{DateTime, NaiveDate, Utc};
5pub use rust_decimal::Decimal;
6use rust_decimal::prelude::{FromPrimitive, ToPrimitive};
7
8#[derive(Debug, Clone, Copy, PartialEq, Eq)]
9pub enum DataType {
10 Bool,
11 I64,
12 U64,
13 F64,
14 Decimal,
15 Text,
16 LargeText,
17 Json,
18 Date,
19 Timestamp,
20}
21
22#[derive(Debug, Clone, PartialEq)]
23pub enum Value {
24 Null,
25 Bool(bool),
26 I64(i64),
27 U64(u64),
28 F64(f64),
29 Decimal(Decimal),
30 Text(String),
31 Json(serde_json::Value),
32 Date(NaiveDate),
33 Timestamp(crate::time::Timestamp),
34 Object(BTreeMap<String, Value>),
35 List(Vec<Value>),
36 TypedNull(DataType),
37}
38
39impl From<&str> for Value {
40 fn from(value: &str) -> Self {
41 Self::Text(value.to_owned())
42 }
43}
44
45impl From<String> for Value {
46 fn from(value: String) -> Self {
47 Self::Text(value)
48 }
49}
50
51impl From<i64> for Value {
52 fn from(value: i64) -> Self {
53 Self::I64(value)
54 }
55}
56
57impl From<i32> for Value {
58 fn from(value: i32) -> Self {
59 Self::I64(i64::from(value))
60 }
61}
62
63impl From<i16> for Value {
64 fn from(value: i16) -> Self {
65 Self::I64(i64::from(value))
66 }
67}
68
69impl From<u64> for Value {
70 fn from(value: u64) -> Self {
71 Self::U64(value)
72 }
73}
74
75impl From<u32> for Value {
76 fn from(value: u32) -> Self {
77 Self::U64(u64::from(value))
78 }
79}
80
81impl From<u16> for Value {
82 fn from(value: u16) -> Self {
83 Self::U64(u64::from(value))
84 }
85}
86
87impl From<f64> for Value {
88 fn from(value: f64) -> Self {
89 Self::F64(value)
90 }
91}
92
93impl From<f32> for Value {
94 fn from(value: f32) -> Self {
95 Self::F64(f64::from(value))
96 }
97}
98
99impl From<bool> for Value {
100 fn from(value: bool) -> Self {
101 Self::Bool(value)
102 }
103}
104
105impl From<Decimal> for Value {
106 fn from(value: Decimal) -> Self {
107 Self::Decimal(value)
108 }
109}
110
111impl From<serde_json::Value> for Value {
112 fn from(value: serde_json::Value) -> Self {
113 Self::Json(value)
114 }
115}
116
117impl From<NaiveDate> for Value {
118 fn from(value: NaiveDate) -> Self {
119 Self::Date(value)
120 }
121}
122
123impl From<crate::time::Timestamp> for Value {
124 fn from(value: crate::time::Timestamp) -> Self {
125 Self::Timestamp(value)
126 }
127}
128
129impl From<DateTime<Utc>> for Value {
130 fn from(value: DateTime<Utc>) -> Self {
131 Self::Timestamp(crate::time::Timestamp(value.timestamp_millis()))
132 }
133}
134
135impl Value {
136 pub fn object(record: crate::Record) -> Self {
137 Self::Object(record)
138 }
139
140 pub fn try_i64(&self) -> Option<i64> {
141 match self {
142 Self::I64(value) => Some(*value),
143 Self::U64(value) => i64::try_from(*value).ok(),
144 Self::Decimal(value) => value.to_i64(),
145 _ => None,
146 }
147 }
148
149 pub fn try_u64(&self) -> Option<u64> {
150 match self {
151 Self::U64(value) => Some(*value),
152 Self::I64(value) => u64::try_from(*value).ok(),
153 Self::Decimal(value) => value.to_u64(),
154 _ => None,
155 }
156 }
157
158 pub fn try_decimal(&self) -> Option<Decimal> {
159 match self {
160 Self::Decimal(value) => Some(*value),
161 Self::I64(value) => Some(Decimal::from(*value)),
162 Self::U64(value) => Some(Decimal::from(*value)),
163 Self::F64(value) if value.is_finite() => Decimal::from_f64(*value),
168 Self::Text(value) => Decimal::from_str(value).ok(),
169 _ => None,
170 }
171 }
172
173 pub fn try_f64(&self) -> Option<f64> {
174 match self {
175 Self::F64(value) => Some(*value),
176 Self::I64(value) => Some(*value as f64),
177 Self::U64(value) => Some(*value as f64),
178 Self::Decimal(value) => value.to_f64(),
179 _ => None,
180 }
181 }
182
183 pub fn try_text(&self) -> Option<&str> {
184 match self {
185 Self::Text(value) => Some(value),
186 _ => None,
187 }
188 }
189
190 pub fn try_bool(&self) -> Option<bool> {
191 match self {
192 Self::Bool(value) => Some(*value),
193 _ => None,
194 }
195 }
196
197 pub fn try_date(&self) -> Option<NaiveDate> {
198 match self {
199 Self::Date(value) => Some(*value),
200 Self::Text(value) => {
201 if let Ok(nd) = chrono::NaiveDate::parse_from_str(value, "%Y-%m-%d") {
202 return Some(nd);
203 }
204 None
205 }
206 Self::I64(value) => {
207 chrono::DateTime::from_timestamp_millis(*value).map(|dt| dt.naive_utc().date())
208 }
209 Self::U64(value) => i64::try_from(*value)
210 .ok()
211 .and_then(chrono::DateTime::from_timestamp_millis)
212 .map(|dt| dt.naive_utc().date()),
213 _ => None,
214 }
215 }
216
217 pub fn try_timestamp(&self) -> Option<crate::time::Timestamp> {
218 match self {
219 Self::Timestamp(value) => Some(*value),
220 Self::Text(value) => {
221 if let Ok(dt) = chrono::DateTime::parse_from_rfc3339(value) {
222 return Some(crate::time::Timestamp(dt.timestamp_millis()));
223 }
224 if let Ok(ndt) = chrono::NaiveDateTime::parse_from_str(value, "%Y-%m-%d %H:%M:%S") {
225 return Some(crate::time::Timestamp(
226 chrono::DateTime::<Utc>::from_naive_utc_and_offset(ndt, chrono::Utc)
227 .timestamp_millis(),
228 ));
229 }
230 if let Ok(nd) = chrono::NaiveDate::parse_from_str(value, "%Y-%m-%d") {
231 let ndt = nd.and_hms_opt(0, 0, 0)?;
232 return Some(crate::time::Timestamp(
233 chrono::DateTime::<Utc>::from_naive_utc_and_offset(ndt, chrono::Utc)
234 .timestamp_millis(),
235 ));
236 }
237 None
238 }
239 Self::I64(value) => Some(crate::time::Timestamp(*value)),
240 Self::U64(value) => i64::try_from(*value).ok().map(crate::time::Timestamp),
241 _ => None,
242 }
243 }
244
245 pub fn to_json_value(&self) -> serde_json::Value {
246 match self {
247 Self::Null => serde_json::Value::Null,
248 Self::Bool(value) => serde_json::Value::Bool(*value),
249 Self::I64(value) => serde_json::Value::from(*value),
250 Self::U64(value) => serde_json::Value::from(*value),
251 Self::F64(value) => serde_json::Number::from_f64(*value)
252 .map(serde_json::Value::Number)
253 .unwrap_or(serde_json::Value::Null),
254 Self::Decimal(value) => serde_json::Value::String(value.to_string()),
255 Self::Text(value) => serde_json::Value::String(value.clone()),
256 Self::Json(value) => value.clone(),
257 Self::Date(value) => serde_json::Value::String(value.to_string()),
258 Self::Timestamp(value) => serde_json::Value::from(value.0),
259 Self::Object(record) => crate::record_to_json_value(record),
260 Self::List(values) => {
261 serde_json::Value::Array(values.iter().map(Value::to_json_value).collect())
262 }
263 Self::TypedNull(_) => serde_json::Value::Null,
264 }
265 }
266}
267
268#[cfg(test)]
269mod tests {
270 use super::*;
271
272 #[test]
273 fn value_try_i64_accepts_representable_numeric_variants() {
274 assert_eq!(Value::I64(i64::MIN).try_i64(), Some(i64::MIN));
275 assert_eq!(Value::I64(i64::MAX).try_i64(), Some(i64::MAX));
276 assert_eq!(Value::U64(i64::MAX as u64).try_i64(), Some(i64::MAX));
277 assert_eq!(Value::Decimal(Decimal::from(-42)).try_i64(), Some(-42));
278 }
279
280 #[test]
281 fn value_try_i64_rejects_unsigned_overflow_and_unrelated_variants() {
282 assert_eq!(Value::U64(i64::MAX as u64 + 1).try_i64(), None);
283 assert_eq!(Value::U64(u64::MAX).try_i64(), None);
284 assert_eq!(Value::F64(42.0).try_i64(), None);
285 assert_eq!(Value::Text("42".to_owned()).try_i64(), None);
286 assert_eq!(Value::Null.try_i64(), None);
287 }
288
289 #[test]
290 fn value_try_u64_accepts_representable_numeric_variants() {
291 assert_eq!(Value::U64(0).try_u64(), Some(0));
292 assert_eq!(Value::U64(u64::MAX).try_u64(), Some(u64::MAX));
293 assert_eq!(Value::I64(i64::MAX).try_u64(), Some(i64::MAX as u64));
294 assert_eq!(Value::Decimal(Decimal::from(42)).try_u64(), Some(42));
295 }
296
297 #[test]
298 fn value_try_u64_rejects_negative_and_unrelated_variants() {
299 assert_eq!(Value::I64(-1).try_u64(), None);
300 assert_eq!(Value::Decimal(Decimal::from(-1)).try_u64(), None);
301 assert_eq!(Value::F64(42.0).try_u64(), None);
302 assert_eq!(Value::Text("42".to_owned()).try_u64(), None);
303 assert_eq!(Value::Null.try_u64(), None);
304 }
305
306 #[test]
307 fn value_try_decimal_accepts_decimal_numeric_and_text_variants() {
308 let decimal = Decimal::from_str("123.450").expect("valid decimal");
309
310 assert_eq!(Value::Decimal(decimal).try_decimal(), Some(decimal));
311 assert_eq!(
312 Value::I64(i64::MIN).try_decimal(),
313 Some(Decimal::from(i64::MIN))
314 );
315 assert_eq!(
316 Value::U64(u64::MAX).try_decimal(),
317 Some(Decimal::from(u64::MAX))
318 );
319 assert_eq!(
320 Value::Text("123.450".to_owned()).try_decimal(),
321 Some(decimal)
322 );
323 assert_eq!(
324 Value::F64(136.25).try_decimal(),
325 Decimal::from_f64_retain(136.25)
326 );
327 }
328
329 #[test]
330 fn value_try_decimal_rejects_invalid_text_and_unrelated_variants() {
331 assert_eq!(Value::Text("not-a-decimal".to_owned()).try_decimal(), None);
332 assert_eq!(Value::Bool(true).try_decimal(), None);
333 assert_eq!(Value::F64(f64::NAN).try_decimal(), None);
334 assert_eq!(Value::Null.try_decimal(), None);
335 }
336
337 #[test]
338 fn value_try_decimal_uses_human_decimal_for_f64() {
339 assert_eq!(
340 Value::F64(129.95).try_decimal(),
341 Some(Decimal::from_str("129.95").unwrap())
342 );
343 }
344
345 #[test]
346 fn value_try_f64_accepts_supported_numeric_variants() {
347 assert_eq!(Value::F64(1.25).try_f64(), Some(1.25));
348 assert_eq!(Value::I64(-2).try_f64(), Some(-2.0));
349 assert_eq!(Value::U64(2).try_f64(), Some(2.0));
350 assert_eq!(
351 Value::Decimal(Decimal::from_str("1.5").expect("valid decimal")).try_f64(),
352 Some(1.5)
353 );
354 }
355
356 #[test]
357 fn value_try_f64_rejects_unrelated_variants() {
358 assert_eq!(Value::Text("1.5".to_owned()).try_f64(), None);
359 assert_eq!(Value::Bool(true).try_f64(), None);
360 assert_eq!(Value::Null.try_f64(), None);
361 }
362
363 #[test]
364 fn value_try_date_accepts_date_and_iso_date_text() {
365 let leap_day = NaiveDate::from_ymd_opt(2024, 2, 29).expect("valid leap day");
366
367 assert_eq!(Value::Date(leap_day).try_date(), Some(leap_day));
368 assert_eq!(
369 Value::Text("2024-02-29".to_owned()).try_date(),
370 Some(leap_day)
371 );
372 let millis = leap_day
373 .and_hms_opt(0, 0, 0)
374 .unwrap()
375 .and_utc()
376 .timestamp_millis();
377 assert_eq!(Value::I64(millis).try_date(), Some(leap_day));
378 assert_eq!(Value::U64(millis as u64).try_date(), Some(leap_day));
379 }
380
381 #[test]
382 fn value_try_date_rejects_invalid_dates_and_unrelated_variants() {
383 assert_eq!(Value::Text("2023-02-29".to_owned()).try_date(), None);
384 assert_eq!(
385 Value::Text("2024-02-29T00:00:00Z".to_owned()).try_date(),
386 None
387 );
388 assert_eq!(Value::Null.try_date(), None);
389 }
390
391 #[test]
392 fn value_try_timestamp_accepts_timestamp_and_supported_text_formats() {
393 let utc_timestamp = crate::time::Timestamp(
394 DateTime::parse_from_rfc3339("2024-01-02T03:04:05Z")
395 .expect("valid RFC 3339 timestamp")
396 .with_timezone(&Utc)
397 .timestamp_millis(),
398 );
399 let offset_timestamp = crate::time::Timestamp(
400 DateTime::parse_from_rfc3339("2024-01-02T03:04:05+08:00")
401 .expect("valid RFC 3339 timestamp")
402 .with_timezone(&Utc)
403 .timestamp_millis(),
404 );
405 let naive_timestamp = NaiveDate::from_ymd_opt(2024, 1, 2)
406 .expect("valid date")
407 .and_hms_opt(3, 4, 5)
408 .expect("valid time");
409 let midnight = NaiveDate::from_ymd_opt(2024, 1, 2)
410 .expect("valid date")
411 .and_hms_opt(0, 0, 0)
412 .expect("valid time");
413
414 assert_eq!(
415 Value::Timestamp(utc_timestamp).try_timestamp(),
416 Some(utc_timestamp)
417 );
418 assert_eq!(
419 Value::Text("2024-01-02T03:04:05+08:00".to_owned()).try_timestamp(),
420 Some(offset_timestamp)
421 );
422 assert_eq!(
423 Value::Text("2024-01-02 03:04:05".to_owned()).try_timestamp(),
424 Some(crate::time::Timestamp(
425 DateTime::<Utc>::from_naive_utc_and_offset(naive_timestamp, Utc).timestamp_millis()
426 ))
427 );
428 assert_eq!(
429 Value::Text("2024-01-02".to_owned()).try_timestamp(),
430 Some(crate::time::Timestamp(
431 DateTime::<Utc>::from_naive_utc_and_offset(midnight, Utc).timestamp_millis()
432 ))
433 );
434
435 let millis = utc_timestamp.0;
436 assert_eq!(Value::I64(millis).try_timestamp(), Some(utc_timestamp));
437 assert_eq!(
438 Value::U64(millis as u64).try_timestamp(),
439 Some(utc_timestamp)
440 );
441 }
442
443 #[test]
444 fn value_try_timestamp_normalizes_offsets_and_rejects_invalid_input() {
445 let expected_utc = crate::time::Timestamp(
446 DateTime::parse_from_rfc3339("2024-01-01T19:04:05Z")
447 .expect("valid RFC 3339 timestamp")
448 .with_timezone(&Utc)
449 .timestamp_millis(),
450 );
451
452 assert_eq!(
453 Value::Text("2024-01-02T03:04:05+08:00".to_owned()).try_timestamp(),
454 Some(expected_utc)
455 );
456 assert_eq!(
457 Value::Text("2024-13-40 25:61:61".to_owned()).try_timestamp(),
458 None
459 );
460 assert_eq!(Value::Bool(true).try_timestamp(), None);
461 assert_eq!(Value::Null.try_timestamp(), None);
462 }
463}