Rule Cascade
Usage by language

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@latest
import 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

WhatHow it surfacesWhat 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 MessageDo not start; fix it in CI
A request of the wrong shape*rulecascade.RequestError from Evaluate, with MessageAnswer 400
A rule that cannot be evaluated, or a missing operatorNo error: a blocking RULE-EVALUATION-ERROR findingAlert 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:

packages/go/example_test.go
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.

On this page