An explainable expression rules engine for JSON data, implemented in MoonBit.
Dependencies
///|
test "README quick start" {
let input : Json = {
"user": { "age": 24, "country": "CN", "roles": ["editor"], "active": true },
}
let result = check(
"user.age >= 18 && user.country in [\"CN\", \"SG\"] && user.active", input,
)
assert_eq(result, Ok(true))
}///|
test "README compile once" {
let program = compile("order.total > 0 && order.currency == \"CNY\"").unwrap()
assert_eq(
evaluate(program, { "order": { "total": 99, "currency": "CNY" } }),
Ok(Json::boolean(true)),
)
}///|
test "README defensive compile limits" {
let limits : CompileLimits = {
max_source_length: 4096,
max_ast_nodes: 1024,
max_ast_depth: 64,
}
let program = compile_with_limits("user.active", limits).unwrap()
assert_true(program.node_count() > 0)
assert_true(program.ast_depth() > 0)
}///|
test "README ruleset" {
let rules = RuleSet::compile([
RuleDefinition::new("adult", "user.age >= 18", "The user must be an adult."),
RuleDefinition::new("active", "user.active", "The account must be active."),
]).unwrap()
let report = rules.evaluate({ "user": { "age": 20, "active": true } })
assert_true(report.passed)
assert_eq(report.passed_count, 2)
}///|
test "README optional validation" {
let rule = "is_email(coalesce(request?.user?.email, \"\"))"
assert_eq(check(rule, Json::empty_object()), Ok(false))
assert_eq(
check(rule, { "request": { "user": { "email": "dev@example.com" } } }),
Ok(true),
)
}moon run cmd/main -- check examples/access-rules.json examples/user-valid.jsonmoon run cmd/main -- check examples/access-rules.json examples/user-invalid.jsonmoon run cmd/main -- lint-rules examples/api-validation-rules.json
moon run cmd/main -- check examples/api-validation-rules.json examples/api-request-valid.json
moon run cmd/main -- check examples/api-validation-rules.json examples/api-request-invalid.json
moon run cmd/main -- test-rules examples/api-validation-rules.json examples/api-validation-cases.jsonmoon run cmd/main -- explain-file \
"user.age >= 18 && user.active" \
examples/user-valid.jsonmoonrule eval <expression> <json>
moonrule eval-file <expression> <data.json>
moonrule explain <expression> <json>
moonrule explain-file <expression> <data.json>
moonrule analyze <expression>
moonrule analyze-rules <rules.json>
moonrule lint <expression>
moonrule lint-rules <rules.json>
moonrule test-rules <rules.json> <cases.json>
moonrule check <rules.json> <data.json>
moonrule check-batch <rules.json> <data-array.json>
moonrule functions| 类别 | 语法 |
|---|---|
| 字面量 | true、false、null、数字、字符串、数组 |
| 路径 | user.name、orders[0].total、meta["key"] |
| 可选访问 | user?.profile?.email、items?[0] |
| 算术 | +、-、*、/、% |
| 比较 | ==、!=、<、<=、>、>= |
| 布尔 | !、&&、|| |
| 成员 | value in array、key in object、part in string |
| 函数 | 说明 |
|---|---|
| len(value) | 返回字符串字符数、数组长度或对象字段数 |
| contains(container, value) | 检查字符串、数组或对象 |
| starts_with(text, prefix) | 检查字符串前缀 |
| ends_with(text, suffix) | 检查字符串后缀 |
| lower(text) / upper(text) | ASCII/Unicode大小写转换 |
| abs(number) | 数值绝对值 |
| matches(text, pattern) | 正则表达式匹配 |
| exists(value) | 判断值不是 null |
| has(object, key) | 判断对象是否包含字段 |
| get(object, key, default) | 安全读取字段,缺失时返回默认值 |
| min / max / clamp / sum | 常用数值与数组聚合 |
| type_of(value) / coalesce(...) | 类型识别与空值回退 |
| all(booleans) / any(booleans) | 布尔数组聚合 |
| is_date(text) / is_datetime(text) | ISO 日期与 RFC 3339 时间戳校验 |
| is_ipv4(text) / is_email(text) | IPv4 地址与常用邮箱地址校验 |
| is_uuid(text) / is_semver(text) | 标准 UUID 与语义化版本校验 |
[
{
"name": "adult-user",
"expression": "user.age >= 18",
"message": "The user must be at least 18 years old.",
"severity": "Error"
}
][
{
"name": "minor user",
"input": { "user": { "age": 15 } },
"expected_passed": false,
"expected_failed_rules": ["adult-user"]
}
]moon check --deny-warn
moon test --package YeeHh2004/moonrule --deny-warn
moon bench
moon coverage analyze
moon coverage report
moon info
moon package --listpub(all) struct AnalysisGateReport {
passed : Bool
analysis : ProgramAnalysis
violations : Array[AnalysisFinding]
} derive(Eq, ToJson, Debug, FromJson)pub(all) struct BatchItemReport {
index : Int
passed : Bool
report : PolicyReport
} derive(Eq, ToJson, Debug, FromJson)pub(all) struct BatchOptions {
rule_options : RuleEvaluationOptions
stop_on_first_rejected_record : Bool
} derive(Eq, ToJson, Debug, FromJson)pub enum BinaryOp {
Or
And
Equal
NotEqual
Less
LessEqual
Greater
GreaterEqual
In
Add
Subtract
Multiply
Divide
Remainder
} derive(Debug)fn Diagnostic::new(stage : DiagnosticStage, code : String, message : String, span : Span, hint? : String) -> Diagnosticpub enum Expr {
Literal(Json, Span)
Variable(String, Span)
ArrayLiteral(Array[Expr], Span)
Member(Expr, String, Span)
Index(Expr, Expr, Span)
OptionalMember(Expr, String, Span)
OptionalIndex(Expr, Expr, Span)
Call(String, Array[Expr], Span)
Unary(UnaryOp, Expr, Span)
Binary(Expr, BinaryOp, Expr, Span)
} derive(Debug)pub(all) struct FunctionSpec {
name : String
category : FunctionCategory
min_arguments : Int
max_arguments : Int
signature : String
summary : String
} derive(Eq, ToJson, Debug, FromJson)pub(all) struct ProgramAnalysis {
source_length : Int
node_count : Int
ast_depth : Int
estimated_cost : Int
statistics : ExpressionStatistics
referenced_paths : Array[String]
called_functions : Array[String]
findings : Array[AnalysisFinding]
} derive(Eq, ToJson, Debug, FromJson)fn ProgramAnalysis::findings_at(self : ProgramAnalysis, level : AnalysisLevel) -> Array[AnalysisFinding]fn RuleDefinition::new(name : String, expression : String, message : String, severity? : Severity) -> RuleDefinitionpub(all) struct RuleEvaluationOptions {
failure_threshold : FailureThreshold
stop_on_blocking_failure : Bool
stop_on_evaluation_error : Bool
} derive(Eq, ToJson, Debug, FromJson)pub(all) struct RuleOutcome {
name : String
status : OutcomeStatus
message : String
severity : Severity
diagnostic : Diagnostic?
} derive(Eq, ToJson, Debug, FromJson)fn RuleSet::analyze_with_policy(self : RuleSet, policy : AnalysisPolicy) -> Result[RuleSetAnalysisGateReport, Diagnostic]fn RuleSet::compile_with_limits(definitions : Array[RuleDefinition], limits : RuleSetLimits) -> Result[RuleSet, Array[Diagnostic]]fn RuleSet::evaluate_batch(self : RuleSet, contexts : Array[Json], options? : BatchOptions) -> BatchReportfn RuleSet::evaluate_with_options(self : RuleSet, context : Json, options : RuleEvaluationOptions) -> PolicyReportfn RuleSet::from_json_with_limits(config : Json, limits : RuleSetLimits) -> Result[RuleSet, Array[Diagnostic]]fn RuleSetAnalysisGateReport::to_json_string(self : RuleSetAnalysisGateReport, indent? : Int) -> Stringpub(all) struct RuleSetLimits {
max_rules : Int
max_name_length : Int
max_message_length : Int
max_total_source_length : Int
require_unique_names : Bool
compile_limits : CompileLimits
} derive(Eq, ToJson, Debug, FromJson)fn RuleTestCase::new(name : String, input : Json, expected_passed : Bool, expected_failed_rules? : Array[String]) -> RuleTestCasefn RuleTestSuite::from_json(config : Json, limits? : RuleTestSuiteLimits) -> Result[RuleTestSuite, Array[Diagnostic]]fn RuleTestSuite::new(cases : Array[RuleTestCase], limits? : RuleTestSuiteLimits) -> Result[RuleTestSuite, Array[Diagnostic]]fn analyze_with_policy(program : Program, policy : AnalysisPolicy) -> Result[AnalysisGateReport, Diagnostic]fn builtin_functions_json(indent? : Int) -> Stringfn evaluate_with_limits(program : Program, context : Json, limits : EvaluationLimits) -> Result[Json, Diagnostic]An explainable expression rules engine for JSON data, implemented in MoonBit.
Dependencies