zen

package module
v2.1.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 27, 2026 License: MIT Imports: 12 Imported by: 0

README

[!NOTE]

This is a maintained fork

phenixrizen/zen-go is the Go binding for phenixrizen/zen, a maintained fork of gorules/zen, maintained by Phenix Rizen (Nathan Rockhold).

The bundled static libraries are built from that fork, so this binding exposes features upstream does not have:

addition what it does
databaseNode Look reference data up from inside a decision, with a host-supplied handler.
Pure-Rust SQLite handler Register it once at startup with NewEngineWithSqlite; every query runs in-process with no crossing back over the FFI. No C, no vendored amalgamation.
Decision-level $params Static parameters supplied per decision, reachable from switch, expression, and function nodes.
TZ is honoured Date resolution respects TZ rather than only /etc/localtime.
Exact fractional numbers Fixes silent truncation of every non-integer value.
go get github.com/phenixrizen/zen-go/v2

Not affiliated with or endorsed by GoRules. MIT licensed, same as upstream, with the original copyright retained in LICENSE.

Go Rules Engine

Business logic humans can read and machines can run. One copy of your rules: the owner reads it, every system runs it.

Go Reference

ZEN Engine is a cross-platform, open-source Business Rules Engine (BRE) written in Rust with native Go bindings, alongside Node.js, Python, Java, Kotlin and .NET. Decisions evaluate in microseconds, run identically on every platform, and are stored as portable JSON. Loading the JSON is up to you: file system, database or service call.

Rules that read like sentences

Conditions are written the way the business says them, in the ZEN Expression Language. The developer view is one toggle away, and the two can never drift apart: there is only one source of truth, and this engine runs it.

Rules as graphs, or as documents

Model a decision on a visual canvas of decision tables, switches, expressions, functions and reusable sub-decisions. Or write it as a policy document with prose, typed data models and tables. Both compile to the same engine and return the same answers.

A JDM document is either a graph (decision tables, switches, expressions, functions and reusable sub-decisions) or a policy (prose, typed data models and tables). Both compile to the same engine and return the same answers.

What's new in 2.0

Version 2.0 is the first stable release of the new engine line:

  • Policy documents: model decisions as readable documents with typed data models, expressions, decision tables, match blocks and assertions. Policies compile to the same engine as graphs and return the same answers.
  • Workspace analysis: static type checking across policies and graphs. Type flow, exhaustiveness checking, write-conflict detection and precise diagnostics, all available before anything runs.
  • Per-column collect: decision table output columns can collect across all matching rows (tags[]) while the rest of the table stays first-match.
  • Pre-compiled engine: decisions are parsed and compiled once at load; evaluation is allocation-light and repeat-safe.
  • Hardened runtime: out-of-range numbers, arithmetic overflow and malformed inputs return errors or nulls instead of crashing the process.
  • Unified bindings: configurable loaders, batch evaluation and consistent error envelopes across Node.js, Python, Go and FFI consumers.

Installation

go get github.com/phenixrizen/zen-go/v2

Quickstart

package main

import (
	"fmt"
	"os"
	"path"

	zen "github.com/phenixrizen/zen-go/v2"
)

func readTestFile(key string) ([]byte, error) {
	filePath := path.Join("test-data", key)
	return os.ReadFile(filePath)
}

func main() {
	engine := zen.NewEngine(zen.EngineConfig{Loader: zen.Loader(readTestFile)})
	defer engine.Dispose() // Call to avoid leaks

	output, err := engine.Evaluate("rule.json", map[string]any{})
	if err != nil {
		fmt.Println(err)
	}

	fmt.Println(output)
}

Loader Configurations

EngineConfig.Loader accepts either a loader callback wrapped in zen.Loader (as above) or a loader configuration of a known type. With a configuration, decisions are pre-loaded and pre-compiled at engine creation for faster evaluations.

engine := zen.NewEngine(zen.EngineConfig{Loader: zen.FilesystemLoader{Path: "test-data"}})

engine := zen.NewEngine(zen.EngineConfig{Loader: zen.StaticLoader{
	Content: map[string]json.RawMessage{"rule.json": ruleJson},
}})

engine := zen.NewEngine(zen.EngineConfig{Loader: zen.ZipLoader{Bytes: zipBytes}})

If the loader configuration is invalid (e.g. corrupted zip bytes), the error is returned by the first call to Evaluate, GetDecision or CreateDecision.

The same callback pattern works for loading from a REST API, S3, a database, or anywhere else.

Other platforms

Support matrix

Arch Go
linux-x64-gnu
linux-arm64-gnu
darwin-x64
darwin-arm64
win32-x64-msvc

We do not support linux-musl currently.

Contribution

Contributions are welcome here. This fork exists partly because upstream cannot take them.

Note that the Go code in this repository is a thin binding: most behaviour lives in phenixrizen/zen, and the deps/ static libraries are built from it. A change to evaluation belongs there; a change to the Go surface belongs here.

