ruleengine

package module
v1.0.0 Latest Latest
Warning

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

Go to latest
Published: Aug 26, 2026 License: MIT Imports: 17 Imported by: 0

README

rule-engine

CI CodeQL Coverage Mutation Documentation Go Reference Release Go License

rule-engine is a deterministic, typed, inspectable engine for evaluating facts and propositions. It compiles immutable execution plans, evaluates them concurrently without hidden I/O, supports bounded forward chaining, and emits redacted diagnostics and explanations.

It is intentionally not an authorization system, feature-flag service, validator, workflow engine, database query layer, or action runner. Those products may adapt its decisions while retaining their own fail-closed and domain semantics.

country := ruleengine.MustPath("shipment", "country")
set := ruleengine.RuleSet{ID: "routing", Rules: []ruleengine.Rule{{
    ID: "finland",
    When: ruleengine.Compare(ruleengine.OpEqual,
        ruleengine.Variable(country),
        ruleengine.Literal(ruleengine.String("FI"))),
}}}
plan, diagnostics, err := ruleengine.NewCompiler(
    ruleengine.DefaultLimits(),
).Compile(context.Background(), set)

See the executable package example, the quick start, and the JSON AST fixture.

Guarantees

  • Missing and null are distinct typed values; there is no truthiness or implicit coercion.
  • Priorities sort descending and equal priorities sort by rule ID ascending.
  • Logical operands evaluate left to right and short-circuit deterministically.
  • Compilation rejects duplicate IDs, unknown operators, incompatible literal types, dependency cycles, non-finite floats, unsafe regexes, and every configured bound violation.
  • Evaluation bounds time, iterations, derived facts, explanations, and errors.
  • Canonical JSON and SHA-256 hashes are stable for equivalent definitions.
  • Built-in plans and contexts are immutable and safe for concurrent reuse.

Documentation

Verification

make check runs formatting, module hygiene, vet, static analysis, lint, tests, meaningful 100% production coverage, race tests, fuzz smoke tests, mutation tests, benchmarks, documentation checks, API compatibility, security policy checks, vulnerability scanning, and workflow validation.

The module requires Go 1.26.6 and has no runtime dependencies. Exact decimal, temporal-period, and measurement adapters live in isolated nested modules described in the extension guide, so core consumers do not inherit their dependency graphs.

License

MIT. See LICENSE.

Ecosystem

Use the Golib documentation portal to choose companion packages, supported stacks, recipes, and operations guidance.

Documentation

Overview

Package ruleengine evaluates typed propositions over immutable fact contexts. It deliberately provides no authorization, feature-rollout, validation, workflow, persistence, or action-execution semantics.

Example
package main

import (
	"context"
	"fmt"

	ruleengine "github.com/faustbrian/go-rule-engine"
)

func main() {
	country := ruleengine.MustPath("shipment", "country")
	weight := ruleengine.MustPath("shipment", "weight_grams")
	set := ruleengine.RuleSet{ID: "location-routing", Rules: []ruleengine.Rule{{
		ID:       "finland-heavy",
		Priority: 100,
		When: ruleengine.All(
			ruleengine.Compare(ruleengine.OpEqual,
				ruleengine.Variable(country), ruleengine.Literal(ruleengine.String("FI"))),
			ruleengine.Compare(ruleengine.OpGreaterOrEqual,
				ruleengine.Variable(weight), ruleengine.Literal(ruleengine.Int(1_000))),
		),
	}}}
	plan, _, err := ruleengine.NewCompiler(ruleengine.DefaultLimits()).Compile(context.Background(), set)
	if err != nil {
		panic(err)
	}
	facts, err := ruleengine.NewContext(
		ruleengine.Fact{Path: country, Value: ruleengine.String("FI"), Owner: ruleengine.OwnerResource},
		ruleengine.Fact{Path: weight, Value: ruleengine.Int(1_500), Owner: ruleengine.OwnerResource},
	)
	if err != nil {
		panic(err)
	}
	result := plan.Evaluate(context.Background(), facts)

	fmt.Println(result.Decision == ruleengine.Matched)
	fmt.Println(result.MatchedRules)
}
Output:
true
[finland-heavy]

Index

Examples

Constants

This section is empty.

Variables

