rest-sql 0.3.0

RSQL/FIQL filter parser and validator for REST APIs — parse, validate, compile to native DB queries
Documentation
use std::ops::{BitAnd, BitOr};

/// Raw AST — unvalidated, produced directly by the parser.
#[derive(Debug, Clone, PartialEq)]
pub enum Ast {
    And(Vec<Ast>),
    Or(Vec<Ast>),
    Constraint(Constraint),
}

impl Ast {
    /// Combines `nodes` with AND. Panics if `nodes` is empty; unwraps if single element.
    pub fn and(nodes: impl IntoIterator<Item = Ast>) -> Ast {
        let v: Vec<_> = nodes.into_iter().collect();
        assert!(!v.is_empty(), "Ast::and requires at least one node");
        if v.len() == 1 {
            v.into_iter().next().unwrap()
        } else {
            Ast::And(v)
        }
    }

    /// Combines `nodes` with OR. Panics if `nodes` is empty; unwraps if single element.
    pub fn or(nodes: impl IntoIterator<Item = Ast>) -> Ast {
        let v: Vec<_> = nodes.into_iter().collect();
        assert!(!v.is_empty(), "Ast::or requires at least one node");
        if v.len() == 1 {
            v.into_iter().next().unwrap()
        } else {
            Ast::Or(v)
        }
    }

    /// Like `and`, but returns `None` for an empty iterator.
    pub fn try_and(nodes: impl IntoIterator<Item = Ast>) -> Option<Ast> {
        let v: Vec<_> = nodes.into_iter().collect();
        if v.is_empty() {
            None
        } else if v.len() == 1 {
            v.into_iter().next()
        } else {
            Some(Ast::And(v))
        }
    }

    /// Like `try_or`, but returns `None` for an empty iterator.
    pub fn try_or(nodes: impl IntoIterator<Item = Ast>) -> Option<Ast> {
        let v: Vec<_> = nodes.into_iter().collect();
        if v.is_empty() {
            None
        } else if v.len() == 1 {
            v.into_iter().next()
        } else {
            Some(Ast::Or(v))
        }
    }

    /// Like `try_and`, but accepts optional nodes — `None` entries are silently skipped.
    ///
    /// Useful when building a filter from a mix of optional query parameters:
    ///
    /// ```rust
    /// use rest_sql::{Ast, filter::{eq, gte}};
    ///
    /// let ast = Ast::try_and_opts([
    ///     Some(eq("genre", "Drama")),
    ///     None,                        // absent query param — skipped
    ///     Some(gte("year", 2000i64)),
    /// ]);
    /// // equivalent to: Some(eq("genre", "Drama") & gte("year", 2000))
    /// ```
    pub fn try_and_opts(nodes: impl IntoIterator<Item = Option<Ast>>) -> Option<Ast> {
        Self::try_and(nodes.into_iter().flatten())
    }

    /// Like `try_or`, but accepts optional nodes — `None` entries are silently skipped.
    pub fn try_or_opts(nodes: impl IntoIterator<Item = Option<Ast>>) -> Option<Ast> {
        Self::try_or(nodes.into_iter().flatten())
    }
}

/// `And & And` → flat `And`; `And & other` → appends to the `And` list; otherwise wraps in a new `And`.
impl BitAnd for Ast {
    type Output = Ast;
    fn bitand(self, rhs: Ast) -> Ast {
        match (self, rhs) {
            (Ast::And(mut a), Ast::And(b)) => {
                a.extend(b);
                Ast::And(a)
            }
            (Ast::And(mut a), rhs) => {
                a.push(rhs);
                Ast::And(a)
            }
            (lhs, rhs) => Ast::And(vec![lhs, rhs]),
        }
    }
}

/// `Or | Or` → flat `Or`; `Or | other` → appends to the `Or` list; otherwise wraps in a new `Or`.
impl BitOr for Ast {
    type Output = Ast;
    fn bitor(self, rhs: Ast) -> Ast {
        match (self, rhs) {
            (Ast::Or(mut a), Ast::Or(b)) => {
                a.extend(b);
                Ast::Or(a)
            }
            (Ast::Or(mut a), rhs) => {
                a.push(rhs);
                Ast::Or(a)
            }
            (lhs, rhs) => Ast::Or(vec![lhs, rhs]),
        }
    }
}

#[derive(Debug, Clone, PartialEq)]
pub struct Constraint {
    pub field: String,
    pub operator: Operator,
    pub value: Value,
}

#[derive(Debug, Clone, PartialEq)]
pub enum Operator {
    Eq,
    Neq,
    Lt,
    Lte,
    Gt,
    Gte,
    In,
    Out,
    Like,
    Ilike,
    Between,
    Null,
    NotNull,
}

#[derive(Debug, Clone, PartialEq)]
pub enum Value {
    Null,
    Bool(bool),
    Int(i64),
    Float(f64),
    String(String),
    Date(String),
    DateTime(String),
    List(Vec<Value>),
}

impl From<bool> for Value {
    fn from(v: bool) -> Self {
        Value::Bool(v)
    }
}

impl From<i32> for Value {
    fn from(v: i32) -> Self {
        Value::Int(v as i64)
    }
}

impl From<i64> for Value {
    fn from(v: i64) -> Self {
        Value::Int(v)
    }
}

impl From<f32> for Value {
    fn from(v: f32) -> Self {
        Value::Float(v as f64)
    }
}

impl From<f64> for Value {
    fn from(v: f64) -> Self {
        Value::Float(v)
    }
}

impl From<&str> for Value {
    fn from(v: &str) -> Self {
        Value::String(v.to_owned())
    }
}

impl From<String> for Value {
    fn from(v: String) -> Self {
        Value::String(v)
    }
}