1#![allow(clippy::unwrap_used)]
16use super::ConversionError;
17
18use std::{
19 collections::BTreeMap,
20 ops::{Index, IndexMut},
21};
22
23use crate::evaluation::variable_value::{
24 float::Float, integer::Integer, zoned_datetime::ZonedDateTime as VarZonedDateTime,
25 VariableValue,
26};
27
28use std::sync::Arc;
29
30use chrono::{DateTime, FixedOffset, NaiveDateTime};
31use ordered_float::OrderedFloat;
32use serde::{Deserialize, Serialize};
33
34#[derive(Debug, Clone, Hash, Eq, PartialEq, Default, Serialize, Deserialize)]
35pub enum ElementValue {
36 #[default]
37 Null,
38 Bool(bool),
39 Float(OrderedFloat<f64>),
40 Integer(i64),
41 String(Arc<str>),
42 List(Vec<ElementValue>),
43 Object(ElementPropertyMap),
44 LocalDateTime(NaiveDateTime),
45 ZonedDateTime(DateTime<FixedOffset>),
48}
49
50impl From<&ElementPropertyMap> for VariableValue {
51 fn from(val: &ElementPropertyMap) -> Self {
52 let mut map = BTreeMap::new();
53 for (key, value) in val.values.iter() {
54 map.insert(key.to_string(), value.into());
55 }
56 VariableValue::Object(map)
57 }
58}
59
60impl From<&ElementValue> for VariableValue {
61 fn from(val: &ElementValue) -> Self {
62 match val {
63 ElementValue::Null => VariableValue::Null,
64 ElementValue::Bool(b) => VariableValue::Bool(*b),
65 ElementValue::Float(f) => VariableValue::Float(Float::from(f.0)),
66 ElementValue::Integer(i) => VariableValue::Integer(Integer::from(*i)),
67 ElementValue::String(s) => VariableValue::String(s.to_string()),
68 ElementValue::List(l) => VariableValue::List(l.iter().map(|x| x.into()).collect()),
69 ElementValue::Object(o) => o.into(),
70 ElementValue::LocalDateTime(dt) => VariableValue::LocalDateTime(*dt),
71 ElementValue::ZonedDateTime(dt) => {
72 VariableValue::ZonedDateTime(VarZonedDateTime::new(*dt, None))
74 }
75 }
76 }
77}
78
79impl TryInto<ElementValue> for &VariableValue {
80 type Error = ConversionError;
81
82 fn try_into(self) -> Result<ElementValue, ConversionError> {
83 match self {
84 VariableValue::Null => Ok(ElementValue::Null),
85 VariableValue::Bool(b) => Ok(ElementValue::Bool(*b)),
86 VariableValue::Float(f) => Ok(ElementValue::Float(OrderedFloat(
87 f.as_f64().unwrap_or_default(),
88 ))),
89 VariableValue::Integer(i) => Ok(ElementValue::Integer(i.as_i64().unwrap_or_default())),
90 VariableValue::String(s) => Ok(ElementValue::String(Arc::from(s.as_str()))),
91 VariableValue::List(l) => Ok(ElementValue::List(
92 l.iter().map(|x| x.try_into().unwrap_or_default()).collect(),
93 )),
94 VariableValue::Object(o) => Ok(ElementValue::Object(o.into())),
95 VariableValue::LocalDateTime(dt) => Ok(ElementValue::LocalDateTime(*dt)),
96 VariableValue::ZonedDateTime(zdt) => Ok(ElementValue::ZonedDateTime(*zdt.datetime())),
98 _ => Err(ConversionError {}),
99 }
100 }
101}
102
103impl TryInto<ElementValue> for VariableValue {
104 type Error = ConversionError;
105
106 fn try_into(self) -> Result<ElementValue, ConversionError> {
107 match self {
108 VariableValue::Null => Ok(ElementValue::Null),
109 VariableValue::Bool(b) => Ok(ElementValue::Bool(b)),
110 VariableValue::Float(f) => Ok(ElementValue::Float(OrderedFloat(
111 f.as_f64().unwrap_or_default(),
112 ))),
113 VariableValue::Integer(i) => Ok(ElementValue::Integer(i.as_i64().unwrap_or_default())),
114 VariableValue::String(s) => Ok(ElementValue::String(Arc::from(s.as_str()))),
115 VariableValue::List(l) => Ok(ElementValue::List(
116 l.iter().map(|x| x.try_into().unwrap_or_default()).collect(),
117 )),
118 VariableValue::Object(o) => Ok(ElementValue::Object(o.into())),
119 VariableValue::LocalDateTime(dt) => Ok(ElementValue::LocalDateTime(dt)),
120 VariableValue::ZonedDateTime(zdt) => Ok(ElementValue::ZonedDateTime(*zdt.datetime())),
122 _ => Err(ConversionError {}),
123 }
124 }
125}
126
127const DRASI_TYPE_TAG: &str = "__drasi_v1_type__";
132const DRASI_ENVELOPE_MARKER_TAG: &str = "__drasi_v1_envelope__";
133const DRASI_ENVELOPE_MARKER_VALUE: &str = "drasi.element_value.datetime";
134
135impl From<&ElementValue> for serde_json::Value {
136 fn from(val: &ElementValue) -> Self {
137 match val {
138 ElementValue::Null => serde_json::Value::Null,
139 ElementValue::Bool(b) => serde_json::Value::Bool(*b),
140 ElementValue::Float(f) => {
141 serde_json::Value::Number(serde_json::Number::from_f64(f.into_inner()).unwrap())
142 }
143 ElementValue::Integer(i) => serde_json::Value::Number(serde_json::Number::from(*i)),
144 ElementValue::String(s) => serde_json::Value::String(s.to_string()),
145 ElementValue::List(l) => serde_json::Value::Array(l.iter().map(|x| x.into()).collect()),
146 ElementValue::Object(o) => serde_json::Value::Object(o.into()),
147 ElementValue::LocalDateTime(dt) => {
148 serde_json::json!({
149 DRASI_ENVELOPE_MARKER_TAG: DRASI_ENVELOPE_MARKER_VALUE,
150 DRASI_TYPE_TAG: "drasi.LocalDateTime",
151 "value": dt.to_string()
152 })
153 }
154 ElementValue::ZonedDateTime(dt) => {
155 serde_json::json!({
156 DRASI_ENVELOPE_MARKER_TAG: DRASI_ENVELOPE_MARKER_VALUE,
157 DRASI_TYPE_TAG: "drasi.ZonedDateTime",
158 "value": dt.to_rfc3339()
159 })
160 }
161 }
162 }
163}
164
165impl From<&serde_json::Value> for ElementValue {
166 fn from(value: &serde_json::Value) -> Self {
167 match value {
168 serde_json::Value::Null => ElementValue::Null,
169 serde_json::Value::Bool(b) => ElementValue::Bool(*b),
170 serde_json::Value::Number(n) => {
171 if let Some(i) = n.as_i64() {
172 ElementValue::Integer(i)
173 } else if let Some(f) = n.as_f64() {
174 ElementValue::Float(OrderedFloat(f))
175 } else {
176 ElementValue::Null
177 }
178 }
179 serde_json::Value::String(s) => ElementValue::String(Arc::from(s.as_str())),
180 serde_json::Value::Array(a) => ElementValue::List(a.iter().map(|x| x.into()).collect()),
181 serde_json::Value::Object(o) => {
182 let has_internal_marker = o.get(DRASI_ENVELOPE_MARKER_TAG).and_then(|v| v.as_str())
184 == Some(DRASI_ENVELOPE_MARKER_VALUE);
185 if has_internal_marker {
186 if let Some(type_tag) = o.get(DRASI_TYPE_TAG).and_then(|v| v.as_str()) {
187 if let Some(val_str) = o.get("value").and_then(|v| v.as_str()) {
188 match type_tag {
189 "drasi.LocalDateTime" => {
190 if let Ok(dt) = NaiveDateTime::parse_from_str(
191 val_str,
192 "%Y-%m-%d %H:%M:%S%.f",
193 ) {
194 return ElementValue::LocalDateTime(dt);
195 }
196 }
197 "drasi.ZonedDateTime" => {
198 if let Ok(dt) = DateTime::parse_from_rfc3339(val_str) {
199 return ElementValue::ZonedDateTime(dt);
200 }
201 }
202 _ => {}
203 }
204 }
205 }
206 }
207 ElementValue::Object(o.into())
208 }
209 }
210 }
211}
212
213impl TryInto<ElementPropertyMap> for &VariableValue {
214 type Error = ConversionError;
215
216 fn try_into(self) -> Result<ElementPropertyMap, ConversionError> {
217 match self {
218 VariableValue::Object(o) => {
219 let mut values = BTreeMap::new();
220 for (key, value) in o.iter() {
221 values.insert(Arc::from(key.as_str()), value.try_into()?);
222 }
223 Ok(ElementPropertyMap { values })
224 }
225 _ => Err(ConversionError {}),
226 }
227 }
228}
229
230#[derive(Debug, Clone, Hash, Eq, PartialEq, Serialize, Deserialize)]
231pub struct ElementPropertyMap {
232 values: BTreeMap<Arc<str>, ElementValue>,
233}
234
235impl Default for ElementPropertyMap {
236 fn default() -> Self {
237 Self::new()
238 }
239}
240
241impl ElementPropertyMap {
242 pub fn new() -> Self {
243 ElementPropertyMap {
244 values: BTreeMap::new(),
245 }
246 }
247
248 pub fn get(&self, key: &str) -> Option<&ElementValue> {
249 self.values.get(key)
250 }
251
252 pub fn insert(&mut self, key: &str, value: ElementValue) {
253 self.values.insert(Arc::from(key), value);
254 }
255
256 pub fn merge(&mut self, other: &ElementPropertyMap) {
257 for (key, value) in other.values.iter() {
258 self.values
259 .entry(key.clone())
260 .or_insert_with(|| value.clone());
261 }
262 }
263
264 pub fn map_iter<T>(
265 &self,
266 f: impl Fn(&Arc<str>, &ElementValue) -> T + 'static,
267 ) -> impl Iterator<Item = T> + '_ {
268 self.values.iter().map(move |(k, v)| f(k, v))
269 }
270}
271
272impl Index<&str> for ElementPropertyMap {
273 type Output = ElementValue;
274
275 fn index(&self, key: &str) -> &Self::Output {
276 static NULL: ElementValue = ElementValue::Null;
277 match self.values.get(key) {
278 Some(value) => value,
279 None => &NULL,
280 }
281 }
282}
283
284impl IndexMut<&str> for ElementPropertyMap {
285 fn index_mut(&mut self, key: &str) -> &mut Self::Output {
286 self.values
287 .entry(Arc::from(key))
288 .or_insert_with(|| ElementValue::Null)
289 }
290}
291
292impl From<&BTreeMap<String, VariableValue>> for ElementPropertyMap {
293 fn from(map: &BTreeMap<String, VariableValue>) -> Self {
294 let mut values = BTreeMap::new();
295 for (key, value) in map.iter() {
296 values.insert(Arc::from(key.as_str()), value.try_into().unwrap());
297 }
298 ElementPropertyMap { values }
299 }
300}
301
302impl From<BTreeMap<String, VariableValue>> for ElementPropertyMap {
303 fn from(map: BTreeMap<String, VariableValue>) -> Self {
304 let mut values = BTreeMap::new();
305 for (key, value) in map {
306 values.insert(Arc::from(key.as_str()), value.try_into().unwrap());
307 }
308 ElementPropertyMap { values }
309 }
310}
311
312impl From<BTreeMap<String, ElementValue>> for ElementPropertyMap {
313 fn from(map: BTreeMap<String, ElementValue>) -> Self {
314 let mut values = BTreeMap::new();
315 for (key, value) in map {
316 values.insert(Arc::from(key.as_str()), value);
317 }
318 ElementPropertyMap { values }
319 }
320}
321
322impl From<&ElementPropertyMap> for serde_json::Map<String, serde_json::Value> {
323 fn from(val: &ElementPropertyMap) -> Self {
324 val.values
325 .iter()
326 .map(|(k, v)| (k.to_string(), v.into()))
327 .collect()
328 }
329}
330
331impl From<&serde_json::Map<String, serde_json::Value>> for ElementPropertyMap {
332 fn from(map: &serde_json::Map<String, serde_json::Value>) -> Self {
333 let mut values = BTreeMap::new();
334 for (key, value) in map.iter() {
335 values.insert(Arc::from(key.as_str()), value.into());
336 }
337 ElementPropertyMap { values }
338 }
339}
340
341impl From<serde_json::Value> for ElementPropertyMap {
342 fn from(value: serde_json::Value) -> Self {
343 match value {
344 serde_json::Value::Object(o) => (&o).into(),
345 _ => ElementPropertyMap::new(),
346 }
347 }
348}
349
350impl From<&serde_json::Value> for ElementPropertyMap {
351 fn from(value: &serde_json::Value) -> Self {
352 match value {
353 serde_json::Value::Object(o) => o.into(),
354 _ => ElementPropertyMap::new(),
355 }
356 }
357}
358
359#[cfg(test)]
360mod tests {
361 use super::*;
362 use crate::evaluation::variable_value::{
363 float::Float, integer::Integer, zoned_datetime::ZonedDateTime as VarZonedDateTime,
364 VariableValue,
365 };
366 use chrono::{NaiveDate, TimeZone, Utc};
367
368 fn sample_naive_datetime() -> NaiveDateTime {
369 NaiveDate::from_ymd_opt(2024, 6, 15)
370 .unwrap()
371 .and_hms_opt(10, 30, 45)
372 .unwrap()
373 }
374
375 fn sample_fixed_datetime() -> DateTime<FixedOffset> {
376 let offset = FixedOffset::east_opt(3600).unwrap(); offset.with_ymd_and_hms(2024, 6, 15, 10, 30, 45).unwrap()
378 }
379
380 #[test]
383 fn local_datetime_to_variable_value() {
384 let dt = sample_naive_datetime();
385 let ev = ElementValue::LocalDateTime(dt);
386 let vv: VariableValue = (&ev).into();
387 assert_eq!(vv, VariableValue::LocalDateTime(dt));
388 }
389
390 #[test]
391 fn zoned_datetime_to_variable_value() {
392 let dt = sample_fixed_datetime();
393 let ev = ElementValue::ZonedDateTime(dt);
394 let vv: VariableValue = (&ev).into();
395 let expected = VariableValue::ZonedDateTime(VarZonedDateTime::new(dt, None));
396 assert_eq!(vv, expected);
397 }
398
399 #[test]
402 fn variable_value_ref_local_datetime_to_element_value() {
403 let dt = sample_naive_datetime();
404 let vv = VariableValue::LocalDateTime(dt);
405 let ev: ElementValue = (&vv).try_into().unwrap();
406 assert_eq!(ev, ElementValue::LocalDateTime(dt));
407 }
408
409 #[test]
410 fn variable_value_ref_zoned_datetime_to_element_value() {
411 let dt = sample_fixed_datetime();
412 let vv = VariableValue::ZonedDateTime(VarZonedDateTime::new(dt, None));
413 let ev: ElementValue = (&vv).try_into().unwrap();
414 assert_eq!(ev, ElementValue::ZonedDateTime(dt));
415 }
416
417 #[test]
420 fn variable_value_owned_local_datetime_to_element_value() {
421 let dt = sample_naive_datetime();
422 let vv = VariableValue::LocalDateTime(dt);
423 let ev: ElementValue = vv.try_into().unwrap();
424 assert_eq!(ev, ElementValue::LocalDateTime(dt));
425 }
426
427 #[test]
428 fn variable_value_owned_zoned_datetime_to_element_value() {
429 let dt = sample_fixed_datetime();
430 let vv = VariableValue::ZonedDateTime(VarZonedDateTime::new(dt, None));
431 let ev: ElementValue = vv.try_into().unwrap();
432 assert_eq!(ev, ElementValue::ZonedDateTime(dt));
433 }
434
435 #[test]
438 fn local_datetime_to_json() {
439 let dt = sample_naive_datetime();
440 let ev = ElementValue::LocalDateTime(dt);
441 let json: serde_json::Value = (&ev).into();
442 assert_eq!(
443 json,
444 serde_json::json!({
445 "__drasi_v1_envelope__": "drasi.element_value.datetime",
446 "__drasi_v1_type__": "drasi.LocalDateTime",
447 "value": dt.to_string()
448 })
449 );
450 }
451
452 #[test]
453 fn zoned_datetime_to_json() {
454 let dt = sample_fixed_datetime();
455 let ev = ElementValue::ZonedDateTime(dt);
456 let json: serde_json::Value = (&ev).into();
457 assert_eq!(
458 json,
459 serde_json::json!({
460 "__drasi_v1_envelope__": "drasi.element_value.datetime",
461 "__drasi_v1_type__": "drasi.ZonedDateTime",
462 "value": dt.to_rfc3339()
463 })
464 );
465 }
466
467 #[test]
470 fn roundtrip_local_datetime() {
471 let dt = sample_naive_datetime();
472 let original = ElementValue::LocalDateTime(dt);
473 let vv: VariableValue = (&original).into();
474 let recovered: ElementValue = vv.try_into().unwrap();
475 assert_eq!(original, recovered);
476 }
477
478 #[test]
479 fn roundtrip_zoned_datetime() {
480 let dt = sample_fixed_datetime();
481 let original = ElementValue::ZonedDateTime(dt);
482 let vv: VariableValue = (&original).into();
483 let recovered: ElementValue = vv.try_into().unwrap();
484 assert_eq!(original, recovered);
485 }
486
487 #[test]
490 fn json_string_does_not_become_datetime() {
491 let json = serde_json::Value::String("2024-06-15 10:30:45".to_string());
494 let ev: ElementValue = (&json).into();
495 assert!(matches!(ev, ElementValue::String(_)));
496 }
497
498 #[test]
501 fn json_roundtrip_local_datetime() {
502 let dt = sample_naive_datetime();
503 let original = ElementValue::LocalDateTime(dt);
504 let json: serde_json::Value = (&original).into();
505 let recovered: ElementValue = (&json).into();
506 assert_eq!(original, recovered);
507 }
508
509 #[test]
510 fn json_roundtrip_zoned_datetime() {
511 let dt = sample_fixed_datetime();
512 let original = ElementValue::ZonedDateTime(dt);
513 let json: serde_json::Value = (&original).into();
514 let recovered: ElementValue = (&json).into();
515 assert_eq!(original, recovered);
516 }
517
518 #[test]
519 fn json_roundtrip_zoned_datetime_utc() {
520 let dt = Utc
521 .with_ymd_and_hms(2024, 1, 1, 0, 0, 0)
522 .unwrap()
523 .fixed_offset();
524 let original = ElementValue::ZonedDateTime(dt);
525 let json: serde_json::Value = (&original).into();
526 let recovered: ElementValue = (&json).into();
527 assert_eq!(original, recovered);
528 }
529
530 #[test]
531 fn tagged_object_with_unknown_type_stays_object() {
532 let json = serde_json::json!({
535 "__drasi_v1_envelope__": "drasi.element_value.datetime",
536 "__drasi_v1_type__": "UnknownType",
537 "value": "some-value"
538 });
539 let ev: ElementValue = (&json).into();
540 assert!(matches!(ev, ElementValue::Object(_)));
541 }
542
543 #[test]
544 fn tagged_object_with_invalid_datetime_stays_object() {
545 let json = serde_json::json!({
548 "__drasi_v1_envelope__": "drasi.element_value.datetime",
549 "__drasi_v1_type__": "drasi.ZonedDateTime",
550 "value": "not-a-datetime"
551 });
552 let ev: ElementValue = (&json).into();
553 assert!(matches!(ev, ElementValue::Object(_)));
554 }
555
556 #[test]
557 fn object_with_common_type_key_does_not_get_interpreted_as_datetime() {
558 let json = serde_json::json!({
560 "_type": "drasi.LocalDateTime",
561 "value": "2024-06-15 10:30:45",
562 "timezone_name": "UTC"
563 });
564 let ev: ElementValue = (&json).into();
565 assert!(matches!(ev, ElementValue::Object(_)));
566 }
567
568 #[test]
569 fn object_with_internal_type_but_no_marker_stays_object() {
570 let json = serde_json::json!({
573 "__drasi_v1_type__": "drasi.ZonedDateTime",
574 "value": "1970-01-01T00:00:00+00:00"
575 });
576 let ev: ElementValue = (&json).into();
577 assert!(matches!(ev, ElementValue::Object(_)));
578 }
579
580 #[test]
583 fn null_roundtrip() {
584 let ev = ElementValue::Null;
585 let vv: VariableValue = (&ev).into();
586 assert_eq!(vv, VariableValue::Null);
587 let recovered: ElementValue = vv.try_into().unwrap();
588 assert_eq!(recovered, ElementValue::Null);
589 }
590
591 #[test]
592 fn bool_roundtrip() {
593 let ev = ElementValue::Bool(true);
594 let vv: VariableValue = (&ev).into();
595 assert_eq!(vv, VariableValue::Bool(true));
596 }
597
598 #[test]
599 fn integer_roundtrip() {
600 let ev = ElementValue::Integer(42);
601 let vv: VariableValue = (&ev).into();
602 assert_eq!(vv, VariableValue::Integer(Integer::from(42i64)));
603 }
604
605 #[test]
606 fn zoned_datetime_utc() {
607 let dt = Utc
608 .with_ymd_and_hms(2024, 1, 1, 0, 0, 0)
609 .unwrap()
610 .fixed_offset();
611 let ev = ElementValue::ZonedDateTime(dt);
612 let vv: VariableValue = (&ev).into();
613 let recovered: ElementValue = vv.try_into().unwrap();
614 assert_eq!(recovered, ev);
615 }
616}