rest-sql 0.2.0

RSQL/FIQL filter parser and validator for REST APIs — parse, validate, compile to native DB queries
Documentation

rest-sql

Parse and validate RSQL / FIQL filter queries into a typed AST, or build filters programmatically — then hand the result to a backend driver.

name=like=Chris*;year=gt=1990;genre=in=(Drama,Thriller)

This crate is pure and WASM-compatible — it has no I/O and no backend dependency. For compiling to SQL/MongoDB/SurrealQL, see rest-sql-drivers.


Parse a query string

use rest_sql::RestSql;

let rsql = RestSql::new("title=like=Godfather*;year=gt=1970")?;

Build programmatically

Use the filter module when the query comes from code rather than a string:

use rest_sql::{RestSql, filter::{eq, gte, ilike, in_}};
use rest_sql::Ast;

// Simple composition
let ast = ilike("title", "godfather*") & gte("year", 1970i64);
let rsql = RestSql::from_ast(ast)?;

// Conditional construction — fields are optional at runtime
let nodes: Vec<Ast> = [
    title_filter.map(|p| ilike("title", p)),
    min_year.map(|y| gte("year", y)),
    genres.map(|g| in_("genre", g)),
]
.into_iter()
.flatten()
.collect();

let rsql = RestSql::from_ast(Ast::try_and(nodes).unwrap_or(/* fallback */))?;

from_ast runs the same validation as new — operator/value compatibility, =between= arity, etc.

BitAnd / BitOr combinators

& and | operators flatten adjacent nodes of the same type:

let a = eq("status", "active") & gte("age", 18i64);  // And([eq, gte])
let b = eq("role", "admin")   & eq("verified", true); // And([eq, eq])
let merged = a & b; // And([eq, gte, eq, eq]) — flat, not nested

Operators

Short Long Meaning
== =eq= Equal
!= =neq= Not equal
< =lt= Less than
<= =le= Less than or equal
> =gt= Greater than
>= =ge= Greater than or equal
=in= In list
=out= Not in list
=between= Range (inclusive)
=null= Null / absent
=notnull= Not null / present
=like= Pattern (* = any chars, _ = one char)
=ilike= Case-insensitive pattern

Logical connectors: ; = AND, , = OR, (...) = grouping.

Value types recognized at parse time: String, Integer, Float, Boolean, Null, Date (YYYY-MM-DD), DateTime (YYYY-MM-DDTHH:MM:SSZ).


Field allowlisting

Reject queries that reference undeclared fields — important when filters come from user input:

// Explicit list
let rsql = RestSql::new_for_fields("title==Inception;secret==x", &["title", "year"])?;
// → Err: field 'secret' is not allowed

// Derive from a serde struct (feature `serde`)
#[derive(serde::Deserialize)]
struct Movie { title: String, year: i32, rating: f64 }

let rsql = RestSql::new_for::<Movie>("title==Inception;year=gt=2000")?;

Field mapping

FieldMapper renames logical field names before the AST reaches a driver. Useful for JSONB columns, table aliases, or any naming mismatch:

use rest_sql::{FieldMapper, RestSql};
use std::borrow::Cow;

struct PrefixMapper(&'static str);
impl FieldMapper for PrefixMapper {
    fn map<'a>(&self, field: &'a str) -> Cow<'a, str> {
        Cow::Owned(format!("{}.{}", self.0, field))
    }
}

let rsql = RestSql::new("title==Inception")?.map_fields(&PrefixMapper("f"));
// AST field is now "f.title"

Consumer pattern

The idiomatic way to convert your query structs into a RestSql:

use rest_sql::{RestSql, Ast, filter::{ilike, gte, lte, in_}};

struct FilmQuery {
    title: Option<String>,
    min_year: Option<i64>,
    max_year: Option<i64>,
    genres: Option<Vec<String>>,
}

impl TryFrom<FilmQuery> for RestSql {
    type Error = rest_sql::RestSqlError;

    fn try_from(q: FilmQuery) -> Result<Self, Self::Error> {
        let nodes: Vec<Ast> = [
            q.title.map(|t| ilike("title", t)),
            q.min_year.map(|y| gte("year", y)),
            q.max_year.map(|y| lte("year", y)),
            q.genres.map(|g| in_("genre", g)),
        ]
        .into_iter()
        .flatten()
        .collect();

        RestSql::from_ast(Ast::try_and(nodes).unwrap_or(Ast::and([gte("year", 0i64)])))
    }
}

Feature flags

Feature Default Description
serde off Enables RestSql::new_for::<T>() — field allowlist from #[derive(Deserialize)]

License

MIT — see LICENSE.

Full documentation and examples: github.com/dohrm/rest-sql.