make fmt_check
make test

License

MIT License

Documentation

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func EvaluateExpression

func EvaluateExpression[T any](expression string, context any) (T, error)

func EvaluateUnaryExpression

func EvaluateUnaryExpression(expression string, context any) (bool, error)

func GetNodeField

func GetNodeField[T any](request NodeRequest, path string) (T, error)

func GetNodeFieldRaw

func GetNodeFieldRaw[T any](request NodeRequest, path string) (T, error)

func RenderTemplate

func RenderTemplate[T any](template string, context any) (T, error)

Types

type CustomNode

type CustomNode struct {
	ID     string          `json:"id"`
	Name   string          `json:"name"`
	Kind   string          `json:"kind"`
	Config json.RawMessage `json:"config"`
}

type CustomNodeHandler

type CustomNodeHandler func(request NodeRequest) (NodeResponse, error)

type Decision

type Decision interface {
	Evaluate(context any) (*EvaluationResponse, error)
	EvaluateWithOpts(context any, options EvaluationOptions) (*EvaluationResponse, error)
	Dispose()
}

type Engine

type Engine interface {
	Evaluate(key string, context any) (*EvaluationResponse, error)
	EvaluateWithOpts(key string, context any, options EvaluationOptions) (*EvaluationResponse, error)
	EvaluateBatch(requests []EvaluateBatchRequest) ([]EvaluateBatchResult, error)
	EvaluateBatchWithOpts(requests []EvaluateBatchRequest, options EvaluationOptions) ([]EvaluateBatchResult, error)
	GetDecision(key string) (Decision, error)
	CreateDecision(data []byte) (Decision, error)
	Dispose()
}

func NewEngine

func NewEngine(config EngineConfig) Engine

type EngineConfig

type EngineConfig struct {
	Loader            EngineLoader
	CustomNodeHandler CustomNodeHandler

	// SqliteConfig registers the pure-Rust SQLite handler that serves databaseNode
	// lookups. It is the handler config as JSON, e.g.
	//
	//	{"root":"/catalog"}
	//	{"sources":{"codes":"/catalog/codes.db"},"allowRaw":true}
	//
	// Queries then run inside Rust against the files directly; unlike a custom node,
	// nothing crosses back into Go per lookup. Leave empty to omit the handler, in
	// which case evaluating a databaseNode reports that no handler was provided.
	//
	// Not supported together with a LoaderConfig -- use a Loader callback instead.
	SqliteConfig string
}

type EngineLoader

type EngineLoader interface {
	// contains filtered or unexported methods
}

type EvaluateBatchRequest

type EvaluateBatchRequest struct {
	Key     string
	Context any
}

type EvaluateBatchResult

type EvaluateBatchResult struct {
	Success bool                `json:"success"`
	Data    *EvaluationResponse `json:"data,omitempty"`
	Error   json.RawMessage     `json:"error,omitempty"`
}

type EvaluationOptions

type EvaluationOptions struct {
	Trace    bool  `json:"trace"`
	MaxDepth uint8 `json:"maxDepth"`
}

type EvaluationResponse

type EvaluationResponse struct {
	Performance string           `json:"performance"`
	Result      json.RawMessage  `json:"result"`
	Trace       *json.RawMessage `json:"trace"`
}

type FilesystemLoader

type FilesystemLoader struct {
	Path string
}

type Loader

type Loader func(key string) ([]byte, error)

type LoaderConfig

type LoaderConfig interface {
	EngineLoader
	// contains filtered or unexported methods
}

type NodeRequest

type NodeRequest struct {
	Node  CustomNode      `json:"node"`
	Input json.RawMessage `json:"input"`
}

type NodeResponse

type NodeResponse struct {
	Output    any `json:"output"`
	TraceData any `json:"traceData"`
}

type StaticLoader

type StaticLoader struct {
	Content map[string]json.RawMessage
}

type ZipLoader

type ZipLoader struct {
	Bytes []byte
}

Directories

Path Synopsis
deps
darwin_amd64
Package darwin_amd64 is required to provide support for vendoring modules DO NOT REMOVE
Package darwin_amd64 is required to provide support for vendoring modules DO NOT REMOVE
darwin_arm64
Package darwin_arm64 is required to provide support for vendoring modules DO NOT REMOVE
Package darwin_arm64 is required to provide support for vendoring modules DO NOT REMOVE
linux_amd64
Package linux_amd64 is required to provide support for vendoring modules DO NOT REMOVE
Package linux_amd64 is required to provide support for vendoring modules DO NOT REMOVE
linux_arm64
Package linux_arm64 is required to provide support for vendoring modules DO NOT REMOVE
Package linux_arm64 is required to provide support for vendoring modules DO NOT REMOVE
windows_amd64
Package windows_amd64 is required to provide support for vendoring modules DO NOT REMOVE
Package windows_amd64 is required to provide support for vendoring modules DO NOT REMOVE
examples
custom-node command

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL