Go
Package rulecascade, standard library only. The same code also builds the rcas command and the WebAssembly module.
The module rulescascade.com/go (package rulecascade) evaluates bundles and compiles source
rulesets. The library uses the standard library only; the rcas command built from the same module
adds gopkg.in/yaml.v3 to read YAML. The command and the WebAssembly module have
their own page.
Supported versions: Go 1.22 or later (go directive in go.mod). CI tests Go 1.24 on Linux,
Windows and macOS. No maximum is declared.
Install
go get rulescascade.com/go@latestimport rulecascade "rulescascade.com/go"The module is served from a public mirror that holds only the Go runtime, through the Go module
proxy like any other module. The command installs the same way:
go install rulescascade.com/go/cmd/rcas@latest.
Load
rules, err := rulecascade.FromBundle(bundle) // bundle: the parsed JSON of a *.bundle.json file
if err != nil {
log.Fatal(err) // a *rulecascade.LoadError: do not start
}rulecascade.ParseJSON reads a file's bytes into the values the library expects, keeping the order
of object members; map[string]any from encoding/json works too. To compile source documents
instead, call rulecascade.Load(document, registry, loader); a nil loader skips the checks that
need the entity schemas.
Evaluate
result, err := rules.Evaluate(map[string]any{
"entity": "Transfer",
"operation": "create",
"data": map[string]any{
"type": "international", "amount": 12000, "currency": "USD",
"beneficiary": map[string]any{"name": "Ana", "country": "ES"},
},
"actor": map[string]any{"id": "u-1", "roles": []string{"teller"}},
}, "server", nil)
if err != nil {
log.Fatal(err)
}
fmt.Println(result.Decision)
for _, f := range result.Findings {
fmt.Println(f.Code, f.Severity, f.Fields, f.Message)
}
// deny
// ORG-TRF-003 warning [/memo] Adding a memo makes this transfer easier to reconcile.
// PAY-TRF-002 error [/beneficiary/swiftCode] A valid SWIFT/BIC code is required for international transfers.
// PAY-TRF-003 warning [/amount /beneficiary/name] This is a large transfer to Ana. Please confirm the details.The third argument is a rulecascade.Operators map of custom operators, or nil. Evaluate is
pure and a RuleSet is safe for concurrent use. result.Allowed() is the gate; Result,
Finding, Effect and Command marshal to the JSON of the evaluation API with
rulecascade.Marshal or encoding/json.
Errors
| What | How it surfaces | What to do |
|---|---|---|
| An unusable bundle | *rulecascade.LoadError from FromBundle (BUNDLE_UNSUPPORTED, BUNDLE_INVALID) | Do not start |
| A source ruleset that fails a check | *rulecascade.LoadError from Load; Problems has Code, Rule and Message | Do not start; fix it in CI |
| A request of the wrong shape | *rulecascade.RequestError from Evaluate, with Message | Answer 400 |
| A rule that cannot be evaluated, or a missing operator | No error: a blocking RULE-EVALUATION-ERROR finding | Alert on it |
var malformed *rulecascade.RequestError
if errors.As(err, &malformed) {
return 400, map[string]any{"title": malformed.Message}
}More
The snippets come from the runnable examples in
example_test.go,
which CI runs. The first one, end to end: load the payments bundle, evaluate an international
transfer without a SWIFT code, and print the decision, the findings and the computed fee. The
// Output: block is checked by go test, so it is what the code prints:
func Example() {
bundle := read("bundles/acme.payments.transfer.bundle.json")
rules, err := rulecascade.FromBundle(bundle) // bundle: the parsed JSON of a *.bundle.json file
if err != nil {
log.Fatal(err) // a *rulecascade.LoadError: do not start
}
result, err := rules.Evaluate(map[string]any{
"entity": "Transfer",
"operation": "create",
"data": map[string]any{
"type": "international", "amount": 12000, "currency": "USD",
"beneficiary": map[string]any{"name": "Ana", "country": "ES"},
},
"actor": map[string]any{"id": "u-1", "roles": []string{"teller"}},
}, "server", nil)
if err != nil {
log.Fatal(err)
}
fmt.Println(result.Decision)
for _, f := range result.Findings {
fmt.Println(f.Code, f.Severity, f.Fields, f.Message)
}
for _, e := range result.Effects {
if e.Type == "value" {
fmt.Println(e.Field, "=", e.Value)
}
}
// Output:
// deny
// ORG-TRF-003 warning [/memo] Adding a memo makes this transfer easier to reconcile.
// PAY-TRF-002 error [/beneficiary/swiftCode] A valid SWIFT/BIC code is required for international transfers.
// PAY-TRF-003 warning [/amount /beneficiary/name] This is a large transfer to Ana. Please confirm the details.
// /fee = 180
}The full API, numbers and JSON values are in the package README.