This section is empty.

Functions

func CanonicalHash

func CanonicalHash(set RuleSet) (string, error)

CanonicalHash returns the lowercase SHA-256 digest of MarshalCanonical.

func IsCode

func IsCode(err error, code Code) bool

IsCode reports whether err or a wrapped error has code.

func MarshalCanonical

func MarshalCanonical(set RuleSet) ([]byte, error)

MarshalCanonical serializes a definition containing only built-in operators with stable ordering and field representation. Custom predicates cannot be serialized.

func ParseJSON

func ParseJSON(data []byte, limits Limits) (RuleSet, []Diagnostic, error)

ParseJSON parses a versioned JSON AST containing only built-in operators with strict unknown-field handling.

Types

type Code

type Code string

Code classifies errors without exposing fact values.

const (
	// CodeInvalidLimit begins the stable machine-readable error code set.
	CodeInvalidLimit Code = "invalid_limit"
	// CodeInvalidPath reports a malformed fact path.
	CodeInvalidPath Code = "invalid_path"
	// CodeDuplicateFact reports a repeated fact path.
	CodeDuplicateFact Code = "duplicate_fact"
	// CodeInvalidFact reports a malformed fact value.
	CodeInvalidFact Code = "invalid_fact"
	// CodeInvalidRule reports a malformed rule definition.
	CodeInvalidRule Code = "invalid_rule"
	// CodeDuplicateRule reports a repeated rule identifier.
	CodeDuplicateRule Code = "duplicate_rule"
	// CodeUnknownOperator reports an unregistered operator.
	CodeUnknownOperator Code = "unknown_operator"
	// CodeTypeMismatch reports incompatible value kinds.
	CodeTypeMismatch Code = "type_mismatch"
	// CodeLimitExceeded reports an exhausted resource budget.
	CodeLimitExceeded Code = "limit_exceeded"
	// CodeEvaluation reports a predicate evaluation failure.
	CodeEvaluation Code = "evaluation_error"
	// CodeConflict reports incompatible matches or facts.
	CodeConflict Code = "conflict"
	// CodeCycle reports a derivation dependency cycle.
	CodeCycle Code = "cycle"
	// CodeInvalidJSON reports a malformed JSON AST.
	CodeInvalidJSON Code = "invalid_json"
	// CodeNotSerializable reports an unsupported canonical value.
	CodeNotSerializable Code = "not_serializable"
	// CodeCache reports a plan cache failure.
	CodeCache Code = "cache_error"
)

type Compiler

type Compiler struct {
	// contains filtered or unexported fields
}

Compiler validates rule sets and produces immutable plans.

func NewCompiler

func NewCompiler(limits Limits) Compiler

NewCompiler creates a compiler containing only built-in operators.

func NewCompilerWithOperators

func NewCompilerWithOperators(limits Limits, operators ...Operator) (Compiler, error)

NewCompilerWithOperators creates an isolated operator registry. Built-in names and duplicate custom names cannot be replaced.

func (Compiler) CanonicalHash

func (compiler Compiler) CanonicalHash(set RuleSet) (string, error)

CanonicalHash returns the lowercase SHA-256 digest of compiler's canonical representation, including definitions using its registered custom operators.

func (Compiler) Compile

func (compiler Compiler) Compile(ctx context.Context, set RuleSet) (Plan, []Diagnostic, error)

Compile validates, copies, and deterministically orders a rule set.

func (Compiler) CompileCached

func (compiler Compiler) CompileCached(ctx context.Context, set RuleSet, cache PlanCache) (Plan, []Diagnostic, error)

CompileCached returns a matching cached plan or compiles and stores a new plan. Cache entries with a different embedded hash are ignored.

func (Compiler) MarshalCanonical

func (compiler Compiler) MarshalCanonical(set RuleSet) ([]byte, error)

MarshalCanonical serializes a definition with stable ordering and field representation using compiler's registered custom operators. Custom predicates cannot be serialized.

func (Compiler) ParseJSON

func (compiler Compiler) ParseJSON(data []byte) (RuleSet, []Diagnostic, error)

ParseJSON parses a versioned JSON AST with strict unknown-field handling and validates custom operators against compiler's isolated registry.

type ConflictStrategy

