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