Skip to main content

cql2/
expr.rs

1use crate::{geometry::spatial_op, temporal::temporal_op, Error, Geometry, Validator};
2use geo_types::{coord, Geometry as GGeom, Rect};
3use json_dotpath::DotPaths;
4use like::Like;
5use pg_escape::{quote_identifier, quote_literal};
6use serde::{Deserialize, Serialize};
7use serde_json::Value;
8use std::{
9    collections::HashSet,
10    fmt::Debug,
11    ops::{Add, Deref},
12    str::FromStr,
13};
14use unaccent::unaccent;
15use wkt::TryFromWkt;
16
17/// Boolean Operators
18pub const BOOLOPS: &[&str] = &["and", "or"];
19
20/// Equality Operators
21pub const EQOPS: &[&str] = &["=", "<>"];
22
23/// Comparison Operators
24pub const CMPOPS: &[&str] = &[">", ">=", "<", "<="];
25
26/// Spatial Operators
27pub const SPATIALOPS: &[&str] = &[
28    "s_equals",
29    "s_intersects",
30    "s_disjoint",
31    "s_touches",
32    "s_within",
33    "s_overlaps",
34    "s_crosses",
35    "s_contains",
36];
37
38/// Temporal Operators
39pub const TEMPORALOPS: &[&str] = &[
40    "t_before",
41    "t_after",
42    "t_meets",
43    "t_metby",
44    "t_overlaps",
45    "t_overlappedby",
46    "t_starts",
47    "t_startedby",
48    "t_during",
49    "t_contains",
50    "t_finishes",
51    "t_finishedby",
52    "t_equals",
53    "t_disjoint",
54    "t_intersects",
55];
56
57/// Arithmetic Operators
58pub const ARITHOPS: &[&str] = &["+", "-", "*", "/", "%", "^", "div"];
59
60/// Array Operators
61pub const ARRAYOPS: &[&str] = &["a_equals", "a_contains", "a_containedby", "a_overlaps"];
62
63// todo: array ops, in, casei, accenti, between, not, like
64
65/// A CQL2 expression.
66///
67/// # Examples
68///
69/// [Expr] implements [FromStr]:
70///
71/// ```
72/// use cql2::Expr;
73///
74/// let expr: Expr = "landsat:scene_id = 'LC82030282019133LGN00'".parse().unwrap();
75/// ```
76///
77/// Use [Expr::to_text], [Expr::to_json], and [Expr::to_sql] to use the CQL2,
78/// and use [Expr::is_valid] to check validity.
79#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, PartialOrd)]
80#[serde(untagged)]
81#[allow(missing_docs)]
82pub enum Expr {
83    Operation { op: String, args: Vec<Box<Expr>> },
84    Interval { interval: Vec<Box<Expr>> },
85    Timestamp { timestamp: Box<Expr> },
86    Date { date: Box<Expr> },
87    Property { property: String },
88    BBox { bbox: Vec<Box<Expr>> },
89    Float(f64),
90    Literal(String),
91    Bool(bool),
92    Array(Vec<Box<Expr>>),
93    Geometry(Geometry),
94    Null,
95}
96impl TryFrom<Value> for Expr {
97    type Error = Error;
98    fn try_from(v: Value) -> Result<Expr, Error> {
99        serde_json::from_value(v).map_err(Error::from)
100    }
101}
102impl TryFrom<Expr> for Value {
103    type Error = Error;
104    fn try_from(v: Expr) -> Result<Value, Error> {
105        serde_json::to_value(v).map_err(Error::from)
106    }
107}
108impl TryFrom<Expr> for f64 {
109    type Error = Error;
110    fn try_from(v: Expr) -> Result<f64, Error> {
111        match v {
112            Expr::Float(v) => Ok(v),
113            Expr::Literal(v) => f64::from_str(&v).map_err(Error::from),
114            _ => Err(Error::ExprToF64(v)),
115        }
116    }
117}
118
119impl TryFrom<&Expr> for bool {
120    type Error = Error;
121    fn try_from(v: &Expr) -> Result<bool, Error> {
122        match v {
123            Expr::Bool(v) => Ok(*v),
124            Expr::Literal(v) => bool::from_str(v).map_err(Error::from),
125            _ => Err(Error::ExprToBool(v.clone())),
126        }
127    }
128}
129
130impl TryFrom<Expr> for String {
131    type Error = Error;
132    fn try_from(v: Expr) -> Result<String, Error> {
133        match v {
134            Expr::Literal(v) => Ok(v),
135            Expr::Bool(v) => Ok(v.to_string()),
136            Expr::Float(v) => Ok(v.to_string()),
137            _ => Err(Error::ExprToBool(v)),
138        }
139    }
140}
141
142impl TryFrom<Expr> for GGeom {
143    type Error = Error;
144    fn try_from(v: Expr) -> Result<GGeom, Error> {
145        match v {
146            Expr::Geometry(v) => Ok(GGeom::try_from_wkt_str(&v.to_wkt().unwrap())
147                .expect("Failed to convert WKT to Geometry")),
148            Expr::BBox { ref bbox } => {
149                let minx: f64 = bbox[0].as_ref().clone().try_into()?;
150                let miny: f64 = bbox[1].as_ref().clone().try_into()?;
151                let maxx: f64;
152                let maxy: f64;
153
154                match bbox.len() {
155                    4 => {
156                        maxx = bbox[2].as_ref().clone().try_into()?;
157                        maxy = bbox[3].as_ref().clone().try_into()?;
158                    }
159                    6 => {
160                        maxx = bbox[3].as_ref().clone().try_into()?;
161                        maxy = bbox[4].as_ref().clone().try_into()?;
162                    }
163                    _ => return Err(Error::ExprToGeom(v.clone())),
164                };
165                let rec = Rect::new(coord! {x:minx, y:miny}, coord! {x:maxx,y:maxy});
166                Ok(rec.into())
167            }
168            _ => Err(Error::ExprToGeom(v)),
169        }
170    }
171}
172
173impl TryFrom<Expr> for HashSet<String> {
174    type Error = Error;
175    fn try_from(v: Expr) -> Result<HashSet<String>, Error> {
176        match v {
177            Expr::Array(v) => {
178                let mut h = HashSet::new();
179                for el in v {
180                    let _ = h.insert(el.to_text()?);
181                }
182                Ok(h)
183            }
184            _ => Err(Error::ExprToGeom(v)),
185        }
186    }
187}
188
189fn cmp_op<T: PartialEq + PartialOrd>(left: T, right: T, op: &str) -> Result<Expr, Error> {
190    let out = match op {
191        "=" => Ok(left == right),
192        "<=" => Ok(left <= right),
193        "<" => Ok(left < right),
194        ">=" => Ok(left >= right),
195        ">" => Ok(left > right),
196        "<>" => Ok(left != right),
197        _ => Err(Error::OpNotImplemented("Binary Bool")),
198    };
199    match out {
200        Ok(v) => Ok(Expr::Bool(v)),
201        _ => Err(Error::OperationError()),
202    }
203}
204
205fn arith_op(left: Expr, right: Expr, op: &str) -> Result<Expr, Error> {
206    let left = f64::try_from(left)?;
207    let right = f64::try_from(right)?;
208    let out = match op {
209        "+" => Ok(left + right),
210        "-" => Ok(left - right),
211        "*" => Ok(left * right),
212        "/" => Ok(left / right),
213        "%" => Ok(left % right),
214        "^" => Ok(left.powf(right)),
215        _ => Err(Error::OpNotImplemented("Arith")),
216    };
217    match out {
218        Ok(v) => Ok(Expr::Float(v)),
219        _ => Err(Error::OperationError()),
220    }
221}
222
223fn array_op(left: Expr, right: Expr, op: &str) -> Result<Expr, Error> {
224    let left: HashSet<String> = left.try_into()?;
225    let right: HashSet<String> = right.try_into()?;
226    let out = match op {
227        "a_equals" => Ok(left == right),
228        "a_contains" => Ok(left.is_superset(&right)),
229        "a_containedby" => Ok(left.is_subset(&right)),
230        "a_overlaps" => Ok(!left.is_disjoint(&right)),
231        _ => Err(Error::OpNotImplemented("Arith")),
232    };
233    match out {
234        Ok(v) => Ok(Expr::Bool(v)),
235        _ => Err(Error::OperationError()),
236    }
237}
238
239impl Expr {
240    /// Update this expression with values from the `properties` attribute of a JSON object
241    ///
242    ///  # Examples
243    ///
244    /// ```
245    /// use serde_json::{json, Value};
246    /// use cql2::Expr;
247    /// use std::str::FromStr;
248    ///
249    /// let item = json!({"properties":{"eo:cloud_cover":10, "datetime": "2020-01-01 00:00:00Z", "boolfield": true}});
250    ///
251    /// let fromexpr: Expr = Expr::from_str("boolfield = true").unwrap();
252    /// let reduced = fromexpr.reduce(Some(&item)).unwrap();
253    /// let toexpr: Expr = Expr::from_str("true").unwrap();
254    /// assert_eq!(reduced, toexpr);
255    ///
256    /// let fromexpr: Expr = Expr::from_str("\"eo:cloud_cover\" + 10").unwrap();
257    /// let reduced = fromexpr.reduce(Some(&item)).unwrap();
258    /// let toexpr: Expr = Expr::from_str("20").unwrap();
259    /// assert_eq!(reduced, toexpr);
260    ///
261    /// let fromexpr: Expr = Expr::from_str("(bork=1) and (bork=1) and (bork=1 and true)").unwrap();
262    /// let reduced = fromexpr.reduce(Some(&item)).unwrap();
263    /// let toexpr: Expr = Expr::from_str("bork=1").unwrap();
264    /// assert_eq!(reduced, toexpr);
265    ///
266    /// ```
267    pub fn reduce(self, j: Option<&Value>) -> Result<Expr, Error> {
268        match self {
269            Expr::Property { ref property } => {
270                if let Some(j) = j {
271                    if let Some(value) = j.dot_get::<Value>(property)? {
272                        Expr::try_from(value)
273                    } else if let Some(value) =
274                        j.dot_get::<Value>(&format!("properties.{}", property))?
275                    {
276                        Expr::try_from(value)
277                    } else {
278                        Ok(self)
279                    }
280                } else {
281                    Ok(self)
282                }
283            }
284            Expr::Interval { ref interval } => {
285                let start = interval[0].as_ref().clone().reduce(j)?;
286                let end = interval[1].as_ref().clone().reduce(j)?;
287                Ok(Expr::Interval {
288                    interval: vec![Box::new(start), Box::new(end)],
289                })
290            }
291            Expr::Operation { op, args } => {
292                let op = op.clone().to_lowercase();
293
294                let args: Vec<Box<Expr>> = args
295                    .into_iter()
296                    .map(|expr| expr.reduce(j).map(Box::new))
297                    .collect::<Result<_, _>>()?;
298
299                if op == "isnull" {
300                    match args[0].as_ref() {
301                        Expr::Null => Ok(Expr::Bool(true)),
302                        Expr::Property { property: _ } => Ok(Expr::Bool(true)),
303                        _ => Ok(Expr::Bool(false)),
304                    }
305                } else if args.iter().any(|arg| matches!(arg.as_ref(), Expr::Null)) {
306                    Ok(Expr::Bool(false))
307                } else if BOOLOPS.contains(&op.as_str()) {
308                    let curop = op.clone();
309                    let mut dedupargs: Vec<Box<Expr>> = vec![];
310                    let mut nestedargs: Vec<Box<Expr>> = vec![];
311                    for a in args {
312                        match *a {
313                            Expr::Operation { op, args } if op == curop => {
314                                nestedargs.append(&mut args.clone());
315                            }
316                            _ => {
317                                dedupargs.push(a.clone());
318                            }
319                        }
320                    }
321                    dedupargs.append(&mut nestedargs);
322                    dedupargs.sort_by(|a, b| a.partial_cmp(b).unwrap());
323                    dedupargs.dedup();
324
325                    let mut anytrue: bool = false;
326                    let mut anyfalse: bool = false;
327                    let mut anyexp: bool = false;
328
329                    for a in dedupargs.iter() {
330                        let b = bool::try_from(a.as_ref());
331                        match b {
332                            Ok(true) => {
333                                anytrue = true;
334                            }
335                            Ok(false) => {
336                                anyfalse = true;
337                            }
338                            _ => {
339                                anyexp = true;
340                            }
341                        }
342                    }
343                    if op == "and" && anytrue {
344                        dedupargs.retain(|x| !bool::try_from(x.as_ref()).unwrap_or(false));
345                    }
346                    if dedupargs.len() == 1 {
347                        Ok(dedupargs.pop().unwrap().as_ref().clone())
348                    } else if (op == "and" && anyfalse) || (op == "or" && !anytrue && !anyexp) {
349                        Ok(Expr::Bool(false))
350                    } else if (op == "and" && !anyfalse && !anyexp) || (op == "or" && anytrue) {
351                        Ok(Expr::Bool(true))
352                    } else {
353                        Ok(Expr::Operation {
354                            op,
355                            args: dedupargs.clone(),
356                        })
357                    }
358                } else if op == "not" {
359                    match args[0].deref() {
360                        Expr::Bool(v) => Ok(Expr::Bool(!v)),
361                        _ => Ok(Expr::Operation { op, args }),
362                    }
363                } else if op == "casei" {
364                    match args[0].as_ref() {
365                        Expr::Literal(v) => Ok(Expr::Literal(v.to_lowercase())),
366                        _ => Ok(Expr::Operation { op, args }),
367                    }
368                } else if op == "accenti" {
369                    match args[0].as_ref() {
370                        Expr::Literal(v) => Ok(Expr::Literal(unaccent(v))),
371                        _ => Ok(Expr::Operation { op, args }),
372                    }
373                } else if op == "between" {
374                    Ok(Expr::Bool(args[0] >= args[1] && args[0] <= args[2]))
375                } else if args.len() != 2 {
376                    Ok(Expr::Operation { op, args })
377                } else {
378                    // Two-arg operations
379                    let mut left = args[0].deref().clone();
380                    let mut right = args[1].deref().clone();
381
382                    // If left is Date/Timestamp and right is Literal, convert right
383                    match (&left, &right) {
384                        (Expr::Date { .. }, Expr::Literal(ref v)) => {
385                            right = Expr::Date {
386                                date: Box::new(Expr::Literal(v.clone())),
387                            };
388                        }
389                        (Expr::Timestamp { .. }, Expr::Literal(ref v)) => {
390                            right = Expr::Timestamp {
391                                timestamp: Box::new(Expr::Literal(v.clone())),
392                            };
393                        }
394                        (Expr::Literal(ref v), Expr::Date { .. }) => {
395                            left = Expr::Date {
396                                date: Box::new(Expr::Literal(v.clone())),
397                            };
398                        }
399                        (Expr::Literal(ref v), Expr::Timestamp { .. }) => {
400                            left = Expr::Timestamp {
401                                timestamp: Box::new(Expr::Literal(v.clone())),
402                            };
403                        }
404                        _ => {}
405                    }
406
407                    if TEMPORALOPS.contains(&op.as_str()) {
408                        Ok(temporal_op(left, right, &op)
409                            .unwrap_or_else(|_| Expr::Operation { op, args }))
410                    // Date or Timestamp comparison: convert to jiff Timestamp for correct ordering
411                    } else if (matches!(left, Expr::Date { .. } | Expr::Timestamp { .. })
412                        && matches!(right, Expr::Date { .. } | Expr::Timestamp { .. })
413                        && (EQOPS.contains(&op.as_str()) || CMPOPS.contains(&op.as_str())))
414                    {
415                        // convert both operands to DateRange and compare using PartialOrd/PartialEq
416                        let l_dr = crate::temporal::DateRange::try_from(left.clone())?;
417                        let r_dr = crate::temporal::DateRange::try_from(right.clone())?;
418                        let cmp = match op.as_str() {
419                            "=" => l_dr == r_dr,
420                            "<=" => l_dr <= r_dr,
421                            "<" => l_dr < r_dr,
422                            ">=" => l_dr >= r_dr,
423                            ">" => l_dr > r_dr,
424                            "<>" => l_dr != r_dr,
425                            _ => unreachable!(),
426                        };
427                        Ok(Expr::Bool(cmp))
428                    } else if std::mem::discriminant(&left) == std::mem::discriminant(&right) {
429                        if SPATIALOPS.contains(&op.as_str()) {
430                            Ok(spatial_op(left, right, &op)
431                                .unwrap_or_else(|_| Expr::Operation { op, args }))
432                        } else if ARITHOPS.contains(&op.as_str()) {
433                            Ok(arith_op(left, right, &op)
434                                .unwrap_or_else(|_| Expr::Operation { op, args }))
435                        } else if EQOPS.contains(&op.as_str()) || CMPOPS.contains(&op.as_str()) {
436                            Ok(cmp_op(left, right, &op)
437                                .unwrap_or_else(|_| Expr::Operation { op, args }))
438                        } else if ARRAYOPS.contains(&op.as_str()) {
439                            Ok(array_op(left, right, &op)
440                                .unwrap_or_else(|_| Expr::Operation { op, args }))
441                        } else if op == "like" {
442                            let l: String = left.try_into()?;
443                            let r: String = right.try_into()?;
444                            let m: bool = Like::<true>::like(l.as_str(), r.as_str())?;
445                            Ok(Expr::Bool(m))
446                        } else {
447                            Ok(Expr::Operation { op, args })
448                        }
449                    } else if op == "in" {
450                        let l: String = left.to_text()?;
451                        let r: HashSet<String> = right.try_into()?;
452                        let isin: bool = r.contains(&l);
453                        Ok(Expr::Bool(isin))
454                    } else {
455                        Ok(Expr::Operation { op, args })
456                    }
457                }
458            }
459            _ => Ok(self),
460        }
461    }
462
463    /// Run CQL against a JSON Value
464    ///
465    ///  # Examples
466    ///
467    /// ```
468    /// use serde_json::{json, Value};
469    /// use cql2::Expr;
470    /// let item = json!({"properties":{"eo:cloud_cover":10, "datetime": "2020-01-01 00:00:00Z", "boolfield": true}});
471    ///
472    /// let mut expr: Expr = "boolfield and 1 + 2 = 3".parse().unwrap();
473    /// assert_eq!(true, expr.matches(Some(&item)).unwrap());
474    ///
475    /// let mut expr: Expr = "eo:cloud_cover <= 9".parse().unwrap();
476    /// assert_eq!(false, expr.matches(Some(&item)).unwrap());
477    /// ```
478    pub fn matches(self, j: Option<&Value>) -> Result<bool, Error> {
479        let reduced = self.reduce(j)?;
480
481        match reduced {
482            Expr::Bool(v) => Ok(v),
483            _ => Err(Error::NonReduced()),
484        }
485    }
486
487    /// Returns True if the expression evaluates to true and false if either the expression evaluates to false or does not fully reduce to a boolean.
488    pub fn is_true(self) -> bool {
489        matches!(self, Expr::Bool(true))
490    }
491
492    /// Filters an iterable of JSON values based on this expression.
493    ///
494    /// # Examples
495    ///
496    /// ```
497    /// use cql2::Expr;
498    /// use serde_json::json;
499    ///
500    /// let expr: Expr = "eo:cloud_cover < 20".parse().unwrap();
501    /// let items = vec![
502    ///     json!({"properties": {"eo:cloud_cover": 10}}),
503    ///     json!({"properties": {"eo:cloud_cover": 25}}),
504    ///     json!({"properties": {"eo:cloud_cover": 15}})
505    /// ];
506    /// let filtered = expr.filter(&items).unwrap();
507    /// assert_eq!(filtered.len(), 2);
508    /// assert_eq!(filtered[0]["properties"]["eo:cloud_cover"], 10);
509    /// assert_eq!(filtered[1]["properties"]["eo:cloud_cover"], 15);
510    /// ```
511    pub fn filter<'a, I>(&self, items: I) -> Result<Vec<&'a Value>, Error>
512    where
513        I: IntoIterator<Item = &'a Value>,
514    {
515        let mut filtered = Vec::new();
516        for item in items {
517            let e = self.clone().reduce(Some(item))?;
518            if e.is_true() {
519                filtered.push(item)
520            }
521        }
522        Ok(filtered)
523    }
524
525    /// Converts this expression to CQL2 text.
526    ///
527    /// # Examples
528    ///
529    /// ```
530    /// use cql2::Expr;
531    ///
532    /// let expr = Expr::Bool(true);
533    /// assert_eq!(expr.to_text().unwrap(), "true");
534    /// ```
535    pub fn to_text(&self) -> Result<String, Error> {
536        macro_rules! check_len {
537            ($name:expr, $args:expr, $len:expr, $text:expr) => {
538                if $args.len() == $len {
539                    Ok($text)
540                } else {
541                    Err(Error::InvalidNumberOfArguments {
542                        name: $name.to_string(),
543                        actual: $args.len(),
544                        expected: $len,
545                    })
546                }
547            };
548        }
549
550        match self {
551            Expr::Bool(v) => Ok(v.to_string()),
552            Expr::Float(v) => Ok(v.to_string()),
553            Expr::Literal(v) => Ok(quote_literal(v).to_string()),
554            Expr::Property { property } => Ok(quote_identifier(property).to_string()),
555            Expr::Null => Ok("NULL".to_string()),
556            Expr::Interval { interval } => {
557                check_len!(
558                    "interval",
559                    interval,
560                    2,
561                    format!(
562                        "INTERVAL({},{})",
563                        interval[0].to_text()?,
564                        interval[1].to_text()?
565                    )
566                )
567            }
568            Expr::Date { date } => Ok(format!("DATE({})", date.to_text()?)),
569            Expr::Timestamp { timestamp } => Ok(format!("TIMESTAMP({})", timestamp.to_text()?)),
570            Expr::Geometry(v) => v.to_wkt(),
571            Expr::Array(v) => {
572                let array_els: Vec<String> =
573                    v.iter().map(|a| a.to_text()).collect::<Result<_, _>>()?;
574                Ok(format!("({})", array_els.join(", ")))
575            }
576            Expr::Operation { op, args } => {
577                let a: Vec<String> = args.iter().map(|x| x.to_text()).collect::<Result<_, _>>()?;
578                match op.as_str() {
579                    "and" => Ok(format!("({})", a.join(" AND "))),
580                    "or" => Ok(format!("({})", a.join(" OR "))),
581                    "like" => Ok(format!("({} LIKE {})", a[0], a[1])),
582                    "in" => Ok(format!("({} IN {})", a[0], a[1])),
583                    "between" => {
584                        check_len!(
585                            "between",
586                            a,
587                            3,
588                            format!("({} BETWEEN {} AND {})", a[0], a[1], a[2])
589                        )
590                    }
591                    "not" => {
592                        check_len!("not", a, 1, format!("(NOT {})", a[0]))
593                    }
594                    "isNull" => {
595                        check_len!("is null", a, 1, format!("({} IS NULL)", a[0]))
596                    }
597                    "+" | "-" | "*" | "/" | "%" => {
598                        let paddedop = format!(" {} ", op);
599                        Ok(a.join(&paddedop).to_string())
600                    }
601                    "^" | "=" | "<=" | "<" | "<>" | ">" | ">=" => {
602                        check_len!(op, a, 2, format!("({} {} {})", a[0], op, a[1]))
603                    }
604                    _ => Ok(format!("{}({})", quote_identifier(op), a.join(", "))),
605                }
606            }
607            Expr::BBox { bbox } => {
608                let array_els: Vec<String> =
609                    bbox.iter().map(|a| a.to_text()).collect::<Result<_, _>>()?;
610                Ok(format!("BBOX({})", array_els.join(", ")))
611            }
612        }
613    }
614
615    /// Converts this expression to a JSON string.
616    ///
617    /// # Examples
618    ///
619    /// ```
620    /// use cql2::Expr;
621    ///
622    /// let expr = Expr::Bool(true);
623    /// let s = expr.to_json().unwrap();
624    /// ```
625    pub fn to_json(&self) -> Result<String, Error> {
626        serde_json::to_string(&self).map_err(Error::from)
627    }
628
629    /// Converts this expression to a pretty JSON string.
630    ///
631    /// # Examples
632    ///
633    /// ```
634    /// use cql2::Expr;
635    ///
636    /// let expr = Expr::Bool(true);
637    /// let s = expr.to_json_pretty().unwrap();
638    /// ```
639    pub fn to_json_pretty(&self) -> Result<String, Error> {
640        serde_json::to_string_pretty(&self).map_err(Error::from)
641    }
642
643    /// Converts this expression to a [serde_json::Value].
644    ///
645    /// # Examples
646    ///
647    /// ```
648    /// use cql2::Expr;
649    ///
650    /// let expr = Expr::Bool(true);
651    /// let value = expr.to_value().unwrap();
652    /// ```
653    pub fn to_value(&self) -> Result<Value, Error> {
654        serde_json::to_value(self).map_err(Error::from)
655    }
656
657    /// Returns true if this expression is valid CQL2.
658    ///
659    /// For detailed error reporting, use [Validator::validate] in conjunction with [Expr::to_value].
660    ///
661    /// # Examples
662    ///
663    /// ```
664    /// use cql2::Expr;
665    ///
666    /// let expr = Expr::Bool(true);
667    /// assert!(expr.is_valid());
668    /// ```
669    ///
670    /// # Panics
671    ///
672    /// Panics if the default validator can't be created.
673    pub fn is_valid(&self) -> bool {
674        let value = serde_json::to_value(self);
675        match &value {
676            Ok(value) => {
677                let validator = Validator::new().expect("Could not create default validator");
678                validator.is_valid(value)
679            }
680            _ => false,
681        }
682    }
683}
684
685impl FromStr for Expr {
686    type Err = Error;
687
688    fn from_str(s: &str) -> Result<Expr, Error> {
689        if s.starts_with('{') {
690            crate::parse_json(s).map_err(Error::from)
691        } else {
692            crate::parse_text(s)
693        }
694    }
695}
696
697impl Add for Expr {
698    type Output = Expr;
699
700    ///
701    /// Combines two expressions with the `+` operator.
702    ///
703    /// # Examples
704    ///
705    /// ```
706    /// use cql2::Expr;
707    /// use std::ops::Add;
708    ///
709    /// let expr1 = Expr::Bool(true);
710    /// let expr2 = Expr::Bool(false);
711    /// let expected_expr: Expr = "true and false".parse().unwrap();
712    /// assert_eq!(expr1 + expr2, expected_expr);
713    /// ```
714    ///
715    /// ```
716    /// use cql2::Expr;
717    /// use std::ops::Add;
718    ///
719    /// let expr1 = Expr::Bool(true);
720    /// let expr2 = Expr::Bool(false);
721    /// let expected_expr: Expr = "true and false".parse().unwrap();
722    /// assert_eq!(expr1.add(expr2), expected_expr);
723    /// ```
724    fn add(self, other: Expr) -> Expr {
725        Expr::Operation {
726            op: "and".to_string(),
727            args: vec![Box::new(self), Box::new(other)],
728        }
729    }
730}
731#[cfg(test)]
732mod tests {
733    use super::Expr;
734
735    #[test]
736    fn keep_z() {
737        let point: Expr = "POINT Z(-105.1019 40.1672 4981)".parse().unwrap();
738        assert_eq!("POINT Z(-105.1019 40.1672 4981)", point.to_text().unwrap());
739    }
740
741    #[test]
742    fn implicit_z() {
743        let point: Expr = "POINT (-105.1019 40.1672 4981)".parse().unwrap();
744        assert_eq!("POINT Z(-105.1019 40.1672 4981)", point.to_text().unwrap());
745    }
746
747    #[test]
748    fn keep_m() {
749        let point: Expr = "POINT M(-105.1019 40.1672 42)".parse().unwrap();
750        assert_eq!("POINT M(-105.1019 40.1672 42)", point.to_text().unwrap());
751    }
752
753    #[test]
754    fn keep_zm() {
755        let point: Expr = "POINT ZM(-105.1019 40.1672 4981 42)".parse().unwrap();
756        assert_eq!(
757            "POINT ZM(-105.1019 40.1672 4981 42)",
758            point.to_text().unwrap()
759        );
760    }
761
762    #[test]
763    fn keep_one_element_lists() {
764        // https://github.com/developmentseed/cql2-rs/issues/91
765        let expr: Expr = "ogc_fid IN ('1')".parse().unwrap();
766        assert_eq!(expr.to_text().unwrap(), "(ogc_fid IN ('1'))");
767    }
768}