1use std::borrow;
2use std::cmp::Ordering;
3use std::fmt;
4
5use chrono;
6
7pub type Date = chrono::DateTime<chrono::FixedOffset>;
9
10#[derive(Clone, Debug, Serialize, Deserialize)]
12pub struct ScalarCow<'s>(ScalarCowEnum<'s>);
13
14pub type Scalar = ScalarCow<'static>;
16
17#[derive(Clone, Debug, Serialize, Deserialize)]
19#[serde(untagged)]
20enum ScalarCowEnum<'s> {
21 Integer(i32),
22 Float(f64),
23 Bool(bool),
24 #[serde(with = "friendly_date")]
25 Date(Date),
26 Str(borrow::Cow<'s, str>),
27}
28
29impl<'s> ScalarCow<'s> {
30 pub fn new<T: Into<Self>>(value: T) -> Self {
32 value.into()
33 }
34
35 pub fn source(&self) -> ScalarSource<'_> {
37 ScalarSource(&self.0)
38 }
39
40 pub fn render(&self) -> ScalarRendered<'_> {
42 ScalarRendered(&self.0)
43 }
44
45 pub fn into_owned(self) -> Self {
47 match self.0 {
48 ScalarCowEnum::Str(x) => Scalar::new(x.into_owned()),
49 _ => self,
50 }
51 }
52
53 pub fn as_ref<'r: 's>(&'r self) -> ScalarCow<'r> {
55 match self.0 {
56 ScalarCowEnum::Integer(x) => ScalarCow::new(x),
57 ScalarCowEnum::Float(x) => ScalarCow::new(x),
58 ScalarCowEnum::Bool(x) => ScalarCow::new(x),
59 ScalarCowEnum::Date(x) => ScalarCow::new(x),
60 ScalarCowEnum::Str(ref x) => ScalarCow::new(x.as_ref()),
61 }
62 }
63
64 pub fn to_str(&self) -> borrow::Cow<'_, str> {
66 match self.0 {
67 ScalarCowEnum::Integer(ref x) => borrow::Cow::Owned(x.to_string()),
68 ScalarCowEnum::Float(ref x) => borrow::Cow::Owned(x.to_string()),
69 ScalarCowEnum::Bool(ref x) => borrow::Cow::Owned(x.to_string()),
70 ScalarCowEnum::Date(ref x) => borrow::Cow::Owned(x.format(DATE_FORMAT).to_string()),
71 ScalarCowEnum::Str(ref x) => borrow::Cow::Borrowed(x.as_ref()),
72 }
73 }
74
75 pub fn into_string(self) -> String {
77 match self.0 {
78 ScalarCowEnum::Integer(x) => x.to_string(),
79 ScalarCowEnum::Float(x) => x.to_string(),
80 ScalarCowEnum::Bool(x) => x.to_string(),
81 ScalarCowEnum::Date(x) => x.to_string(),
82 ScalarCowEnum::Str(x) => x.into_owned(),
83 }
84 }
85
86 pub fn to_integer(&self) -> Option<i32> {
88 match self.0 {
89 ScalarCowEnum::Integer(ref x) => Some(*x),
90 ScalarCowEnum::Str(ref x) => x.parse::<i32>().ok(),
91 _ => None,
92 }
93 }
94
95 pub fn to_float(&self) -> Option<f64> {
97 match self.0 {
98 ScalarCowEnum::Integer(ref x) => Some(f64::from(*x)),
99 ScalarCowEnum::Float(ref x) => Some(*x),
100 ScalarCowEnum::Str(ref x) => x.parse::<f64>().ok(),
101 _ => None,
102 }
103 }
104
105 pub fn to_bool(&self) -> Option<bool> {
107 match self.0 {
108 ScalarCowEnum::Bool(ref x) => Some(*x),
109 _ => None,
110 }
111 }
112
113 pub fn to_date(&self) -> Option<Date> {
115 match self.0 {
116 ScalarCowEnum::Date(ref x) => Some(*x),
117 ScalarCowEnum::Str(ref x) => parse_date(x.as_ref()),
118 _ => None,
119 }
120 }
121
122 pub fn is_truthy(&self) -> bool {
124 match self.0 {
126 ScalarCowEnum::Bool(ref x) => *x,
127 _ => true,
128 }
129 }
130
131 pub fn is_default(&self) -> bool {
133 match self.0 {
135 ScalarCowEnum::Bool(ref x) => !*x,
136 ScalarCowEnum::Str(ref x) => x.is_empty(),
137 _ => false,
138 }
139 }
140
141 pub fn type_name(&self) -> &'static str {
143 match self.0 {
144 ScalarCowEnum::Integer(_) => "whole number",
145 ScalarCowEnum::Float(_) => "fractional number",
146 ScalarCowEnum::Bool(_) => "boolean",
147 ScalarCowEnum::Date(_) => "date",
148 ScalarCowEnum::Str(_) => "string",
149 }
150 }
151}
152
153impl<'s> From<i32> for ScalarCow<'s> {
154 fn from(s: i32) -> Self {
155 ScalarCow {
156 0: ScalarCowEnum::Integer(s),
157 }
158 }
159}
160
161impl<'s> From<f64> for ScalarCow<'s> {
162 fn from(s: f64) -> Self {
163 ScalarCow {
164 0: ScalarCowEnum::Float(s),
165 }
166 }
167}
168
169impl<'s> From<bool> for ScalarCow<'s> {
170 fn from(s: bool) -> Self {
171 ScalarCow {
172 0: ScalarCowEnum::Bool(s),
173 }
174 }
175}
176
177impl<'s> From<Date> for ScalarCow<'s> {
178 fn from(s: Date) -> Self {
179 ScalarCow {
180 0: ScalarCowEnum::Date(s),
181 }
182 }
183}
184
185impl<'s> From<String> for ScalarCow<'s> {
186 fn from(s: String) -> Self {
187 ScalarCow {
188 0: ScalarCowEnum::Str(s.into()),
189 }
190 }
191}
192
193impl<'s> From<&'s String> for ScalarCow<'s> {
194 fn from(s: &'s String) -> ScalarCow<'s> {
195 ScalarCow {
196 0: ScalarCowEnum::Str(s.as_str().into()),
197 }
198 }
199}
200
201impl<'s> From<&'s str> for ScalarCow<'s> {
202 fn from(s: &'s str) -> Self {
203 ScalarCow {
204 0: ScalarCowEnum::Str(s.into()),
205 }
206 }
207}
208
209impl<'s> From<borrow::Cow<'s, str>> for ScalarCow<'s> {
210 fn from(s: borrow::Cow<'s, str>) -> Self {
211 ScalarCow {
212 0: ScalarCowEnum::Str(s),
213 }
214 }
215}
216
217impl<'s> PartialEq<ScalarCow<'s>> for ScalarCow<'s> {
218 fn eq(&self, other: &Self) -> bool {
219 scalar_eq(self, other)
220 }
221}
222
223impl<'s> PartialEq<i32> for ScalarCow<'s> {
224 fn eq(&self, other: &i32) -> bool {
225 let other = (*other).into();
226 scalar_eq(self, &other)
227 }
228}
229
230impl<'s> PartialEq<f64> for ScalarCow<'s> {
231 fn eq(&self, other: &f64) -> bool {
232 let other = (*other).into();
233 scalar_eq(self, &other)
234 }
235}
236
237impl<'s> PartialEq<bool> for ScalarCow<'s> {
238 fn eq(&self, other: &bool) -> bool {
239 let other = (*other).into();
240 scalar_eq(self, &other)
241 }
242}
243
244impl<'s> PartialEq<Date> for ScalarCow<'s> {
245 fn eq(&self, other: &Date) -> bool {
246 let other = (*other).into();
247 scalar_eq(self, &other)
248 }
249}
250
251impl<'s> PartialEq<str> for ScalarCow<'s> {
252 fn eq(&self, other: &str) -> bool {
253 let other = other.into();
254 scalar_eq(self, &other)
255 }
256}
257
258impl<'s> Eq for ScalarCow<'s> {}
259
260impl<'s> PartialOrd<ScalarCow<'s>> for ScalarCow<'s> {
261 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
262 scalar_cmp(self, other)
263 }
264}
265
266impl<'s> PartialOrd<i32> for ScalarCow<'s> {
267 fn partial_cmp(&self, other: &i32) -> Option<Ordering> {
268 let other = (*other).into();
269 scalar_cmp(self, &other)
270 }
271}
272
273impl<'s> PartialOrd<f64> for ScalarCow<'s> {
274 fn partial_cmp(&self, other: &f64) -> Option<Ordering> {
275 let other = (*other).into();
276 scalar_cmp(self, &other)
277 }
278}
279
280impl<'s> PartialOrd<bool> for ScalarCow<'s> {
281 fn partial_cmp(&self, other: &bool) -> Option<Ordering> {
282 let other = (*other).into();
283 scalar_cmp(self, &other)
284 }
285}
286
287impl<'s> PartialOrd<Date> for ScalarCow<'s> {
288 fn partial_cmp(&self, other: &Date) -> Option<Ordering> {
289 let other = (*other).into();
290 scalar_cmp(self, &other)
291 }
292}
293
294impl<'s> PartialOrd<str> for ScalarCow<'s> {
295 fn partial_cmp(&self, other: &str) -> Option<Ordering> {
296 let other = other.into();
297 scalar_cmp(self, &other)
298 }
299}
300
301#[derive(Debug)]
303pub struct ScalarSource<'s>(&'s ScalarCowEnum<'s>);
304
305impl<'s> fmt::Display for ScalarSource<'s> {
306 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
307 match self.0 {
308 ScalarCowEnum::Integer(ref x) => write!(f, "{}", x),
309 ScalarCowEnum::Float(ref x) => write!(f, "{}", x),
310 ScalarCowEnum::Bool(ref x) => write!(f, "{}", x),
311 ScalarCowEnum::Date(ref x) => write!(f, "{}", x.format(DATE_FORMAT)),
312 ScalarCowEnum::Str(ref x) => write!(f, r#""{}""#, x),
313 }
314 }
315}
316
317#[derive(Debug)]
319pub struct ScalarRendered<'s>(&'s ScalarCowEnum<'s>);
320
321impl<'s> fmt::Display for ScalarRendered<'s> {
322 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
323 match self.0 {
325 ScalarCowEnum::Integer(ref x) => write!(f, "{}", x),
326 ScalarCowEnum::Float(ref x) => write!(f, "{}", x),
327 ScalarCowEnum::Bool(ref x) => write!(f, "{}", x),
328 ScalarCowEnum::Date(ref x) => write!(f, "{}", x.format(DATE_FORMAT)),
329 ScalarCowEnum::Str(ref x) => write!(f, "{}", x),
330 }
331 }
332}
333
334fn scalar_eq<'s>(lhs: &ScalarCow<'s>, rhs: &ScalarCow<'s>) -> bool {
335 match (&lhs.0, &rhs.0) {
336 (&ScalarCowEnum::Integer(x), &ScalarCowEnum::Integer(y)) => x == y,
337 (&ScalarCowEnum::Integer(x), &ScalarCowEnum::Float(y)) => (f64::from(x)) == y,
338 (&ScalarCowEnum::Float(x), &ScalarCowEnum::Integer(y)) => x == (f64::from(y)),
339 (&ScalarCowEnum::Float(x), &ScalarCowEnum::Float(y)) => x == y,
340 (&ScalarCowEnum::Bool(x), &ScalarCowEnum::Bool(y)) => x == y,
341 (&ScalarCowEnum::Date(x), &ScalarCowEnum::Date(y)) => x == y,
342 (&ScalarCowEnum::Str(ref x), &ScalarCowEnum::Str(ref y)) => x == y,
343 (_, &ScalarCowEnum::Bool(b)) | (&ScalarCowEnum::Bool(b), _) => b,
345 _ => false,
346 }
347}
348
349fn scalar_cmp<'s>(lhs: &ScalarCow<'s>, rhs: &ScalarCow<'s>) -> Option<Ordering> {
350 match (&lhs.0, &rhs.0) {
351 (&ScalarCowEnum::Integer(x), &ScalarCowEnum::Integer(y)) => x.partial_cmp(&y),
352 (&ScalarCowEnum::Integer(x), &ScalarCowEnum::Float(y)) => (f64::from(x)).partial_cmp(&y),
353 (&ScalarCowEnum::Float(x), &ScalarCowEnum::Integer(y)) => x.partial_cmp(&(f64::from(y))),
354 (&ScalarCowEnum::Float(x), &ScalarCowEnum::Float(y)) => x.partial_cmp(&y),
355 (&ScalarCowEnum::Bool(x), &ScalarCowEnum::Bool(y)) => x.partial_cmp(&y),
356 (&ScalarCowEnum::Date(x), &ScalarCowEnum::Date(y)) => x.partial_cmp(&y),
357 (&ScalarCowEnum::Str(ref x), &ScalarCowEnum::Str(ref y)) => x.partial_cmp(y),
358 _ => None,
359 }
360}
361
362const DATE_FORMAT: &str = "%Y-%m-%d %H:%M:%S %z";
363
364mod friendly_date {
365 use super::*;
366 use serde::{self, Deserialize, Deserializer, Serializer};
367
368 pub(crate) fn serialize<S>(date: &Date, serializer: S) -> Result<S::Ok, S::Error>
369 where
370 S: Serializer,
371 {
372 let s = date.format(DATE_FORMAT).to_string();
373 serializer.serialize_str(&s)
374 }
375
376 pub(crate) fn deserialize<'de, D>(deserializer: D) -> Result<Date, D::Error>
377 where
378 D: Deserializer<'de>,
379 {
380 let s = String::deserialize(deserializer)?;
381 Date::parse_from_str(&s, DATE_FORMAT).map_err(serde::de::Error::custom)
382 }
383}
384
385fn parse_date(s: &str) -> Option<Date> {
386 match s {
387 "now" | "today" => {
388 let now = chrono::offset::Utc::now();
389 let now = now.naive_utc();
390 let now = chrono::DateTime::from_utc(now, chrono::offset::FixedOffset::east(0));
391 Some(now)
392 }
393 _ => {
394 let formats = ["%d %B %Y %H:%M:%S %z", "%Y-%m-%d %H:%M:%S %z"];
395 formats
396 .iter()
397 .filter_map(|f| Date::parse_from_str(s, f).ok())
398 .next()
399 }
400 }
401}
402
403#[cfg(test)]
404mod test {
405 use super::*;
406
407 static TRUE: ScalarCow<'_> = ScalarCow(ScalarCowEnum::Bool(true));
408 static FALSE: ScalarCow<'_> = ScalarCow(ScalarCowEnum::Bool(false));
409
410 #[test]
411 fn test_to_str_bool() {
412 assert_eq!(TRUE.to_str(), "true");
413 }
414
415 #[test]
416 fn test_to_str_integer() {
417 let val: ScalarCow<'_> = 42i32.into();
418 assert_eq!(val.to_str(), "42");
419 }
420
421 #[test]
422 fn test_to_str_float() {
423 let val: ScalarCow<'_> = 42f64.into();
424 assert_eq!(val.to_str(), "42");
425
426 let val: ScalarCow<'_> = 42.34.into();
427 assert_eq!(val.to_str(), "42.34");
428 }
429
430 #[test]
431 fn test_to_str_str() {
432 let val: ScalarCow<'_> = "foobar".into();
433 assert_eq!(val.to_str(), "foobar");
434 }
435
436 #[test]
437 fn test_to_integer_bool() {
438 assert_eq!(TRUE.to_integer(), None);
439 }
440
441 #[test]
442 fn test_to_integer_integer() {
443 let val: ScalarCow<'_> = 42i32.into();
444 assert_eq!(val.to_integer(), Some(42i32));
445 }
446
447 #[test]
448 fn test_to_integer_float() {
449 let val: ScalarCow<'_> = 42f64.into();
450 assert_eq!(val.to_integer(), None);
451
452 let val: ScalarCow<'_> = 42.34.into();
453 assert_eq!(val.to_integer(), None);
454 }
455
456 #[test]
457 fn test_to_integer_str() {
458 let val: ScalarCow<'_> = "foobar".into();
459 assert_eq!(val.to_integer(), None);
460
461 let val: ScalarCow<'_> = "42.34".into();
462 assert_eq!(val.to_integer(), None);
463
464 let val: ScalarCow<'_> = "42".into();
465 assert_eq!(val.to_integer(), Some(42));
466 }
467
468 #[test]
469 fn test_to_float_bool() {
470 assert_eq!(TRUE.to_float(), None);
471 }
472
473 #[test]
474 fn test_to_float_integer() {
475 let val: ScalarCow<'_> = 42i32.into();
476 assert_eq!(val.to_float(), Some(42f64));
477 }
478
479 #[test]
480 fn test_to_float_float() {
481 let val: ScalarCow<'_> = 42f64.into();
482 assert_eq!(val.to_float(), Some(42f64));
483
484 let val: ScalarCow<'_> = 42.34.into();
485 assert_eq!(val.to_float(), Some(42.34));
486 }
487
488 #[test]
489 fn test_to_float_str() {
490 let val: ScalarCow<'_> = "foobar".into();
491 assert_eq!(val.to_float(), None);
492
493 let val: ScalarCow<'_> = "42.34".into();
494 assert_eq!(val.to_float(), Some(42.34));
495
496 let val: ScalarCow<'_> = "42".into();
497 assert_eq!(val.to_float(), Some(42f64));
498 }
499
500 #[test]
501 fn test_to_bool_bool() {
502 assert_eq!(TRUE.to_bool(), Some(true));
503 }
504
505 #[test]
506 fn test_to_bool_integer() {
507 let val: ScalarCow<'_> = 42i32.into();
508 assert_eq!(val.to_bool(), None);
509 }
510
511 #[test]
512 fn test_to_bool_float() {
513 let val: ScalarCow<'_> = 42f64.into();
514 assert_eq!(val.to_bool(), None);
515
516 let val: ScalarCow<'_> = 42.34.into();
517 assert_eq!(val.to_bool(), None);
518 }
519
520 #[test]
521 fn test_to_bool_str() {
522 let val: ScalarCow<'_> = "foobar".into();
523 assert_eq!(val.to_bool(), None);
524
525 let val: ScalarCow<'_> = "true".into();
526 assert_eq!(val.to_bool(), None);
527
528 let val: ScalarCow<'_> = "false".into();
529 assert_eq!(val.to_bool(), None);
530 }
531
532 #[test]
533 fn integer_equality() {
534 let val: ScalarCow<'_> = 42i32.into();
535 let zero: ScalarCow<'_> = 0i32.into();
536 assert_eq!(val, val);
537 assert_eq!(zero, zero);
538 assert!(val != zero);
539 assert!(zero != val);
540 }
541
542 #[test]
543 fn integers_have_ruby_truthiness() {
544 let val: ScalarCow<'_> = 42i32.into();
545 let zero: ScalarCow<'_> = 0i32.into();
546 assert_eq!(TRUE, val);
547 assert_eq!(val, TRUE);
548 assert!(val.is_truthy());
549
550 assert_eq!(TRUE, zero);
551 assert_eq!(zero, TRUE);
552 assert!(zero.is_truthy());
553 }
554
555 #[test]
556 fn float_equality() {
557 let val: ScalarCow<'_> = 42f64.into();
558 let zero: ScalarCow<'_> = 0f64.into();
559 assert_eq!(val, val);
560 assert_eq!(zero, zero);
561 assert!(val != zero);
562 assert!(zero != val);
563 }
564
565 #[test]
566 fn floats_have_ruby_truthiness() {
567 let val: ScalarCow<'_> = 42f64.into();
568 let zero: ScalarCow<'_> = 0f64.into();
569 assert_eq!(TRUE, val);
570 assert_eq!(val, TRUE);
571 assert!(val.is_truthy());
572
573 assert_eq!(TRUE, zero);
574 assert_eq!(zero, TRUE);
575 assert!(zero.is_truthy());
576 }
577
578 #[test]
579 fn boolean_equality() {
580 assert_eq!(TRUE, TRUE);
581 assert_eq!(FALSE, FALSE);
582 assert!(FALSE != TRUE);
583 assert!(TRUE != FALSE);
584 }
585
586 #[test]
587 fn booleans_have_ruby_truthiness() {
588 assert!(TRUE.is_truthy());
589 assert!(!FALSE.is_truthy());
590 }
591
592 #[test]
593 fn string_equality() {
594 let alpha: ScalarCow<'_> = "alpha".into();
595 let beta: ScalarCow<'_> = "beta".into();
596 let empty: ScalarCow<'_> = "".into();
597 assert_eq!(alpha, alpha);
598 assert_eq!(empty, empty);
599 assert!(alpha != beta);
600 assert!(beta != alpha);
601 }
602
603 #[test]
604 fn strings_have_ruby_truthiness() {
605 let alpha: ScalarCow<'_> = "alpha".into();
607 let empty: ScalarCow<'_> = "".into();
608 assert_eq!(TRUE, alpha);
609 assert_eq!(alpha, TRUE);
610 assert!(alpha.is_truthy());
611
612 assert_eq!(TRUE, empty);
613 assert_eq!(empty, TRUE);
614 assert!(empty.is_truthy());
615 }
616
617 #[test]
618 fn parse_date_empty_is_bad() {
619 assert!(parse_date("").is_none());
620 }
621
622 #[test]
623 fn parse_date_bad() {
624 assert!(parse_date("aaaaa").is_none());
625 }
626
627 #[test]
628 fn parse_date_now() {
629 assert!(parse_date("now").is_some());
630 }
631
632 #[test]
633 fn parse_date_today() {
634 assert!(parse_date("today").is_some());
635 }
636}