loose_liquid_core/model/value/
view.rs

1use std::cmp::Ordering;
2use std::fmt;
3
4use crate::model::KStringCow;
5
6use super::DisplayCow;
7use super::State;
8use super::Value;
9use crate::model::ArrayView;
10use crate::model::ObjectView;
11use crate::model::ScalarCow;
12
13/// Accessor for Values.
14pub trait ValueView: fmt::Debug {
15    /// Get a `Debug` representation
16    fn as_debug(&self) -> &dyn fmt::Debug;
17
18    /// A `Display` for a `BoxedValue` rendered for the user.
19    fn render(&self) -> DisplayCow<'_>;
20    /// A `Display` for a `Value` as source code.
21    fn source(&self) -> DisplayCow<'_>;
22    /// Report the data type (generally for error reporting).
23    fn type_name(&self) -> &'static str;
24    /// Query the value's state
25    fn query_state(&self, state: State) -> bool;
26
27    /// Interpret as a string.
28    fn to_kstr(&self) -> KStringCow<'_>;
29    /// Convert to an owned type.
30    fn to_value(&self) -> Value;
31
32    /// Extracts the scalar value if it is a scalar.
33    fn as_scalar(&self) -> Option<ScalarCow<'_>> {
34        None
35    }
36    /// Tests whether this value is a scalar
37    fn is_scalar(&self) -> bool {
38        self.as_scalar().is_some()
39    }
40
41    /// Extracts the array value if it is an array.
42    fn as_array(&self) -> Option<&dyn ArrayView> {
43        None
44    }
45    /// Tests whether this value is an array
46    fn is_array(&self) -> bool {
47        self.as_array().is_some()
48    }
49
50    /// Extracts the object value if it is a object.
51    fn as_object(&self) -> Option<&dyn ObjectView> {
52        None
53    }
54    /// Tests whether this value is an object
55    fn is_object(&self) -> bool {
56        self.as_object().is_some()
57    }
58
59    /// Extracts the state if it is one
60    fn as_state(&self) -> Option<State> {
61        None
62    }
63    /// Tests whether this value is state
64    fn is_state(&self) -> bool {
65        self.as_state().is_some()
66    }
67
68    /// Tests whether this value is nil
69    ///
70    /// See the [Stack overflow table](https://stackoverflow.com/questions/885414/a-concise-explanation-of-nil-v-empty-v-blank-in-ruby-on-rails)
71    fn is_nil(&self) -> bool {
72        false
73    }
74}
75
76impl<'v, V: ValueView + ?Sized> ValueView for &'v V {
77    fn as_debug(&self) -> &dyn fmt::Debug {
78        <V as ValueView>::as_debug(self)
79    }
80
81    fn render(&self) -> DisplayCow<'_> {
82        <V as ValueView>::render(self)
83    }
84    fn source(&self) -> DisplayCow<'_> {
85        <V as ValueView>::source(self)
86    }
87    fn type_name(&self) -> &'static str {
88        <V as ValueView>::type_name(self)
89    }
90    fn query_state(&self, state: State) -> bool {
91        <V as ValueView>::query_state(self, state)
92    }
93
94    fn to_kstr(&self) -> KStringCow<'_> {
95        <V as ValueView>::to_kstr(self)
96    }
97    fn to_value(&self) -> Value {
98        <V as ValueView>::to_value(self)
99    }
100
101    fn as_scalar(&self) -> Option<ScalarCow<'_>> {
102        <V as ValueView>::as_scalar(self)
103    }
104
105    fn as_array(&self) -> Option<&dyn ArrayView> {
106        <V as ValueView>::as_array(self)
107    }
108
109    fn as_object(&self) -> Option<&dyn ObjectView> {
110        <V as ValueView>::as_object(self)
111    }
112
113    fn as_state(&self) -> Option<State> {
114        <V as ValueView>::as_state(self)
115    }
116
117    fn is_nil(&self) -> bool {
118        <V as ValueView>::is_nil(self)
119    }
120}
121
122static NIL: Value = Value::Nil;
123
124impl<T: ValueView> ValueView for Option<T> {
125    fn as_debug(&self) -> &dyn fmt::Debug {
126        self
127    }
128
129    fn render(&self) -> DisplayCow<'_> {
130        forward(self).render()
131    }
132    fn source(&self) -> DisplayCow<'_> {
133        forward(self).source()
134    }
135    fn type_name(&self) -> &'static str {
136        forward(self).type_name()
137    }
138    fn query_state(&self, state: State) -> bool {
139        forward(self).query_state(state)
140    }
141
142    fn to_kstr(&self) -> KStringCow<'_> {
143        forward(self).to_kstr()
144    }
145    fn to_value(&self) -> Value {
146        forward(self).to_value()
147    }
148
149    fn as_scalar(&self) -> Option<ScalarCow<'_>> {
150        forward(self).as_scalar()
151    }
152
153    fn as_array(&self) -> Option<&dyn ArrayView> {
154        forward(self).as_array()
155    }
156
157    fn as_object(&self) -> Option<&dyn ObjectView> {
158        forward(self).as_object()
159    }
160
161    fn as_state(&self) -> Option<State> {
162        forward(self).as_state()
163    }
164
165    fn is_nil(&self) -> bool {
166        forward(self).is_nil()
167    }
168}
169
170fn forward(o: &Option<impl ValueView>) -> &dyn ValueView {
171    o.as_ref()
172        .map(|v| v as &dyn ValueView)
173        .unwrap_or(&NIL as &dyn ValueView)
174}
175
176/// `Value` comparison semantics for types implementing `ValueView`.
177#[derive(Copy, Clone, Debug)]
178pub struct ValueViewCmp<'v>(&'v dyn ValueView);
179
180impl<'v> ValueViewCmp<'v> {
181    /// `Value` comparison semantics for types implementing `ValueView`.
182    pub fn new(v: &dyn ValueView) -> ValueViewCmp<'_> {
183        ValueViewCmp(v)
184    }
185}
186
187impl<'v> PartialEq<ValueViewCmp<'v>> for ValueViewCmp<'v> {
188    fn eq(&self, other: &Self) -> bool {
189        value_eq(self.0, other.0)
190    }
191}
192
193impl<'v> PartialEq<i64> for ValueViewCmp<'v> {
194    fn eq(&self, other: &i64) -> bool {
195        super::value_eq(self.0, other)
196    }
197}
198
199impl<'v> PartialEq<f64> for ValueViewCmp<'v> {
200    fn eq(&self, other: &f64) -> bool {
201        super::value_eq(self.0, other)
202    }
203}
204
205impl<'v> PartialEq<bool> for ValueViewCmp<'v> {
206    fn eq(&self, other: &bool) -> bool {
207        super::value_eq(self.0, other)
208    }
209}
210
211impl<'v> PartialEq<crate::model::scalar::DateTime> for ValueViewCmp<'v> {
212    fn eq(&self, other: &crate::model::scalar::DateTime) -> bool {
213        super::value_eq(self.0, other)
214    }
215}
216
217impl<'v> PartialEq<crate::model::scalar::Date> for ValueViewCmp<'v> {
218    fn eq(&self, other: &crate::model::scalar::Date) -> bool {
219        super::value_eq(self.0, other)
220    }
221}
222
223impl<'v> PartialEq<str> for ValueViewCmp<'v> {
224    fn eq(&self, other: &str) -> bool {
225        let other = KStringCow::from_ref(other);
226        super::value_eq(self.0, &other)
227    }
228}
229
230impl<'v> PartialEq<&'v str> for ValueViewCmp<'v> {
231    fn eq(&self, other: &&str) -> bool {
232        super::value_eq(self.0, other)
233    }
234}
235
236impl<'v> PartialEq<String> for ValueViewCmp<'v> {
237    fn eq(&self, other: &String) -> bool {
238        self == other.as_str()
239    }
240}
241
242impl<'v> PartialEq<crate::model::KString> for ValueViewCmp<'v> {
243    fn eq(&self, other: &crate::model::KString) -> bool {
244        super::value_eq(self.0, &other.as_ref())
245    }
246}
247
248impl<'v> PartialEq<crate::model::KStringRef<'v>> for ValueViewCmp<'v> {
249    fn eq(&self, other: &crate::model::KStringRef<'v>) -> bool {
250        super::value_eq(self.0, other)
251    }
252}
253
254impl<'v> PartialEq<crate::model::KStringCow<'v>> for ValueViewCmp<'v> {
255    fn eq(&self, other: &crate::model::KStringCow<'v>) -> bool {
256        super::value_eq(self.0, other)
257    }
258}
259
260impl<'v> PartialOrd<ValueViewCmp<'v>> for ValueViewCmp<'v> {
261    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
262        value_cmp(self.0, other.0)
263    }
264}
265
266pub(crate) fn value_eq(lhs: &dyn ValueView, rhs: &dyn ValueView) -> bool {
267    if let (Some(x), Some(y)) = (lhs.as_array(), rhs.as_array()) {
268        if x.size() != y.size() {
269            return false;
270        }
271        return x.values().zip(y.values()).all(|(x, y)| value_eq(x, y));
272    }
273
274    if let (Some(x), Some(y)) = (lhs.as_object(), rhs.as_object()) {
275        if x.size() != y.size() {
276            return false;
277        }
278        return x.iter().all(|(key, value)| {
279            y.get(key.as_str())
280                .map(|v| value_eq(v, value))
281                .unwrap_or(false)
282        });
283    }
284
285    if lhs.is_nil() && rhs.is_nil() {
286        return true;
287    }
288
289    if let Some(state) = lhs.as_state() {
290        return rhs.query_state(state);
291    } else if let Some(state) = rhs.as_state() {
292        return lhs.query_state(state);
293    }
294
295    match (lhs.as_scalar(), rhs.as_scalar()) {
296        (Some(x), Some(y)) => return x == y,
297        (None, None) => (),
298        // encode Ruby truthiness: all values except false and nil are true
299        (Some(x), _) => {
300            if rhs.is_nil() {
301                return !x.to_bool().unwrap_or(true);
302            } else {
303                return x.to_bool().unwrap_or(false);
304            }
305        }
306        (_, Some(x)) => {
307            if lhs.is_nil() {
308                return !x.to_bool().unwrap_or(true);
309            } else {
310                return x.to_bool().unwrap_or(false);
311            }
312        }
313    }
314
315    false
316}
317
318pub(crate) fn value_cmp(lhs: &dyn ValueView, rhs: &dyn ValueView) -> Option<Ordering> {
319    if let (Some(x), Some(y)) = (lhs.as_scalar(), rhs.as_scalar()) {
320        return x.partial_cmp(&y);
321    }
322
323    if let (Some(x), Some(y)) = (lhs.as_array(), rhs.as_array()) {
324        return x
325            .values()
326            .map(|v| ValueViewCmp(v))
327            .partial_cmp(y.values().map(|v| ValueViewCmp(v)));
328    }
329
330    if let (Some(x), Some(y)) = (lhs.as_object(), rhs.as_object()) {
331        return x
332            .iter()
333            .map(|(k, v)| (k, ValueViewCmp(v)))
334            .partial_cmp(y.iter().map(|(k, v)| (k, ValueViewCmp(v))));
335    }
336
337    None
338}
339
340#[cfg(test)]
341mod test {
342    use super::*;
343
344    #[test]
345    fn test_debug() {
346        let scalar = 5;
347        println!("{:?}", scalar);
348        let view: &dyn ValueView = &scalar;
349        println!("{:?}", view);
350        let debug: &dyn fmt::Debug = view.as_debug();
351        println!("{:?}", debug);
352    }
353}