restqs 0.1.0

REST Query Syntax parser for safe REST API filtering plans.
Documentation
# RestQS

RestQS parses REST Query Syntax, or RQS, into a typed query plan for REST
APIs. The core crate does not generate SQL. It accepts query-string-like input,
checks every requested field against an explicit catalog, casts values, and
returns a database-neutral `RqsQuery`.

The crate suits services that treat raw request input as boundary data. A web
handler reads the query string. RestQS parses it into a plan. A repository
adapter translates the plan into database-specific query parts. Domain code
receives typed data and does not parse query strings.

```mermaid
flowchart LR
  request["HTTP request query"] --> handler["Application handler"]
  handler --> catalog["FieldCatalog"]
  handler --> parser["RestQS parser"]
  catalog --> parser
  parser --> plan["RqsQuery plan"]
  plan --> adapter["Repository adapter"]
  adapter --> database["Database library"]
```

## Install

Use the core parser with no runtime dependencies:

```toml
[dependencies]
restqs = "0.1.0"
```

Turn on the SQLx-oriented adapter contract with the `sqlx` feature:

```toml
[dependencies]
restqs = { version = "0.1.0", features = ["sqlx"] }
```

The `sqlx` feature exposes fragment generation for SQLx-style repositories.
It returns SQL text fragments and typed bind values. It does not open a
connection, run a query, or own transaction logic.

## Quick Start

Start with a catalog. The catalog maps public query names to trusted database
columns and value types. This catalog is the main safety boundary.

```rust
use restqs::{FieldCatalog, FilterOp, parse};

let catalog = FieldCatalog::new()
    .allow_integer("age", "users.age")?
    .allow_text("status", "users.status")?;

let query = parse("age>=18&status=in(active,pending)", &catalog)?;

assert_eq!(query.filters()[0].op(), FilterOp::Gte);
# Ok::<(), restqs::RqsError>(())
```

The parser returns a plan. It never emits a finished SQL statement.

```rust
use restqs::{FieldCatalog, RqsValue, parse};

let catalog = FieldCatalog::new().allow_text("status", "users.status")?;
let query = parse("status=active", &catalog)?;
let value = query.filters()[0].value();

assert_eq!(value, Some(&RqsValue::Text("active".to_owned())));
# Ok::<(), restqs::RqsError>(())
```

## Syntax Overview

RQS uses ordinary query-string parameters. Each parameter either creates a
filter or configures sorting, projection, or pagination.

| RQS | Plan meaning |
| --- | --- |
| `field=value` | Equals |
| `field!=value` | Not equals |
| `field>value` | Greater than |
| `field>=value` | Greater than or equal |
| `field<value` | Less than |
| `field<=value` | Less than or equal |
| `field` | Field exists |
| `!field` | Field does not exist |
| `field=in(a,b)` | Value appears in a list |
| `field!=in(a,b)` | Value does not appear in a list |
| `sort=-created_at,name` | Descending and ascending sort terms |
| `fields=id,status` | Projection fields |
| `skip=20` | Offset |
| `limit=50` | Maximum row count |

The parser supports text, integer, float, boolean, date, date-time, UUID, null,
and list values. Typed cast wrappers clarify intent for ambiguous values:
`str(value)`, `int(18)`, `float(1.5)`, `bool(true)`, `date(2026-06-06)`,
`datetime(2026-06-06T12:30:00Z)`, and
`uuid(550e8400-e29b-41d4-a716-446655440000)`.

Text search is not part of this release. Regex parsing exists, but it stays
off by default. A field must permit regex values, and the adapter must allow
regex SQL generation.

## Safe Defaults

RestQS treats the query string as untrusted input. The parser never accepts a
user field name as a database column. Every field must exist in
`FieldCatalog`. The catalog validates the trusted column identifier at
creation time.

The default limits reduce accidental high-cost queries:

| Limit | Default |
| --- | --- |
| Raw query length | 8 KiB |
| Parameter count | 128 |
| Single value length | 2 KiB |
| List item count | 100 |
| Maximum `limit` value | 100 |

Error messages name failure classes and field names. They do not echo the raw
query string.

## SQLx-Oriented Adapter

The SQLx adapter turns a parsed plan into query fragments. The caller owns the
base SQL, bind calls, connection, transaction, and result mapping.

```rust
use restqs::{
    FieldCatalog, parse,
    adapters::sqlx::{SqlDialect, SqlxAdapter},
};

let catalog = FieldCatalog::new()
    .allow_integer("age", "users.age")?
    .allow_text("status", "users.status")?;
let query = parse("age>=18&status=active", &catalog)?;
let parts = SqlxAdapter::new(SqlDialect::Postgres).build(&query)?;

assert_eq!(
    parts.where_clause,
    Some("\"users\".\"age\" >= $1 AND \"users\".\"status\" = $2".to_owned())
);
# Ok::<(), restqs::RqsError>(())
```

The adapter quotes allowlisted columns and returns bind values in placeholder
order. It does not concatenate user values into SQL text.

```mermaid
sequenceDiagram
  participant H as Handler
  participant R as RestQS
  participant A as Adapter
  participant Q as SQLx query builder
  H->>R: parse raw query with catalog
  R-->>H: RqsQuery
  H->>A: build fragments
  A-->>H: where, order, limit, binds
  H->>Q: assemble SQL and bind values
```

## Documented SQLx Examples

The crate does not depend on SQLx, Tokio, PostgreSQL, or SQLite. Application
code adds those crates in its own manifest. The integration guide shows
documentation-only PostgreSQL and SQLite snippets that use dynamic SQL through
`sqlx::query`.

For PostgreSQL applications:

```toml
[dependencies]
restqs = { version = "0.1.0", features = ["sqlx"] }
sqlx = { version = "0.8", default-features = false, features = ["postgres", "runtime-tokio"] }
```

For SQLite applications:

```toml
[dependencies]
restqs = { version = "0.1.0", features = ["sqlx"] }
sqlx = { version = "0.8", default-features = false, features = ["sqlite", "runtime-tokio"] }
```

The documented examples keep the security boundary visible. SQL text contains
trusted catalog columns, fixed SQL keywords, and placeholders. User values
enter the database only through `.bind(...)`.

## Architecture

Each module owns one concern. `parameter` decodes query-string text. `parser`
coordinates the plan build. `value` casts scalar and list values. `catalog`
owns public field authorization and trusted column metadata. `filter`, `sort`,
`projection`, and `pagination` compute plan pieces. `adapters` translate
completed plans.

Functions either coordinate work or compute a value. Tests follow the same
rule: each test function checks one fact.

## Documentation

- [API Guide]docs/api-guide.md
- [Architecture]docs/architecture.md
- [Integration Guide]docs/integrations.md
- [Security Model]docs/security-model.md
- [Testing Guide]docs/testing-guide.md
- [Publishing]docs/publishing.md
- [Release Checklist]docs/release-checklist.md
- [Open Source Practices]docs/open-source.md
- [Support]SUPPORT.md
- [Contributing]CONTRIBUTING.md
- [Security Policy]SECURITY.md
- [Code of Conduct]CODE_OF_CONDUCT.md
- [Changelog]CHANGELOG.md

## Local Commands

```sh
make setup
make test
make coverage
make verify
make package-list
make package
```

`make test` runs all in-memory tests. `make coverage` fails on any uncovered
source line. `make verify` checks formatting, type checking, Clippy, tests,
doc tests, and docs.rs-style docs.

## License

Licensed under the MIT License. See [LICENSE](LICENSE).