1use std::str::FromStr;
4
5use serde::{Deserialize, Deserializer, Serialize, Serializer};
6
7#[derive(Debug, Clone, PartialEq)]
15pub enum Value {
16 Null,
18 Bool(bool),
20 I32(i32),
22 I64(i64),
24 F64(f64),
26 Decimal(rust_decimal::Decimal),
28 DateTime(chrono::NaiveDateTime),
30 Uuid(uuid::Uuid),
32 Json(serde_json::Value),
34 String(String),
36 Bytes(Vec<u8>),
38 Array(Vec<Value>),
40 Array2D(Vec<Vec<Value>>),
42 Enum {
49 value: String,
51 type_name: String,
53 },
54}
55
56#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
57#[serde(tag = "type", content = "value", rename_all = "snake_case")]
58enum SerdeValue {
59 Null,
60 Bool(bool),
61 I32(i32),
62 I64(i64),
63 F64(f64),
64 Decimal(String),
65 DateTime(String),
66 Uuid(String),
67 Json(serde_json::Value),
68 String(String),
69 Bytes(String),
70 Array(Vec<Value>),
71 Array2D(Vec<Vec<Value>>),
72 Enum { value: String, type_name: String },
73}
74
75fn format_datetime(value: chrono::NaiveDateTime) -> String {
76 value.format("%Y-%m-%dT%H:%M:%S%.fZ").to_string()
77}
78
79fn parse_datetime_string(raw: &str) -> std::result::Result<chrono::NaiveDateTime, String> {
80 chrono::DateTime::parse_from_rfc3339(raw)
81 .map(|value| value.naive_utc())
82 .or_else(|_| chrono::NaiveDateTime::parse_from_str(raw, "%Y-%m-%dT%H:%M:%S%.f"))
83 .or_else(|_| chrono::NaiveDateTime::parse_from_str(raw, "%Y-%m-%d %H:%M:%S%.f"))
84 .map_err(|_| format!("invalid datetime '{}'", raw))
85}
86
87impl From<&Value> for SerdeValue {
88 fn from(value: &Value) -> Self {
89 match value {
90 Value::Null => SerdeValue::Null,
91 Value::Bool(v) => SerdeValue::Bool(*v),
92 Value::I32(v) => SerdeValue::I32(*v),
93 Value::I64(v) => SerdeValue::I64(*v),
94 Value::F64(v) => SerdeValue::F64(*v),
95 Value::Decimal(v) => SerdeValue::Decimal(v.to_string()),
96 Value::DateTime(v) => SerdeValue::DateTime(format_datetime(*v)),
97 Value::Uuid(v) => SerdeValue::Uuid(v.to_string()),
98 Value::Json(v) => SerdeValue::Json(v.clone()),
99 Value::String(v) => SerdeValue::String(v.clone()),
100 Value::Bytes(v) => {
101 use base64::Engine;
102 SerdeValue::Bytes(base64::engine::general_purpose::STANDARD.encode(v))
103 }
104 Value::Array(v) => SerdeValue::Array(v.clone()),
105 Value::Array2D(v) => SerdeValue::Array2D(v.clone()),
106 Value::Enum { value, type_name } => SerdeValue::Enum {
107 value: value.clone(),
108 type_name: type_name.clone(),
109 },
110 }
111 }
112}
113
114impl TryFrom<SerdeValue> for Value {
115 type Error = String;
116
117 fn try_from(value: SerdeValue) -> std::result::Result<Self, Self::Error> {
118 match value {
119 SerdeValue::Null => Ok(Value::Null),
120 SerdeValue::Bool(v) => Ok(Value::Bool(v)),
121 SerdeValue::I32(v) => Ok(Value::I32(v)),
122 SerdeValue::I64(v) => Ok(Value::I64(v)),
123 SerdeValue::F64(v) => Ok(Value::F64(v)),
124 SerdeValue::Decimal(raw) => rust_decimal::Decimal::from_str(&raw)
125 .map(Value::Decimal)
126 .map_err(|e| format!("invalid decimal '{}': {}", raw, e)),
127 SerdeValue::DateTime(raw) => parse_datetime_string(&raw).map(Value::DateTime),
128 SerdeValue::Uuid(raw) => uuid::Uuid::parse_str(&raw)
129 .map(Value::Uuid)
130 .map_err(|e| format!("invalid uuid '{}': {}", raw, e)),
131 SerdeValue::Json(v) => Ok(Value::Json(v)),
132 SerdeValue::String(v) => Ok(Value::String(v)),
133 SerdeValue::Bytes(raw) => {
134 use base64::Engine;
135 base64::engine::general_purpose::STANDARD
136 .decode(raw.as_bytes())
137 .map(Value::Bytes)
138 .map_err(|e| format!("invalid base64 bytes '{}': {}", raw, e))
139 }
140 SerdeValue::Array(v) => Ok(Value::Array(v)),
141 SerdeValue::Array2D(v) => Ok(Value::Array2D(v)),
142 SerdeValue::Enum { value, type_name } => Ok(Value::Enum { value, type_name }),
143 }
144 }
145}
146
147impl From<bool> for Value {
148 fn from(v: bool) -> Self {
149 Value::Bool(v)
150 }
151}
152
153impl From<i32> for Value {
154 fn from(v: i32) -> Self {
155 Value::I32(v)
156 }
157}
158
159impl From<i64> for Value {
160 fn from(v: i64) -> Self {
161 Value::I64(v)
162 }
163}
164
165impl From<f64> for Value {
166 fn from(v: f64) -> Self {
167 Value::F64(v)
168 }
169}
170
171impl From<rust_decimal::Decimal> for Value {
172 fn from(v: rust_decimal::Decimal) -> Self {
173 Value::Decimal(v)
174 }
175}
176
177impl From<chrono::NaiveDateTime> for Value {
178 fn from(v: chrono::NaiveDateTime) -> Self {
179 Value::DateTime(v)
180 }
181}
182
183impl From<uuid::Uuid> for Value {
184 fn from(v: uuid::Uuid) -> Self {
185 Value::Uuid(v)
186 }
187}
188
189impl From<serde_json::Value> for Value {
190 fn from(v: serde_json::Value) -> Self {
191 Value::Json(v)
192 }
193}
194
195impl From<String> for Value {
196 fn from(v: String) -> Self {
197 Value::String(v)
198 }
199}
200
201impl From<&str> for Value {
202 fn from(v: &str) -> Self {
203 Value::String(v.to_string())
204 }
205}
206
207impl From<Vec<u8>> for Value {
208 fn from(v: Vec<u8>) -> Self {
209 Value::Bytes(v)
210 }
211}
212
213macro_rules! impl_vec_from {
217 ($($t:ty),* $(,)?) => {
218 $(
219 impl From<Vec<$t>> for Value {
220 fn from(v: Vec<$t>) -> Self {
221 Value::Array(v.into_iter().map(|x| x.into()).collect())
222 }
223 }
224
225 impl From<Vec<Vec<$t>>> for Value {
226 fn from(v: Vec<Vec<$t>>) -> Self {
227 Value::Array2D(
228 v.into_iter()
229 .map(|row| row.into_iter().map(|x| x.into()).collect())
230 .collect(),
231 )
232 }
233 }
234 )*
235 };
236}
237
238impl_vec_from!(
239 i32,
240 i64,
241 f64,
242 bool,
243 String,
244 rust_decimal::Decimal,
245 uuid::Uuid,
246 chrono::NaiveDateTime,
247 serde_json::Value,
248);
249
250macro_rules! impl_option_from {
254 ($($t:ty),* $(,)?) => {
255 $(
256 impl From<Option<$t>> for Value {
257 fn from(v: Option<$t>) -> Self {
258 v.map(|x| x.into()).unwrap_or(Value::Null)
259 }
260 }
261 )*
262 };
263}
264
265impl_option_from!(
266 bool,
267 i32,
268 i64,
269 f64,
270 String,
271 rust_decimal::Decimal,
272 uuid::Uuid,
273 chrono::NaiveDateTime,
274);
275
276impl From<Option<&str>> for Value {
277 fn from(v: Option<&str>) -> Self {
278 v.map(|s| Value::String(s.to_string()))
279 .unwrap_or(Value::Null)
280 }
281}
282
283impl Value {
284 pub fn to_json_plain(&self) -> serde_json::Value {
290 match self {
291 Value::Null => serde_json::Value::Null,
292 Value::Bool(v) => serde_json::Value::Bool(*v),
293 Value::I32(v) => serde_json::Value::Number((*v).into()),
294 Value::I64(v) => serde_json::Value::Number((*v).into()),
295 Value::F64(v) => serde_json::Number::from_f64(*v)
296 .map(serde_json::Value::Number)
297 .unwrap_or(serde_json::Value::Null),
298 Value::Decimal(v) => serde_json::Value::String(v.to_string()),
299 Value::DateTime(v) => serde_json::Value::String(format_datetime(*v)),
300 Value::Uuid(v) => serde_json::Value::String(v.to_string()),
301 Value::Json(v) => v.clone(),
302 Value::String(v) => serde_json::Value::String(v.clone()),
303 Value::Bytes(v) => {
304 use base64::Engine;
305 serde_json::Value::String(base64::engine::general_purpose::STANDARD.encode(v))
306 }
307 Value::Array(v) => {
308 serde_json::Value::Array(v.iter().map(Value::to_json_plain).collect())
309 }
310 Value::Array2D(v) => serde_json::Value::Array(
311 v.iter()
312 .map(|row| {
313 serde_json::Value::Array(row.iter().map(Value::to_json_plain).collect())
314 })
315 .collect(),
316 ),
317 Value::Enum { value, .. } => serde_json::Value::String(value.clone()),
318 }
319 }
320}
321
322impl Serialize for Value {
323 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
324 where
325 S: Serializer,
326 {
327 SerdeValue::from(self).serialize(serializer)
328 }
329}
330
331impl<'de> Deserialize<'de> for Value {
334 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
335 where
336 D: Deserializer<'de>,
337 {
338 let tagged = SerdeValue::deserialize(deserializer)?;
339 Value::try_from(tagged).map_err(serde::de::Error::custom)
340 }
341}
342
343pub(crate) fn json_to_value_ref(json: &serde_json::Value) -> Value {
354 match json {
355 serde_json::Value::Null => Value::Null,
356 serde_json::Value::Bool(b) => Value::Bool(*b),
357 serde_json::Value::Number(n) => {
358 if let Some(i) = n.as_i64() {
359 if i >= i32::MIN as i64 && i <= i32::MAX as i64 {
360 Value::I32(i as i32)
361 } else {
362 Value::I64(i)
363 }
364 } else if let Some(f) = n.as_f64() {
365 Value::F64(f)
366 } else {
367 Value::String(n.to_string())
368 }
369 }
370 serde_json::Value::String(s) => Value::String(s.clone()),
371 serde_json::Value::Array(arr) => Value::Array(arr.iter().map(json_to_value_ref).collect()),
372 serde_json::Value::Object(_) => Value::Json(json.clone()),
373 }
374}
375
376#[cfg(test)]
377mod tests {
378 use core::f64;
379
380 use super::*;
381
382 #[test]
383 fn test_value_variants() {
384 assert_eq!(Value::Null, Value::Null);
385 assert_eq!(Value::Bool(true), Value::from(true));
386 assert_eq!(Value::I32(42), Value::from(42i32));
387 assert_eq!(Value::I64(42), Value::from(42i64));
388 assert_eq!(Value::F64(2.5), Value::from(2.5f64));
389 assert_eq!(Value::String("hello".to_string()), Value::from("hello"));
390 assert_eq!(Value::Bytes(vec![1, 2, 3]), Value::from(vec![1u8, 2, 3]));
391
392 use rust_decimal::Decimal;
393 let dec = Decimal::new(12345, 2);
394 assert_eq!(Value::Decimal(dec), Value::from(dec));
395
396 use chrono::NaiveDate;
397 let dt = NaiveDate::from_ymd_opt(2024, 1, 1)
398 .unwrap()
399 .and_hms_opt(12, 0, 0)
400 .unwrap();
401 assert_eq!(Value::DateTime(dt), Value::from(dt));
402
403 use uuid::Uuid;
404 let id = Uuid::nil();
405 assert_eq!(Value::Uuid(id), Value::from(id));
406
407 use serde_json::json;
408 let j = json!({"key": "value"});
409 assert_eq!(Value::Json(j.clone()), Value::from(j));
410 }
411
412 #[test]
413 fn test_value_to_json_plain_primitives() {
414 assert_eq!(Value::Null.to_json_plain(), serde_json::Value::Null);
415 assert_eq!(
416 Value::Bool(true).to_json_plain(),
417 serde_json::Value::Bool(true)
418 );
419 assert_eq!(Value::I32(42).to_json_plain().as_i64(), Some(42));
420 assert_eq!(
421 Value::I64(9007199254740991).to_json_plain().as_i64(),
422 Some(9007199254740991)
423 );
424 assert_eq!(
425 Value::F64(f64::consts::PI).to_json_plain().as_f64(),
426 Some(f64::consts::PI)
427 );
428 assert_eq!(
429 Value::String("hello world".to_string())
430 .to_json_plain()
431 .as_str(),
432 Some("hello world")
433 );
434 }
435
436 #[test]
437 fn test_value_to_json_plain_special_scalars() {
438 use rust_decimal::Decimal;
439 let dec = Decimal::new(12345, 2);
440 use chrono::NaiveDate;
441 let dt = NaiveDate::from_ymd_opt(2026, 2, 18)
442 .unwrap()
443 .and_hms_opt(10, 30, 45)
444 .unwrap();
445 use uuid::Uuid;
446 let id = Uuid::parse_str("550e8400-e29b-41d4-a716-446655440000").unwrap();
447 assert_eq!(Value::Decimal(dec).to_json_plain().as_str(), Some("123.45"));
448 assert!(Value::DateTime(dt)
449 .to_json_plain()
450 .as_str()
451 .unwrap()
452 .starts_with("2026-02-18T10:30:45"));
453 assert_eq!(
454 Value::Uuid(id).to_json_plain().as_str(),
455 Some("550e8400-e29b-41d4-a716-446655440000")
456 );
457 assert_eq!(
458 Value::Bytes(vec![72, 101, 108, 108, 111])
459 .to_json_plain()
460 .as_str(),
461 Some("SGVsbG8=")
462 );
463 }
464
465 #[test]
466 fn test_value_to_json_plain_json_and_arrays() {
467 use serde_json::json;
468 let object = json!({"name": "Alice", "age": 30});
469 assert_eq!(Value::Json(object.clone()).to_json_plain(), object);
470
471 let value = Value::Array(vec![
472 Value::String("a".to_string()),
473 Value::String("b".to_string()),
474 Value::String("c".to_string()),
475 ]);
476
477 let json = value.to_json_plain();
478 assert_eq!(json[0].as_str(), Some("a"));
479 assert_eq!(json[1].as_str(), Some("b"));
480 assert_eq!(json[2].as_str(), Some("c"));
481 }
482
483 #[test]
484 fn test_value_plain_json_array2d_roundtrip_stays_untyped_without_schema() {
485 let value = Value::Array2D(vec![
486 vec![Value::I32(1), Value::I32(2)],
487 vec![Value::I32(3), Value::I32(4)],
488 ]);
489
490 let json = value.to_json_plain();
491 assert_eq!(json[0][0].as_i64(), Some(1));
492 assert_eq!(json[0][1].as_i64(), Some(2));
493 assert_eq!(json[1][0].as_i64(), Some(3));
494 assert_eq!(json[1][1].as_i64(), Some(4));
495
496 let expected = Value::Array(vec![
501 Value::Array(vec![Value::I32(1), Value::I32(2)]),
502 Value::Array(vec![Value::I32(3), Value::I32(4)]),
503 ]);
504 assert_eq!(json_to_value_ref(&json), expected);
505 }
506
507 #[test]
508 fn test_tagged_serde_shape_is_explicit() {
509 let value = Value::Decimal(rust_decimal::Decimal::new(12345, 2));
510 let json = serde_json::to_value(&value).unwrap();
511
512 assert_eq!(
513 json,
514 serde_json::json!({
515 "type": "decimal",
516 "value": "123.45"
517 })
518 );
519 }
520
521 #[test]
522 fn test_tagged_serde_round_trip_preserves_typed_variants() {
523 use chrono::NaiveDate;
524 use serde_json::json;
525 use uuid::Uuid;
526
527 let values = vec![
528 Value::Null,
529 Value::Bool(false),
530 Value::I32(-42),
531 Value::I64(9007199254740991), Value::F64(f64::consts::E),
533 Value::Decimal(rust_decimal::Decimal::new(314, 2)),
534 Value::DateTime(
535 NaiveDate::from_ymd_opt(2026, 2, 18)
536 .unwrap()
537 .and_hms_opt(10, 30, 45)
538 .unwrap(),
539 ),
540 Value::Uuid(Uuid::parse_str("550e8400-e29b-41d4-a716-446655440000").unwrap()),
541 Value::Bytes(vec![1, 2, 3, 4]),
542 Value::Json(json!({"ok": true})),
543 Value::String("test".to_string()),
544 Value::Array(vec![Value::I32(1), Value::I32(2)]),
545 Value::Array2D(vec![vec![Value::I32(1), Value::I32(2)]]),
546 Value::Enum {
547 value: "ADMIN".to_string(),
548 type_name: "role".to_string(),
549 },
550 ];
551
552 for value in values {
553 let json = serde_json::to_value(&value).unwrap();
554 let deserialized: Value = serde_json::from_value(json).unwrap();
555 assert_eq!(deserialized, value);
556 }
557 }
558}