type ConflictStrategy uint8

ConflictStrategy controls how ordered matches are selected.

const (
	// FirstMatch selects only the first deterministically ordered match.
	FirstMatch ConflictStrategy = iota
	// CollectAll selects every unique matching rule.
	CollectAll
	// ErrorOnMultiple rejects more than one unique match.
	ErrorOnMultiple
)

type Context

type Context struct {
	// contains filtered or unexported fields
}

Context is an immutable snapshot of supplied facts.

func NewContext

func NewContext(facts ...Fact) (Context, error)

NewContext builds a context with DefaultLimits.

func NewContextWithLimits

func NewContextWithLimits(limits Limits, facts ...Fact) (Context, error)

NewContextWithLimits validates and copies all facts.

func (Context) Lookup

func (c Context) Lookup(path Path) Value

Lookup returns Missing when the path was not supplied.

func (Context) Owner

func (c Context) Owner(path Path) (Owner, bool)

Owner returns the supplied owner and whether the fact exists.

type Decision

type Decision uint8

Decision is the rule-set evaluation state.

const (
	// Unmatched means no selected rule matched and no error occurred.
	Unmatched Decision = iota
	// Matched means at least one selected rule matched without an error.
	Matched
	// Indeterminate means an error or bound prevented a reliable decision.
	Indeterminate
)

type Diagnostic

type Diagnostic struct {
	RuleID   RuleID
	Code     Code
	Severity Severity
	Message  string
}

Diagnostic describes a safe compile finding without operand values.

type Error

type Error struct {
	// contains filtered or unexported fields
}

Error is a safe diagnostic error. Its message never contains fact values.

func (*Error) Code

func (e *Error) Code() Code

Code returns the stable machine-readable classification.

func (*Error) Error

func (e *Error) Error() string

Error implements error.

type Explanation

type Explanation struct {
	RuleID  RuleID
	Matched bool
}

Explanation records one bounded rule evaluation without fact values.

type Fact

type Fact struct {
	Path  Path
	Value Value
	Owner Owner
}

Fact associates a typed value with an explicit path and owner.

type FactResolver

type FactResolver interface {
	Resolve(context.Context, Path) (Value, Owner, bool, error)
}

FactResolver supplies explicitly requested missing facts. Implementations must be deterministic; EvaluateResolved invokes paths in lexical order.

type Kind

type Kind uint8

Kind is the exact runtime type of a Value.

const (
	// KindMissing represents an absent path rather than a supplied value.
	KindMissing Kind = iota
	// KindNull represents an explicitly supplied null.
	KindNull
	// KindBool represents a boolean.
	KindBool
	// KindInt represents a signed 64-bit integer.
	KindInt
	// KindFloat represents a finite 64-bit floating-point number.
	KindFloat
	// KindString represents valid UTF-8 text.
	KindString
	// KindTime represents an instant with no monotonic clock reading.
	KindTime
	// KindDuration represents a time duration.
	KindDuration
	// KindList represents an ordered immutable list of values.
	KindList
)

type Limits

type Limits struct {
	MaxRules           int
	MaxFacts           int
	MaxASTDepth        int
	MaxOperands        int
	MaxCollection      int
	MaxStringBytes     int
	MaxDefinitionBytes int
	MaxRegexBytes      int
	MaxIdentifierBytes int
	MaxTags            int
	MaxTagBytes        int
	MaxPathBytes       int
	MaxPathSegments    int
	MaxIterations      int
	MaxDerivedFacts    int
	MaxDiagnostics     int
	MaxExplanation     int
	EvaluationTimeout  time.Duration
}

Limits bounds compilation and evaluation work. Zero values are invalid; callers should start with DefaultLimits and reduce values as needed.

func DefaultLimits

func DefaultLimits() Limits

DefaultLimits returns conservative process-local limits.

type MemoryPlanCache

type MemoryPlanCache struct {
	// contains filtered or unexported fields
}

MemoryPlanCache is a bounded concurrency-safe LRU plan cache.

func NewMemoryPlanCache

func NewMemoryPlanCache(capacity int) (*MemoryPlanCache, error)

NewMemoryPlanCache constructs a cache with a strict positive capacity.

func (*MemoryPlanCache) Get

