loose_liquid_core/model/scalar/
mod.rs

1//! A Liquid scalar value
2
3#![allow(clippy::eq_op)]
4
5mod date;
6mod datetime;
7pub(crate) mod ser;
8
9use std::cmp::Ordering;
10use std::{borrow::Cow, fmt};
11
12use crate::model::KString;
13use crate::model::KStringCow;
14use crate::model::KStringRef;
15
16use crate::model::value::{DisplayCow, State};
17use crate::model::{Value, ValueView};
18
19pub use date::*;
20pub use datetime::*;
21pub use ser::to_scalar;
22
23/// A Liquid scalar value
24#[derive(Clone, serde::Serialize, serde::Deserialize)]
25#[serde(transparent)]
26#[repr(transparent)]
27pub struct ScalarCow<'s>(ScalarCowEnum<'s>);
28
29/// A Liquid scalar value
30pub type Scalar = ScalarCow<'static>;
31
32/// An enum to represent different value types
33#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
34#[serde(untagged)]
35enum ScalarCowEnum<'s> {
36    Integer(i64),
37    Float(f64),
38    Bool(bool),
39    DateTime(DateTime),
40    Date(Date),
41    Str(KStringCow<'s>),
42}
43
44impl<'s> ScalarCow<'s> {
45    /// Convert a value into a `ScalarCow`.
46    pub fn new<T: Into<Self>>(value: T) -> Self {
47        value.into()
48    }
49
50    /// Create an owned version of the value.
51    pub fn into_owned(self) -> Scalar {
52        match self.0 {
53            ScalarCowEnum::Integer(x) => Scalar::new(x),
54            ScalarCowEnum::Float(x) => Scalar::new(x),
55            ScalarCowEnum::Bool(x) => Scalar::new(x),
56            ScalarCowEnum::DateTime(x) => Scalar::new(x),
57            ScalarCowEnum::Date(x) => Scalar::new(x),
58            ScalarCowEnum::Str(x) => Scalar::new(x.into_owned()),
59        }
60    }
61
62    /// Create a reference to the value.
63    pub fn as_ref<'r: 's>(&'r self) -> ScalarCow<'r> {
64        match self.0 {
65            ScalarCowEnum::Integer(x) => ScalarCow::new(x),
66            ScalarCowEnum::Float(x) => ScalarCow::new(x),
67            ScalarCowEnum::Bool(x) => ScalarCow::new(x),
68            ScalarCowEnum::DateTime(x) => ScalarCow::new(x),
69            ScalarCowEnum::Date(x) => ScalarCow::new(x),
70            ScalarCowEnum::Str(ref x) => ScalarCow::new(x.as_ref()),
71        }
72    }
73
74    /// Create a reference to the value.
75    pub fn as_view<'r: 's>(&'r self) -> &'s dyn ValueView {
76        match self.0 {
77            ScalarCowEnum::Integer(ref x) => x,
78            ScalarCowEnum::Float(ref x) => x,
79            ScalarCowEnum::Bool(ref x) => x,
80            ScalarCowEnum::DateTime(ref x) => x,
81            ScalarCowEnum::Date(ref x) => x,
82            ScalarCowEnum::Str(ref x) => x,
83        }
84    }
85
86    /// Convert to a string.
87    pub fn into_string(self) -> KString {
88        match self.0 {
89            ScalarCowEnum::Integer(x) => x.to_string().into(),
90            ScalarCowEnum::Float(x) => x.to_string().into(),
91            ScalarCowEnum::Bool(x) => x.to_string().into(),
92            ScalarCowEnum::DateTime(x) => x.to_string().into(),
93            ScalarCowEnum::Date(x) => x.to_string().into(),
94            ScalarCowEnum::Str(x) => x.into_owned(),
95        }
96    }
97
98    /// Interpret as an integer, if possible
99    pub fn to_integer(&self) -> Option<i64> {
100        match self.0 {
101            ScalarCowEnum::Integer(ref x) => Some(*x),
102            ScalarCowEnum::Str(ref x) => x.parse::<i64>().ok(),
103            _ => None,
104        }
105    }
106
107    /// Interpret as a float, if possible
108    pub fn to_float(&self) -> Option<f64> {
109        match self.0 {
110            ScalarCowEnum::Integer(ref x) => Some(*x as f64),
111            ScalarCowEnum::Float(ref x) => Some(*x),
112            ScalarCowEnum::Str(ref x) => x.parse::<f64>().ok(),
113            _ => None,
114        }
115    }
116
117    /// Interpret as a bool, if possible
118    pub fn to_bool(&self) -> Option<bool> {
119        match self.0 {
120            ScalarCowEnum::Bool(ref x) => Some(*x),
121            _ => None,
122        }
123    }
124
125    /// Interpret as a date time, if possible
126    pub fn to_date_time(&self) -> Option<DateTime> {
127        match self.0 {
128            ScalarCowEnum::DateTime(ref x) => Some(*x),
129            ScalarCowEnum::Str(ref x) => DateTime::from_str(x.as_str()),
130            _ => None,
131        }
132    }
133
134    /// Interpret as a date time, if possible
135    pub fn to_date(&self) -> Option<Date> {
136        match self.0 {
137            ScalarCowEnum::DateTime(ref x) => Some(x.date()),
138            ScalarCowEnum::Date(ref x) => Some(*x),
139            ScalarCowEnum::Str(ref x) => Date::from_str(x.as_str()),
140            _ => None,
141        }
142    }
143
144    /// Interpret as a Cow str, borrowing if possible
145    pub fn into_cow_str(self) -> Cow<'s, str> {
146        match self {
147            Self(ScalarCowEnum::Str(x)) => x.into_cow_str(),
148            other => other.into_string().into_cow_str(),
149        }
150    }
151}
152
153impl<'s> fmt::Debug for ScalarCow<'s> {
154    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
155        fmt::Debug::fmt(&self.0, f)
156    }
157}
158
159impl<'s> ValueView for ScalarCow<'s> {
160    fn as_debug(&self) -> &dyn fmt::Debug {
161        self
162    }
163
164    fn render(&self) -> DisplayCow<'_> {
165        self.as_view().render()
166    }
167    fn source(&self) -> DisplayCow<'_> {
168        self.as_view().source()
169    }
170    fn type_name(&self) -> &'static str {
171        self.as_view().type_name()
172    }
173    fn query_state(&self, state: State) -> bool {
174        self.as_view().query_state(state)
175    }
176
177    fn to_kstr(&self) -> KStringCow<'_> {
178        self.as_view().to_kstr()
179    }
180    fn to_value(&self) -> Value {
181        self.as_view().to_value()
182    }
183
184    fn as_scalar(&self) -> Option<ScalarCow<'_>> {
185        Some(self.as_ref())
186    }
187}
188
189impl<'s> PartialEq<ScalarCow<'s>> for ScalarCow<'s> {
190    fn eq(&self, other: &Self) -> bool {
191        scalar_eq(self, other)
192    }
193}
194
195impl<'s> PartialOrd<ScalarCow<'s>> for ScalarCow<'s> {
196    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
197        scalar_cmp(self, other)
198    }
199}
200
201impl ValueView for i64 {
202    fn as_debug(&self) -> &dyn fmt::Debug {
203        self
204    }
205
206    fn render(&self) -> DisplayCow<'_> {
207        DisplayCow::Borrowed(self)
208    }
209    fn source(&self) -> DisplayCow<'_> {
210        DisplayCow::Borrowed(self)
211    }
212    fn type_name(&self) -> &'static str {
213        "whole number"
214    }
215    fn query_state(&self, state: State) -> bool {
216        match state {
217            State::Truthy => true,
218            State::DefaultValue => false,
219            State::Empty => false,
220            State::Blank => false,
221        }
222    }
223
224    fn to_kstr(&self) -> KStringCow<'_> {
225        self.render().to_string().into()
226    }
227    fn to_value(&self) -> Value {
228        Value::scalar(*self)
229    }
230
231    fn as_scalar(&self) -> Option<ScalarCow<'_>> {
232        Some(ScalarCow::new(*self))
233    }
234}
235
236impl<'s> From<i64> for ScalarCow<'s> {
237    fn from(s: i64) -> Self {
238        ScalarCow(ScalarCowEnum::Integer(s))
239    }
240}
241
242impl<'s> PartialEq<i64> for ScalarCow<'s> {
243    fn eq(&self, other: &i64) -> bool {
244        let other = (*other).into();
245        scalar_eq(self, &other)
246    }
247}
248
249impl<'s> PartialOrd<i64> for ScalarCow<'s> {
250    fn partial_cmp(&self, other: &i64) -> Option<Ordering> {
251        let other = (*other).into();
252        scalar_cmp(self, &other)
253    }
254}
255
256macro_rules! impl_copyable {
257    ($user: ty, $internal: ty) => {
258        impl ValueView for $user {
259            fn as_debug(&self) -> &dyn fmt::Debug {
260                self
261            }
262
263            fn render(&self) -> DisplayCow<'_> {
264                DisplayCow::Borrowed(self)
265            }
266            fn source(&self) -> DisplayCow<'_> {
267                DisplayCow::Borrowed(self)
268            }
269            fn type_name(&self) -> &'static str {
270                <$internal>::from(*self).type_name()
271            }
272            fn query_state(&self, state: State) -> bool {
273                <$internal>::from(*self).query_state(state)
274            }
275
276            fn to_kstr(&self) -> KStringCow<'_> {
277                self.render().to_string().into()
278            }
279            fn to_value(&self) -> Value {
280                <$internal>::from(*self).to_value()
281            }
282
283            fn as_scalar(&self) -> Option<ScalarCow<'_>> {
284                Some(ScalarCow::new(<$internal>::from(*self)))
285            }
286        }
287
288        impl<'s> From<$user> for ScalarCow<'s> {
289            fn from(s: $user) -> Self {
290                ScalarCow::from(<$internal>::from(s))
291            }
292        }
293
294        impl<'s> PartialEq<$user> for ScalarCow<'s> {
295            fn eq(&self, other: &$user) -> bool {
296                let other = <$internal>::from(*other).into();
297                scalar_eq(self, &other)
298            }
299        }
300
301        impl<'s> PartialOrd<$user> for ScalarCow<'s> {
302            fn partial_cmp(&self, other: &$user) -> Option<Ordering> {
303                let other = <$internal>::from(*other).into();
304                scalar_cmp(self, &other)
305            }
306        }
307    };
308}
309
310impl_copyable!(u8, i64);
311impl_copyable!(i8, i64);
312impl_copyable!(u16, i64);
313impl_copyable!(i16, i64);
314impl_copyable!(u32, i64);
315impl_copyable!(i32, i64);
316
317impl ValueView for f64 {
318    fn as_debug(&self) -> &dyn fmt::Debug {
319        self
320    }
321
322    fn render(&self) -> DisplayCow<'_> {
323        DisplayCow::Borrowed(self)
324    }
325    fn source(&self) -> DisplayCow<'_> {
326        DisplayCow::Borrowed(self)
327    }
328    fn type_name(&self) -> &'static str {
329        "fractional number"
330    }
331    fn query_state(&self, state: State) -> bool {
332        match state {
333            State::Truthy => true,
334            State::DefaultValue => false,
335            State::Empty => false,
336            State::Blank => false,
337        }
338    }
339
340    fn to_kstr(&self) -> KStringCow<'_> {
341        self.render().to_string().into()
342    }
343    fn to_value(&self) -> Value {
344        Value::scalar(*self)
345    }
346
347    fn as_scalar(&self) -> Option<ScalarCow<'_>> {
348        Some(ScalarCow::new(*self))
349    }
350}
351
352impl<'s> From<f64> for ScalarCow<'s> {
353    fn from(s: f64) -> Self {
354        ScalarCow(ScalarCowEnum::Float(s))
355    }
356}
357
358impl<'s> PartialEq<f64> for ScalarCow<'s> {
359    fn eq(&self, other: &f64) -> bool {
360        let other = (*other).into();
361        scalar_eq(self, &other)
362    }
363}
364
365impl<'s> PartialOrd<f64> for ScalarCow<'s> {
366    fn partial_cmp(&self, other: &f64) -> Option<Ordering> {
367        let other = (*other).into();
368        scalar_cmp(self, &other)
369    }
370}
371
372impl_copyable!(f32, f64);
373
374impl ValueView for bool {
375    fn as_debug(&self) -> &dyn fmt::Debug {
376        self
377    }
378
379    fn render(&self) -> DisplayCow<'_> {
380        DisplayCow::Borrowed(self)
381    }
382    fn source(&self) -> DisplayCow<'_> {
383        DisplayCow::Borrowed(self)
384    }
385    fn type_name(&self) -> &'static str {
386        "boolean"
387    }
388    fn query_state(&self, state: State) -> bool {
389        match state {
390            State::Truthy => *self,
391            State::DefaultValue => !self,
392            State::Empty => false,
393            State::Blank => !self,
394        }
395    }
396
397    fn to_kstr(&self) -> KStringCow<'_> {
398        self.render().to_string().into()
399    }
400    fn to_value(&self) -> Value {
401        Value::scalar(*self)
402    }
403
404    fn as_scalar(&self) -> Option<ScalarCow<'_>> {
405        Some(ScalarCow::new(*self))
406    }
407}
408
409impl<'s> From<bool> for ScalarCow<'s> {
410    fn from(s: bool) -> Self {
411        ScalarCow(ScalarCowEnum::Bool(s))
412    }
413}
414
415impl<'s> PartialEq<bool> for ScalarCow<'s> {
416    fn eq(&self, other: &bool) -> bool {
417        let other = (*other).into();
418        scalar_eq(self, &other)
419    }
420}
421
422impl<'s> PartialOrd<bool> for ScalarCow<'s> {
423    fn partial_cmp(&self, other: &bool) -> Option<Ordering> {
424        let other = (*other).into();
425        scalar_cmp(self, &other)
426    }
427}
428
429impl ValueView for DateTime {
430    fn as_debug(&self) -> &dyn fmt::Debug {
431        self
432    }
433
434    fn render(&self) -> DisplayCow<'_> {
435        DisplayCow::Borrowed(self)
436    }
437    fn source(&self) -> DisplayCow<'_> {
438        DisplayCow::Borrowed(self)
439    }
440    fn type_name(&self) -> &'static str {
441        "date time"
442    }
443    fn query_state(&self, state: State) -> bool {
444        match state {
445            State::Truthy => true,
446            State::DefaultValue => false,
447            State::Empty => false,
448            State::Blank => false,
449        }
450    }
451
452    fn to_kstr(&self) -> KStringCow<'_> {
453        self.render().to_string().into()
454    }
455    fn to_value(&self) -> Value {
456        Value::scalar(*self)
457    }
458
459    fn as_scalar(&self) -> Option<ScalarCow<'_>> {
460        Some(ScalarCow::new(*self))
461    }
462}
463
464impl<'s> From<DateTime> for ScalarCow<'s> {
465    fn from(s: DateTime) -> Self {
466        ScalarCow(ScalarCowEnum::DateTime(s))
467    }
468}
469
470impl<'s> PartialEq<DateTime> for ScalarCow<'s> {
471    fn eq(&self, other: &DateTime) -> bool {
472        let other = (*other).into();
473        scalar_eq(self, &other)
474    }
475}
476
477impl<'s> PartialOrd<DateTime> for ScalarCow<'s> {
478    fn partial_cmp(&self, other: &DateTime) -> Option<Ordering> {
479        let other = (*other).into();
480        scalar_cmp(self, &other)
481    }
482}
483
484impl ValueView for Date {
485    fn as_debug(&self) -> &dyn fmt::Debug {
486        self
487    }
488
489    fn render(&self) -> DisplayCow<'_> {
490        DisplayCow::Borrowed(self)
491    }
492    fn source(&self) -> DisplayCow<'_> {
493        DisplayCow::Borrowed(self)
494    }
495    fn type_name(&self) -> &'static str {
496        "date"
497    }
498    fn query_state(&self, state: State) -> bool {
499        match state {
500            State::Truthy => true,
501            State::DefaultValue => false,
502            State::Empty => false,
503            State::Blank => false,
504        }
505    }
506
507    fn to_kstr(&self) -> KStringCow<'_> {
508        self.render().to_string().into()
509    }
510    fn to_value(&self) -> Value {
511        Value::scalar(*self)
512    }
513
514    fn as_scalar(&self) -> Option<ScalarCow<'_>> {
515        Some(ScalarCow::new(*self))
516    }
517}
518
519impl<'s> From<Date> for ScalarCow<'s> {
520    fn from(s: Date) -> Self {
521        ScalarCow(ScalarCowEnum::Date(s))
522    }
523}
524
525impl<'s> PartialEq<Date> for ScalarCow<'s> {
526    fn eq(&self, other: &Date) -> bool {
527        let other = (*other).into();
528        scalar_eq(self, &other)
529    }
530}
531
532impl<'s> PartialOrd<Date> for ScalarCow<'s> {
533    fn partial_cmp(&self, other: &Date) -> Option<Ordering> {
534        let other = (*other).into();
535        scalar_cmp(self, &other)
536    }
537}
538
539impl<'s> ValueView for &'s str {
540    fn as_debug(&self) -> &dyn fmt::Debug {
541        self
542    }
543
544    fn render(&self) -> DisplayCow<'_> {
545        DisplayCow::Borrowed(self)
546    }
547    fn source(&self) -> DisplayCow<'_> {
548        DisplayCow::Owned(Box::new(StrSource { s: self }))
549    }
550    fn type_name(&self) -> &'static str {
551        "string"
552    }
553    fn query_state(&self, state: State) -> bool {
554        match state {
555            State::Truthy => true,
556            State::DefaultValue => self.is_empty(),
557            State::Empty => self.is_empty(),
558            State::Blank => self.trim().is_empty(),
559        }
560    }
561
562    fn to_kstr(&self) -> KStringCow<'_> {
563        (*self).into()
564    }
565    fn to_value(&self) -> Value {
566        Value::scalar(ScalarCow::new(KString::from_ref(self)))
567    }
568
569    fn as_scalar(&self) -> Option<ScalarCow<'_>> {
570        Some(ScalarCow::new(*self))
571    }
572}
573
574struct StrSource<'s> {
575    s: &'s str,
576}
577
578impl<'s> fmt::Display for StrSource<'s> {
579    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
580        write!(f, r#""{}""#, self.s)
581    }
582}
583
584impl<'s> From<&'s str> for ScalarCow<'s> {
585    fn from(s: &'s str) -> Self {
586        ScalarCow(ScalarCowEnum::Str(s.into()))
587    }
588}
589
590impl<'s> PartialEq<str> for ScalarCow<'s> {
591    fn eq(&self, other: &str) -> bool {
592        let other = other.into();
593        scalar_eq(self, &other)
594    }
595}
596
597impl<'s> PartialEq<&'s str> for ScalarCow<'s> {
598    fn eq(&self, other: &&str) -> bool {
599        let other = (*other).into();
600        scalar_eq(self, &other)
601    }
602}
603
604impl<'s> PartialOrd<str> for ScalarCow<'s> {
605    fn partial_cmp(&self, other: &str) -> Option<Ordering> {
606        let other = other.into();
607        scalar_cmp(self, &other)
608    }
609}
610
611impl ValueView for String {
612    fn as_debug(&self) -> &dyn fmt::Debug {
613        self
614    }
615
616    fn render(&self) -> DisplayCow<'_> {
617        DisplayCow::Borrowed(self)
618    }
619    fn source(&self) -> DisplayCow<'_> {
620        DisplayCow::Owned(Box::new(StrSource { s: self.as_str() }))
621    }
622    fn type_name(&self) -> &'static str {
623        ValueView::type_name(&self.as_str())
624    }
625    fn query_state(&self, state: State) -> bool {
626        ValueView::query_state(&self.as_str(), state)
627    }
628
629    fn to_kstr(&self) -> KStringCow<'_> {
630        self.as_str().into()
631    }
632    fn to_value(&self) -> Value {
633        ValueView::to_value(&self.as_str())
634    }
635
636    fn as_scalar(&self) -> Option<ScalarCow<'_>> {
637        Some(ScalarCow::new(self.as_str()))
638    }
639}
640
641impl<'s> From<String> for ScalarCow<'s> {
642    fn from(s: String) -> Self {
643        ScalarCow(ScalarCowEnum::Str(s.into()))
644    }
645}
646
647impl<'s> From<&'s String> for ScalarCow<'s> {
648    fn from(s: &'s String) -> ScalarCow<'s> {
649        ScalarCow(ScalarCowEnum::Str(s.as_str().into()))
650    }
651}
652
653impl<'s> PartialEq<String> for ScalarCow<'s> {
654    fn eq(&self, other: &String) -> bool {
655        let other = other.into();
656        scalar_eq(self, &other)
657    }
658}
659
660impl<'s> PartialOrd<String> for ScalarCow<'s> {
661    fn partial_cmp(&self, other: &String) -> Option<Ordering> {
662        let other = other.into();
663        scalar_cmp(self, &other)
664    }
665}
666
667impl ValueView for KString {
668    fn as_debug(&self) -> &dyn fmt::Debug {
669        self
670    }
671
672    fn render(&self) -> DisplayCow<'_> {
673        DisplayCow::Borrowed(self)
674    }
675    fn source(&self) -> DisplayCow<'_> {
676        DisplayCow::Owned(Box::new(StrSource { s: self.as_str() }))
677    }
678    fn type_name(&self) -> &'static str {
679        ValueView::type_name(&self.as_str())
680    }
681    fn query_state(&self, state: State) -> bool {
682        ValueView::query_state(&self.as_str(), state)
683    }
684
685    fn to_kstr(&self) -> KStringCow<'_> {
686        self.as_ref().into()
687    }
688    fn to_value(&self) -> Value {
689        Value::scalar(Scalar::new(self.clone()))
690    }
691
692    fn as_scalar(&self) -> Option<ScalarCow<'_>> {
693        Some(ScalarCow::new(self))
694    }
695}
696
697impl<'s> From<KString> for ScalarCow<'s> {
698    fn from(s: KString) -> Self {
699        ScalarCow(ScalarCowEnum::Str(s.into()))
700    }
701}
702
703impl<'s> From<&'s KString> for ScalarCow<'s> {
704    fn from(s: &'s KString) -> Self {
705        ScalarCow(ScalarCowEnum::Str(s.as_ref().into()))
706    }
707}
708
709impl<'s> PartialEq<KString> for ScalarCow<'s> {
710    fn eq(&self, other: &KString) -> bool {
711        let other = other.into();
712        scalar_eq(self, &other)
713    }
714}
715
716impl<'s> PartialOrd<KString> for ScalarCow<'s> {
717    fn partial_cmp(&self, other: &KString) -> Option<Ordering> {
718        let other = other.into();
719        scalar_cmp(self, &other)
720    }
721}
722
723impl<'s> ValueView for KStringCow<'s> {
724    fn as_debug(&self) -> &dyn fmt::Debug {
725        self
726    }
727
728    fn render(&self) -> DisplayCow<'_> {
729        DisplayCow::Borrowed(self)
730    }
731    fn source(&self) -> DisplayCow<'_> {
732        DisplayCow::Owned(Box::new(StrSource { s: self.as_str() }))
733    }
734    fn type_name(&self) -> &'static str {
735        ValueView::type_name(&self.as_str())
736    }
737    fn query_state(&self, state: State) -> bool {
738        ValueView::query_state(&self.as_str(), state)
739    }
740
741    fn to_kstr(&self) -> KStringCow<'_> {
742        self.clone()
743    }
744    fn to_value(&self) -> Value {
745        Value::scalar(Scalar::new(self.clone().into_owned()))
746    }
747
748    fn as_scalar(&self) -> Option<ScalarCow<'_>> {
749        Some(ScalarCow::new(self))
750    }
751}
752
753impl<'s> From<KStringCow<'s>> for ScalarCow<'s> {
754    fn from(s: KStringCow<'s>) -> Self {
755        ScalarCow(ScalarCowEnum::Str(s))
756    }
757}
758
759impl<'s> From<&'s KStringCow<'s>> for ScalarCow<'s> {
760    fn from(s: &'s KStringCow<'s>) -> Self {
761        ScalarCow(ScalarCowEnum::Str(s.clone()))
762    }
763}
764
765impl<'s> PartialEq<KStringCow<'s>> for ScalarCow<'s> {
766    fn eq(&self, other: &KStringCow<'s>) -> bool {
767        let other = other.into();
768        scalar_eq(self, &other)
769    }
770}
771
772impl<'s> PartialOrd<KStringCow<'s>> for ScalarCow<'s> {
773    fn partial_cmp(&self, other: &KStringCow<'s>) -> Option<Ordering> {
774        let other = other.into();
775        scalar_cmp(self, &other)
776    }
777}
778
779impl<'s> ValueView for KStringRef<'s> {
780    fn as_debug(&self) -> &dyn fmt::Debug {
781        self
782    }
783
784    fn render(&self) -> DisplayCow<'_> {
785        DisplayCow::Borrowed(self)
786    }
787    fn source(&self) -> DisplayCow<'_> {
788        DisplayCow::Owned(Box::new(StrSource { s: self.as_str() }))
789    }
790    fn type_name(&self) -> &'static str {
791        ValueView::type_name(&self.as_str())
792    }
793    fn query_state(&self, state: State) -> bool {
794        ValueView::query_state(&self.as_str(), state)
795    }
796
797    fn to_kstr(&self) -> KStringCow<'_> {
798        self.into()
799    }
800    fn to_value(&self) -> Value {
801        Value::scalar(Scalar::new(self.to_owned()))
802    }
803
804    fn as_scalar(&self) -> Option<ScalarCow<'_>> {
805        Some(ScalarCow::new(self))
806    }
807}
808
809impl<'s> From<KStringRef<'s>> for ScalarCow<'s> {
810    fn from(s: KStringRef<'s>) -> Self {
811        ScalarCow(ScalarCowEnum::Str(s.into()))
812    }
813}
814
815impl<'s> From<&'s KStringRef<'s>> for ScalarCow<'s> {
816    fn from(s: &'s KStringRef<'s>) -> Self {
817        ScalarCow(ScalarCowEnum::Str(s.into()))
818    }
819}
820
821impl<'s> PartialEq<KStringRef<'s>> for ScalarCow<'s> {
822    fn eq(&self, other: &KStringRef<'s>) -> bool {
823        let other = other.into();
824        scalar_eq(self, &other)
825    }
826}
827
828impl<'s> PartialOrd<KStringRef<'s>> for ScalarCow<'s> {
829    fn partial_cmp(&self, other: &KStringRef<'s>) -> Option<Ordering> {
830        let other = other.into();
831        scalar_cmp(self, &other)
832    }
833}
834
835impl<'s> Eq for ScalarCow<'s> {}
836
837fn scalar_eq<'s>(lhs: &ScalarCow<'s>, rhs: &ScalarCow<'s>) -> bool {
838    match (&lhs.0, &rhs.0) {
839        (&ScalarCowEnum::Integer(x), &ScalarCowEnum::Integer(y)) => x == y,
840        (&ScalarCowEnum::Integer(x), &ScalarCowEnum::Float(y)) => (x as f64) == y,
841        (&ScalarCowEnum::Float(x), &ScalarCowEnum::Integer(y)) => x == (y as f64),
842        (&ScalarCowEnum::Float(x), &ScalarCowEnum::Float(y)) => x == y,
843        (&ScalarCowEnum::Bool(x), &ScalarCowEnum::Bool(y)) => x == y,
844        (&ScalarCowEnum::DateTime(x), &ScalarCowEnum::DateTime(y)) => x == y,
845        (&ScalarCowEnum::Date(x), &ScalarCowEnum::Date(y)) => x == y,
846        (&ScalarCowEnum::DateTime(x), &ScalarCowEnum::Date(y)) => x == x.with_date(y),
847        (&ScalarCowEnum::Date(x), &ScalarCowEnum::DateTime(y)) => y.with_date(x) == y,
848        (ScalarCowEnum::Str(x), ScalarCowEnum::Str(y)) => x == y,
849        // encode Ruby truthiness: all values except false and nil are true
850        (_, &ScalarCowEnum::Bool(b)) | (&ScalarCowEnum::Bool(b), _) => b,
851        _ => false,
852    }
853}
854
855fn scalar_cmp<'s>(lhs: &ScalarCow<'s>, rhs: &ScalarCow<'s>) -> Option<Ordering> {
856    match (&lhs.0, &rhs.0) {
857        (&ScalarCowEnum::Integer(x), &ScalarCowEnum::Integer(y)) => x.partial_cmp(&y),
858        (&ScalarCowEnum::Integer(x), &ScalarCowEnum::Float(y)) => (x as f64).partial_cmp(&y),
859        (&ScalarCowEnum::Float(x), &ScalarCowEnum::Integer(y)) => x.partial_cmp(&(y as f64)),
860        (&ScalarCowEnum::Float(x), &ScalarCowEnum::Float(y)) => x.partial_cmp(&y),
861        (&ScalarCowEnum::Bool(x), &ScalarCowEnum::Bool(y)) => x.partial_cmp(&y),
862        (&ScalarCowEnum::DateTime(x), &ScalarCowEnum::DateTime(y)) => x.partial_cmp(&y),
863        (&ScalarCowEnum::Date(x), &ScalarCowEnum::Date(y)) => x.partial_cmp(&y),
864        (&ScalarCowEnum::DateTime(x), &ScalarCowEnum::Date(y)) => x.partial_cmp(&x.with_date(y)),
865        (&ScalarCowEnum::Date(x), &ScalarCowEnum::DateTime(y)) => y.with_date(x).partial_cmp(&y),
866        (ScalarCowEnum::Str(x), ScalarCowEnum::Str(y)) => x.partial_cmp(y),
867        _ => None,
868    }
869}
870
871#[cfg(test)]
872mod test {
873    use super::*;
874
875    static TRUE: ScalarCow<'_> = ScalarCow(ScalarCowEnum::Bool(true));
876    static FALSE: ScalarCow<'_> = ScalarCow(ScalarCowEnum::Bool(false));
877
878    #[test]
879    fn test_to_str_bool() {
880        assert_eq!(TRUE.to_kstr(), "true");
881    }
882
883    #[test]
884    fn test_to_str_integer() {
885        let val: ScalarCow<'_> = 42i64.into();
886        assert_eq!(val.to_kstr(), "42");
887    }
888
889    #[test]
890    fn test_to_str_float() {
891        let val: ScalarCow<'_> = 42f64.into();
892        assert_eq!(val.to_kstr(), "42");
893
894        let val: ScalarCow<'_> = 42.34.into();
895        assert_eq!(val.to_kstr(), "42.34");
896    }
897
898    #[test]
899    fn test_to_str_str() {
900        let val: ScalarCow<'_> = "foobar".into();
901        assert_eq!(val.to_kstr(), "foobar");
902    }
903
904    #[test]
905    fn test_to_integer_bool() {
906        assert_eq!(TRUE.to_integer(), None);
907    }
908
909    #[test]
910    fn test_to_integer_integer() {
911        let val: ScalarCow<'_> = 42i64.into();
912        assert_eq!(val.to_integer(), Some(42i64));
913    }
914
915    #[test]
916    fn test_to_integer_float() {
917        let val: ScalarCow<'_> = 42f64.into();
918        assert_eq!(val.to_integer(), None);
919
920        let val: ScalarCow<'_> = 42.34.into();
921        assert_eq!(val.to_integer(), None);
922    }
923
924    #[test]
925    fn test_to_integer_str() {
926        let val: ScalarCow<'_> = "foobar".into();
927        assert_eq!(val.to_integer(), None);
928
929        let val: ScalarCow<'_> = "42.34".into();
930        assert_eq!(val.to_integer(), None);
931
932        let val: ScalarCow<'_> = "42".into();
933        assert_eq!(val.to_integer(), Some(42));
934    }
935
936    #[test]
937    fn test_to_float_bool() {
938        assert_eq!(TRUE.to_float(), None);
939    }
940
941    #[test]
942    fn test_to_float_integer() {
943        let val: ScalarCow<'_> = 42i64.into();
944        assert_eq!(val.to_float(), Some(42f64));
945    }
946
947    #[test]
948    fn test_to_float_float() {
949        let val: ScalarCow<'_> = 42f64.into();
950        assert_eq!(val.to_float(), Some(42f64));
951
952        let val: ScalarCow<'_> = 42.34.into();
953        assert_eq!(val.to_float(), Some(42.34));
954    }
955
956    #[test]
957    fn test_to_float_str() {
958        let val: ScalarCow<'_> = "foobar".into();
959        assert_eq!(val.to_float(), None);
960
961        let val: ScalarCow<'_> = "42.34".into();
962        assert_eq!(val.to_float(), Some(42.34));
963
964        let val: ScalarCow<'_> = "42".into();
965        assert_eq!(val.to_float(), Some(42f64));
966    }
967
968    #[test]
969    fn test_to_bool_bool() {
970        assert_eq!(TRUE.to_bool(), Some(true));
971    }
972
973    #[test]
974    fn test_to_bool_integer() {
975        let val: ScalarCow<'_> = 42i64.into();
976        assert_eq!(val.to_bool(), None);
977    }
978
979    #[test]
980    fn test_to_bool_float() {
981        let val: ScalarCow<'_> = 42f64.into();
982        assert_eq!(val.to_bool(), None);
983
984        let val: ScalarCow<'_> = 42.34.into();
985        assert_eq!(val.to_bool(), None);
986    }
987
988    #[test]
989    fn test_to_bool_str() {
990        let val: ScalarCow<'_> = "foobar".into();
991        assert_eq!(val.to_bool(), None);
992
993        let val: ScalarCow<'_> = "true".into();
994        assert_eq!(val.to_bool(), None);
995
996        let val: ScalarCow<'_> = "false".into();
997        assert_eq!(val.to_bool(), None);
998    }
999
1000    #[test]
1001    fn integer_equality() {
1002        let val: ScalarCow<'_> = 42i64.into();
1003        let zero: ScalarCow<'_> = 0i64.into();
1004        assert_eq!(val, val);
1005        assert_eq!(zero, zero);
1006        assert!(val != zero);
1007        assert!(zero != val);
1008    }
1009
1010    #[test]
1011    fn integers_have_ruby_truthiness() {
1012        let val: ScalarCow<'_> = 42i64.into();
1013        let zero: ScalarCow<'_> = 0i64.into();
1014        assert_eq!(TRUE, val);
1015        assert_eq!(val, TRUE);
1016        assert!(val.query_state(State::Truthy));
1017
1018        assert_eq!(TRUE, zero);
1019        assert_eq!(zero, TRUE);
1020        assert!(zero.query_state(State::Truthy));
1021    }
1022
1023    #[test]
1024    fn float_equality() {
1025        let val: ScalarCow<'_> = 42f64.into();
1026        let zero: ScalarCow<'_> = 0f64.into();
1027        assert_eq!(val, val);
1028        assert_eq!(zero, zero);
1029        assert!(val != zero);
1030        assert!(zero != val);
1031    }
1032
1033    #[test]
1034    fn floats_have_ruby_truthiness() {
1035        let val: ScalarCow<'_> = 42f64.into();
1036        let zero: ScalarCow<'_> = 0f64.into();
1037        assert_eq!(TRUE, val);
1038        assert_eq!(val, TRUE);
1039        assert!(val.query_state(State::Truthy));
1040
1041        assert_eq!(TRUE, zero);
1042        assert_eq!(zero, TRUE);
1043        assert!(zero.query_state(State::Truthy));
1044    }
1045
1046    #[test]
1047    fn boolean_equality() {
1048        assert_eq!(TRUE, TRUE);
1049        assert_eq!(FALSE, FALSE);
1050        assert!(FALSE != TRUE);
1051        assert!(TRUE != FALSE);
1052    }
1053
1054    #[test]
1055    fn booleans_have_ruby_truthiness() {
1056        assert!(TRUE.query_state(State::Truthy));
1057        assert!(!FALSE.query_state(State::Truthy));
1058    }
1059
1060    #[test]
1061    fn string_equality() {
1062        let alpha: ScalarCow<'_> = "alpha".into();
1063        let beta: ScalarCow<'_> = "beta".into();
1064        let empty: ScalarCow<'_> = "".into();
1065        assert_eq!(alpha, alpha);
1066        assert_eq!(empty, empty);
1067        assert!(alpha != beta);
1068        assert!(beta != alpha);
1069    }
1070
1071    #[test]
1072    fn strings_have_ruby_truthiness() {
1073        // all strings in ruby are true
1074        let alpha: ScalarCow<'_> = "alpha".into();
1075        let empty: ScalarCow<'_> = "".into();
1076        assert_eq!(TRUE, alpha);
1077        assert_eq!(alpha, TRUE);
1078        assert!(alpha.query_state(State::Truthy));
1079
1080        assert_eq!(TRUE, empty);
1081        assert_eq!(empty, TRUE);
1082        assert!(empty.query_state(State::Truthy));
1083    }
1084
1085    #[test]
1086    fn borrows_from_scalar_cow() {
1087        fn is_borrowed(cow: Cow<'_, str>) -> bool {
1088            match cow {
1089                Cow::Borrowed(_) => true,
1090                Cow::Owned(_) => false,
1091            }
1092        }
1093
1094        let s: String = "gamma".into();
1095        let sc: ScalarCow<'_> = s.into();
1096
1097        // clones instead of borrowing
1098        {
1099            fn extract_cow_str(value: &dyn ValueView) -> Cow<'_, str> {
1100                value.to_kstr().into_cow_str()
1101            }
1102            assert_eq!(is_borrowed(extract_cow_str(sc.as_view())), false);
1103        }
1104
1105        // borrows successfully!
1106        {
1107            fn extract_cow_str(value: &dyn ValueView) -> Cow<'_, str> {
1108                value.as_scalar().unwrap().into_cow_str()
1109            }
1110            assert_eq!(is_borrowed(extract_cow_str(&sc)), true);
1111        }
1112    }
1113}