JsonLogic rules
Business rules as JsonLogic, kept as JSON and shared with the browser: evaluated against data, used as filters and as decision tables.
Version 1.0.0 · 4 functions · 1 crate
Functions
What use json_logic.qwer gives a program, with the types Quillo sees. To take only some, or under other names: use jsonlogic_apply, jsonlogic_test from json_logic (Quillo 0.6).
jsonlogic_apply(rule: any, data: any) → anyor an errorThe value of a rule for data: { "if": [{ ">": [{ "var": "cart.total" }, 100] }, "free", "paid"] } with { cart: { total: 140 } } is "free". Operators: var (dots go into maps, missing), == != < <= > >=, and or ! !!, if, + - * / % min max, in, cat, substr, map filter reduce all some none merge.
jsonlogic_test(rule: any, data: any) → boolor an errorWhether a rule holds for data, by JsonLogic's truthiness (false, null, 0, "" and [] are false).
jsonlogic_filter(rule: any, items: list) → listor an errorThe items a rule holds for, in order: a saved search, a segment of customers, offers a user qualifies for. At most 100,000 items.
jsonlogic_decide(rules: list, data: any, otherwise: any) → anyor an errorA decision table: rules are [{ when: <rule>, then: <value> }], tried in order; gives { then, index } of the first whose when holds for data, or { then: otherwise, index: -1 } when none does. then may itself be a rule, evaluated with the same data.
Get it
$ quillo install json_logic # json_logic.qwer into this folder$ quillo install json_logic --example # and its example, json_logic.qweOr with curl:
$ curl -O https://quillolang.org/plugins/files/json_logic.qwer$ curl -O https://quillolang.org/plugins/files/json_logic.qweThen, in your program, all of it or only what you call:
use json_logic.qweruse jsonlogic_apply, jsonlogic_test from json_logic // Quillo 0.6: only theseIt uses this crate, which cargo fetches the first time the plugin is built:
jsonlogic-rs = "0.5"Example
// Shipping tiers and promotions as JsonLogic rules, as an admin would keep them in the// database and the checkout page would also run them in the browser (json-logic-js).//// quillo run json_logic.qwe then open http://localhost:8080/shipping?total=80&country=NLuse "json_logic.qwer"let tiers = [ { when: { ">=": [{ var: "total" }, 100] }, then: "free" }, { when: { and: [{ in: [{ var: "country" }, ["NL", "BE", "LU"]] }, { ">=": [{ var: "total" }, 50] }] }, then: "benelux" }, { when: { "==": [{ var: "member" }, true] }, then: "member" },]route GET /shipping ? { total: float, country: str = "DE", member: bool = false } { let d = jsonlogic_decide(tiers, { total, country, member }, "standard")? { tier: d.then, rule: d.index }}let customers = [ { name: "Ada", orders: 14, country: "UK", newsletter: true }, { name: "Bas", orders: 2, country: "NL", newsletter: true }, { name: "Cleo", orders: 31, country: "NL", newsletter: false },]route GET /segment = jsonlogic_filter({ and: [{ ">": [{ var: "orders" }, 10] }, { "!": [{ "==": [{ var: "country" }, "UK"] }] }] }, customers)? | map((c) => c.name)route GET /price = { discount: jsonlogic_apply({ if: [{ ">=": [{ var: "qty" }, 10] }, { "*": [{ var: "price" }, 0.1] }, 0] }, { qty: 12, price: 30 })?, eligible: jsonlogic_test({ "<=": [18, { var: "age" }, 65] }, { age: 40 })?, missing: jsonlogic_apply({ missing: ["email", "name"] }, { name: "Ada" })?,}$ quillo run json_logic.qwe$ curl 'localhost:8080/shipping?total=80&country=NL'$ curl 'localhost:8080/segment'$ curl 'localhost:8080/price'Source
//! Business rules as JsonLogic: rules kept as JSON (in a database, edited in an admin, shared with a JavaScript front end) and evaluated against data, for eligibility, pricing tiers, feature flags and form logic.//! version: 1.0.0//!//! [dependencies]//! jsonlogic-rs = "0.5"use serde_json::{json, Value};const MAX_RULE: usize = 256 * 1024;/// JsonLogic's own truthiness, as in JavaScript: false, null, 0, "" and [] are false.fn truthy(v: &Value) -> bool { match v { Value::Null => false, Value::Bool(b) => *b, Value::Number(n) => n.as_f64().map(|f| f != 0.0 && !f.is_nan()).unwrap_or(false), Value::String(s) => !s.is_empty(), Value::Array(a) => !a.is_empty(), Value::Object(_) => true, }}fn apply(rule: &Value, data: &Value) -> Result<Value, String> { if rule.to_string().len() > MAX_RULE { return Err("a rule is at most 256 KB of JSON".into()); } jsonlogic_rs::apply(rule, data).map_err(|e| e.to_string())}/// The value of a rule for `data`: `{ "if": [{ ">": [{ "var": "cart.total" }, 100] }, "free", "paid"] }` with `{ cart: { total: 140 } }` is "free"./// Operators: `var` (dots go into maps, `missing`), `== != < <= > >=`, `and or ! !!`, `if`, `+ - * / % min max`, `in`, `cat`, `substr`, `map filter reduce all some none merge`.pub fn jsonlogic_apply(rule: serde_json::Value, data: serde_json::Value) -> Result<Value, String> { apply(&rule, &data)}/// Whether a rule holds for `data`, by JsonLogic's truthiness (false, null, 0, "" and [] are false).pub fn jsonlogic_test(rule: serde_json::Value, data: serde_json::Value) -> Result<bool, String> { apply(&rule, &data).map(|v| truthy(&v))}/// The items a rule holds for, in order: a saved search, a segment of customers, offers a user qualifies for. At most 100,000 items.pub fn jsonlogic_filter(rule: serde_json::Value, items: Vec<serde_json::Value>) -> Result<Vec<Value>, String> { if items.len() > 100_000 { return Err("at most 100,000 items at once".into()); } let mut out = Vec::new(); for it in items { if truthy(&apply(&rule, &it)?) { out.push(it); } } Ok(out)}/// A decision table: `rules` are `[{ when: <rule>, then: <value> }]`, tried in order; gives `{ then, index }` of the first whose `when` holds for `data`,/// or `{ then: otherwise, index: -1 }` when none does. `then` may itself be a rule, evaluated with the same data.pub fn jsonlogic_decide(rules: Vec<serde_json::Value>, data: serde_json::Value, otherwise: serde_json::Value) -> Result<Value, String> { for (i, r) in rules.iter().enumerate() { let when = r.get("when").ok_or(format!("rule {} has no `when`", i))?; if truthy(&apply(when, &data)?) { let then = r.get("then").cloned().unwrap_or(Value::Null); let v = if then.is_object() { apply(&then, &data)? } else { then }; return Ok(json!({ "then": v, "index": i })); } } Ok(json!({ "then": otherwise, "index": -1 }))}Related
feature_flags.qwer · 1.0.0Feature flags kept as data: kill switches, users named outright, rules on their attributes, and percentage roll-outs and variants that stay the same for each user.7 views · 9 downloadsjq jq.qwer · 1.0.0jq filters over JSON with jaq: pick, reshape, filter and sum API answers, webhook bodies and JSON lines in the language people already write in jq, with variables and a check for typed-in filters.7 views · 8 downloadsJSON types json_types.qwer · 1.0.0Types inferred from JSON samples: TypeScript interfaces, a JSON Schema and Rust structs for an API's answers or a webhook's body, with optional and nullable fields found across samples.7 views · 7 downloadsOpenAPI openapi.qwer · 1.0.0OpenAPI 3 documents read from JSON or YAML: each operation's path, parameters, body and responses, and a request path matched to its operation.8 views · 9 downloadsWebAssembly sandbox wasm_run.qwer · 1.0.0WebAssembly functions run sandboxed, with a fuel limit so a loop cannot hang the server and a memory cap: pricing or scoring rules others write.7 views · 7 downloadsFormulas formulas.qwer · 1.0.0Spreadsheet-like formulas users write, such as if(total > 100, 0, 4.95), evaluated safely with their variables, checked before they are saved.8 views · 7 downloadsThread 0 posts
On JsonLogic rules: questions, tips, corrections, and code that uses it.
No posts yet. The first one opens the thread.