6 releases
Uses new Rust 2024
| 0.1.6 | Jul 15, 2026 |
|---|---|
| 0.1.5 | Mar 23, 2026 |
| 0.1.3 | Jul 15, 2025 |
| 0.1.1 | Mar 26, 2025 |
| 0.1.0 | Sep 11, 2024 |
#375 in Caching
305KB
7.5K
SLoC
中文 | English
QCL
a simple language that allows you to check the eval result of a query.
Intro
It's designed to be used in ACL (Access Control List) systems, where you need to check if a user has access to a resource.
Expression language
- Literals: string, int, float, bool, nil, list, and map
- Nested lists/maps, plus trailing commas
- Comments:
//and nested/* ... */ - Context lookup with
@path, including quoted field names, integer indexes, negative indexes, computed path segments, and nested@access - Postfix access on any primary expression with
.field/.index - Operators:
!, unary-,+ - * / %,== != < > <= >=,in,&&,||,??, and?: insupports substring, list membership/subset, and map key lookup- Missing access resolves to
nil - Bare identifiers are string values outside
@, and field names inside@ - Default arithmetic keeps exact integer division when possible and promotes to float when needed
- Optional advanced arithmetic extends
+/-to strings, lists, and maps
Example
(@record.published || @record.owner == @req.user.id) // Normal case
|| // Or operator
(@req.user.role == 'admin' || @req.user.id in @record.granted) // Special case
Let's break it down:
@req.user.role == 'admin': Check if the user has the role ofadmin.@req.user.id in @record.granted: Check if the user's id is in thegrantedlist of the record.@record.published: Check if the record is published.@record.owner == @req.user.id: Check if the record's owner is the user.
The above example is a simple ACL system that checks if the user has access to a record.
More language details can be found in LANG.md.
Capabilities
Input formats
- JSON is supported in the default build
- YAML and TOML are available as optional input backends
- When a backend is compiled in, the CLI can force it with
--json,--yaml, or--toml
CLI
- Reads the context from
stdinand the expression from argv --check/-cexits with code0for truthy results and1for falsy results--astprints the parsed AST instead of evaluating it--version/-Vprints the version--help/-hprints usage
Rust API
- Parse with
Expr::try_fromor cachedExpr::parse_cached_arc - Evaluate with
Expr::eval - Inspect required context names with
requested_ctx - Check whether an expression is context-independent with
is_ctx_independent
Bindings
- WASM via
wasm-bindgen(docs) - C shared library (docs)
- Python module via PyO3 (docs)
- The library also supports
no_std(withalloc) whenstdis disabled
Usage
Integration
// Parse expr
let expr = "@req.user.name in 'foobar' && @files.0.published == true";
let expr = Expr::try_from(expr)?;
// Construct context
let ctx_names = expr.requested_ctx(); // ["req", "files"]
// You can construct the context indeed, but we use json! for simplicity
let ctx = json!({
"req": {
"user": "foo"
},
"files": [
{
"name": "file1",
"published": true
}
]
});
// Eval
let result = expr.eval(ctx.into())?; // Val::Bool(true)
match result {
Val::Bool(b) => {
assert!(b);
}
_ => {
panic!("unexpected result");
}
}
CLI
echo '{"req": {"user": {"role": "admin"}}}' | cargo run -- '@req.user.role == "admin"'
# Check mode (exit code 0/1)
echo '{"x": 1}' | cargo run -- --check '@x == 1' && echo ok
# Dump AST
echo '{}' | cargo run -- --ast '1 + 2'
Build Bindings
make wasm # WASM package → pkg/
make ffi # C shared lib → target/release/libqcl.so
make python # Python module → installed into active venv
See docs/ for detailed binding documentation.
Ports
License
Apache-2.0 lollipopkit
Dependencies
~1.6–3.5MB
~73K SLoC