func (cache *MemoryPlanCache) Get(ctx context.Context, key string) (Plan, bool, error)

Get returns and promotes a cached plan.

func (*MemoryPlanCache) Len

func (cache *MemoryPlanCache) Len() int

Len returns the current entry count.

func (*MemoryPlanCache) Put

func (cache *MemoryPlanCache) Put(ctx context.Context, key string, plan Plan) error

Put inserts or replaces a plan and evicts the least recently used entry.

type Operand

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

Operand resolves a typed value from a literal or fact variable.

func Literal

func Literal(value Value) Operand

Literal returns an immutable literal operand.

func Variable

func Variable(path Path) Operand

Variable resolves an explicit fact path during evaluation.

type Operator

type Operator interface {
	Name() OperatorName
	Signatures() []Signature
	Evaluate(context.Context, Value, Value) (bool, error)
}

Operator is an explicitly registered, typed, concurrency-safe extension. Implementations must be deterministic and must honor context cancellation.

type OperatorName

type OperatorName string

OperatorName is a stable operator identifier.

const (
	// OpEqual begins the stable built-in operator name set.
	OpEqual OperatorName = "equal"
	// OpNotEqual tests exact inequality.
	OpNotEqual OperatorName = "not_equal"
	// OpLessThan tests strict lower ordering.
	OpLessThan OperatorName = "less_than"
	// OpLessOrEqual tests inclusive lower ordering.
	OpLessOrEqual OperatorName = "less_or_equal"
	// OpGreaterThan tests strict higher ordering.
	OpGreaterThan OperatorName = "greater_than"
	// OpGreaterOrEqual tests inclusive higher ordering.
	OpGreaterOrEqual OperatorName = "greater_or_equal"
	// OpIn tests list membership.
	OpIn OperatorName = "in"
	// OpNotIn tests list non-membership.
	OpNotIn OperatorName = "not_in"
	// OpContains tests substring or list membership.
	OpContains OperatorName = "contains"
	// OpStartsWith tests a string prefix.
	OpStartsWith OperatorName = "starts_with"
	// OpEndsWith tests a string suffix.
	OpEndsWith OperatorName = "ends_with"
	// OpMatches tests a bounded regular expression.
	OpMatches OperatorName = "matches"
)

type Owner

type Owner uint8

Owner describes which integration supplied a fact. It has no authorization semantics.

const (
	// OwnerUnspecified records facts without supplied provenance.
	OwnerUnspecified Owner = iota
	// OwnerSubject records facts supplied by the evaluated subject.
	OwnerSubject
	// OwnerResource records facts supplied by the evaluated resource.
	OwnerResource
	// OwnerEnvironment records facts supplied by the environment.
	OwnerEnvironment
)

type Path

type Path struct {
	// contains filtered or unexported fields
}

Path identifies a fact without reflection or model discovery.

func MustPath

func MustPath(segments ...string) Path

MustPath creates a path with DefaultLimits and panics on programmer error.

func NewPath

func NewPath(limits Limits, segments ...string) (Path, error)

NewPath validates and copies explicit path segments.

func (Path) Segments

func (p Path) Segments() []string

Segments returns a copy of the path segments.

func (Path) String

func (p Path) String() string

String returns the stable dotted representation.

type Plan

type Plan struct {
	// contains filtered or unexported fields
}

Plan is an immutable, concurrency-safe execution plan.

func (Plan) Evaluate

func (plan Plan) Evaluate(ctx context.Context, facts Context) (result Result)

Evaluate applies the compiled plan to an immutable fact snapshot.

func (Plan) EvaluateResolved

func (plan Plan) EvaluateResolved(ctx context.Context, base Context, resolver FactResolver) Result

EvaluateResolved resolves only required paths absent from the base context, validates every returned value, then evaluates the completed snapshot.

func (Plan) Hash

func (plan Plan) Hash() string

Hash returns the canonical definition hash when the plan was compiled through CompileCached.

type PlanCache

type PlanCache interface {
	Get(context.Context, string) (Plan, bool, error)
	Put(context.Context, string, Plan) error
}

PlanCache stores compiled immutable plans by canonical definition hash.

type Predicate

type Predicate interface {
	Evaluate(context.Context, Context) (bool, error)
}

Predicate evaluates supplied facts without side effects.

func All

func All(children ...Predicate) Predicate

All evaluates children left-to-right and stops at the first false result.

func Any

func Any(children ...Predicate) Predicate

Any evaluates children left-to-right and stops at the first true result.

func Compare

func Compare(operator OperatorName, left, right Operand) Predicate

Compare creates a typed binary comparison.

func Exists

func Exists(path Path) Predicate

Exists reports whether a path was supplied, including an explicit null.

func False

func False() Predicate

False returns an always-false predicate.

func Not

func Not(child Predicate) Predicate

Not negates a predicate.

func True

func True() Predicate

True returns an always-true predicate.

type PredicateFunc

type PredicateFunc func(context.Context, Context) (bool, error)

PredicateFunc adapts a function as an explicit extension predicate.

func (PredicateFunc) Evaluate

func (predicate PredicateFunc) Evaluate(ctx context.Context, facts Context) (bool, error)

Evaluate invokes the adapted predicate function.

type Result

type Result struct {
	Decision     Decision
	MatchedRules []string
	Explanation  []Explanation
	Errors       []error
	Duration     time.Duration
	DerivedFacts Context
}

Result is the complete inspectable evaluation result.

type Rule

type Rule struct {
	ID        RuleID
	Namespace string
	Priority  int
	Tags      []string
	When      Predicate
	Derive    []Fact
}

Rule is a proposition with stable metadata and optional derived facts.

type RuleID

type RuleID string

RuleID is a stable identifier within a rule set.

type RuleSet

type RuleSet struct {
	ID        string
	Namespace string
	Strategy  ConflictStrategy
	Rules     []Rule
}

RuleSet is an independently compiled collection of rules.

type Severity

type Severity uint8

Severity classifies a compile diagnostic.

const (
	// SeverityWarning identifies a non-blocking diagnostic.
	SeverityWarning Severity = iota
	// SeverityError identifies a blocking diagnostic.
	SeverityError
)

type Signature

type Signature struct {
	Left  Kind
	Right Kind
}

Signature declares one exact pair of operand kinds accepted by an Operator.

type Value

type Value struct {
	// contains filtered or unexported fields
}

Value is a typed fact or operand value.

func Bool

func Bool(value bool) Value

Bool returns a boolean value.

func Duration

func Duration(value time.Duration) Value

Duration returns a duration value.

func Float

func Float(value float64) Value

Float returns a floating-point value; contexts reject non-finite values.

func Int

func Int(value int64) Value

Int returns a signed integer value.

func List

func List(values ...Value) Value

List copies its input and returns a collection value.

func Missing

func Missing() Value

Missing returns the absent-path sentinel.

func Null

func Null() Value

Null returns an explicit null value.

func String

func String(value string) Value

String returns a string value.

func Time

func Time(value time.Time) Value

Time returns a time value with its monotonic reading removed.

func (Value) BoolValue

func (v Value) BoolValue() (bool, bool)

BoolValue returns the boolean and whether the kind matches.

func (Value) DurationValue

func (v Value) DurationValue() (time.Duration, bool)

DurationValue returns the duration and whether the kind matches.

func (Value) FloatValue

func (v Value) FloatValue() (float64, bool)

FloatValue returns the float and whether the kind matches.

func (Value) IntValue

func (v Value) IntValue() (int64, bool)

IntValue returns the integer and whether the kind matches.

func (Value) Interface

func (v Value) Interface() any

Interface returns the scalar value or a copied []Value for a list.

func (Value) Kind

func (v Value) Kind() Kind

Kind returns the exact value kind.

func (Value) ListValue

func (v Value) ListValue() ([]Value, bool)

ListValue returns a defensive copy and whether the kind matches.

func (Value) StringValue

func (v Value) StringValue() (string, bool)

StringValue returns the string and whether the kind matches.

func (Value) TimeValue

func (v Value) TimeValue() (time.Time, bool)

TimeValue returns the time and whether the kind matches.

Directories

Path Synopsis
adapters
math module
measurement module
temporal module
Package jsonast exposes the bounded versioned JSON rule-definition DSL.
Package jsonast exposes the bounded versioned JSON rule-definition DSL.

Jump to

Keyboard shortcuts

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