rulekit

package module
v0.0.0-...-5986cb2 Latest Latest
Warning

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

Go to latest
Published: Feb 18, 2026 License: MIT Imports: 14 Imported by: 5

README

Rulekit icon

Rulekit

Rulekit is a flexible expression-based rules engine for Go, providing a simple and expressive syntax for defining business rules that can be evaluated against key-value data.

Rulekit Demo

Overview

This package implements an expression-based rules engine that evaluates expressions against a key-value map of values, returning a true/false result with additional context.

Rules follow a simple and intuitive syntax. For example, the following rule:

domain matches /example\.com$/

When evaluated against:

  • map[string]any{"domain": "example.com"} → returns true
  • map[string]any{"domain": "qpoint.io"} → returns false

In this document, domain is referred to as a field and /example\.com$/ as a value.

Rulekit supports a flexible syntax where fields and values may appear on either side of an operator:

  • field operator value (e.g., domain == "example.com")
  • value operator field (e.g., "example.com" == domain)
  • value operator value (e.g., 123 == 123)
  • field operator field (e.g., src.port == dst.port)

A field on its own (without an operator) will check if the field contains a non-zero value. For example: hash && version > 1 will check if the hash field is non-zero and the version is greater than 1.

Usage Example

import "github.com/qpoint-io/rulekit"

// ...

r, err := rule.Parse(`domain matches /example\.com$/ and port == 8080`)
if err != nil { /* ... */ }

// define input data
input := rulekit.KV{
    "domain": "example.com",
    "port": 8080,
}

// evaluate the rule
result := r.Eval(&rulekit.Ctx{KV: inputData})

// check for errors
if !result.Ok() {
    fmt.Printf("error evaluating rule: %v\n", result.Error)
} else {
    if result.Pass() {
        fmt.Println("PASS!")
    } else if result.Fail() {
        fmt.Println("FAIL :(")
    }
}

Result

When a rule is evaluated, it returns a Result struct containing:

  • Value: The evaluated value, usually a boolean
  • Error: Any evaluation errors such as fields missing from the KV map
  • EvaluatedRule: The sub-rule that determined the returned value. Useful for debugging and understanding which part of a complex rule caused the result.

The Result also provides additional helper methods:

  • Pass(): Returns true if the rule returns true/a non-zero value with no errors
  • Fail(): Returns true if the rule returns false/a zero value with no errors
  • Ok(): Returns true if the rule executed with no error

Supported Operators

Operator Alias Description
or || Logical OR
and && Logical AND
not ! Logical NOT
() Parentheses for grouping
== eq Equal to
!= ne Not equal to
> gt Greater than
>= ge Greater than or equal to
< lt Less than
<= le Less than or equal to
contains Check if a value contains another value
in Check if a value is contained within an array or an IP within a CIDR
matches Match against a regular expression

Supported Types

Basic values
Type Used As Example Description
bool VALUE, FIELD true Valid values: true, false
number VALUE, FIELD 8080 Integer or float. Parsed as either int64 or uint64 if out of range for int64, or float64 if float.
string VALUE, FIELD "domain.com" A double-quoted string. Quotes may be escaped with a backslash: "a string \"with\" quotes". Any quoted value is parsed as a string.
IP address VALUE, FIELD 192.168.1.1, 2001:db8:3333:4444:cccc:dddd:eeee:ffff An IPv4, IPv6, or an IPv6 dual address. Maps to Go type: net.IP
CIDR VALUE 192.168.1.0/24, 2001:db8:3333:4444:cccc:dddd:eeee:ffff/64 An IPv4 or IPv6 CIDR block. Maps to Go type: *net.IPNet
Hexadecimal string VALUE, FIELD 12:34:56:78:ab (MAC address), 504f5354 (hex string "POST") A hexadecimal string, optionally separated by colons.
Regex VALUE /example\.com$/ A Go-style regular expression. Must be surrounded by forward slashes. May not be quoted with double quotes (otherwise it will be parsed as a string). Maps to Go type: *regexp.Regexp
Constructs
Type Used As Example Description
Array VALUE [1, "string", true] An array of mixed value types. Can be used with most operators including in and contains.
Function VALUE starts_with(url, "https://") A function call with optional arguments. Can be built-in or custom.
Macro VALUE isValidRequest() A zero-argument function that encapsulates a predefined rule.

Macros

Macros can be used for complex or commonly-used rules. They are defined in the evaluation context:

// create a macro
isInternalAPI, err := rulekit.Parse(`domain matches /\.internal\.example\.com$/ or ip in 10.0.0.0/8`)
if err != nil { /* ... */ }

// create a rule that uses the macro
rule, err := rulekit.Parse(`isInternalAPI() && user != "root"`)
if err != nil { /* ... */ }

// evaluate the rule, making sure to pass the macro in the eval context
result := rule.Eval(&rulekit.Ctx{
    Macros: map[string]rulekit.Rule{
        "isInternalAPI": isInternalAPI,
    },
    KV: rulekit.KV{
        "user": user,
        // ...
    },
})

Functions

Functions can be called inside rules and used as value objects. Functions may accept zero or more arguments.

Standard library

Rulekit comes with a built-in standard library of functions:

Function Description Example
starts_with(value, prefix) Checks if a value starts with the given prefix. Works with strings, numbers, and other types by converting them to strings. starts_with(url, "https://")
Custom Functions

Custom functions may be used to extend Rulekit with additional functionality. Note that functions only have access to their arguments and do not have access to the context KV map. Rulekit will validate the function's arguments per the provided spec before executing the handler.

// define a custom function
customFuncs := map[string]*rulekit.Function{
    "randomInt": {
        Args: []rulekit.FunctionArg{
            {Name: "min"},
            {Name: "max"},
        },
        Eval: func(args map[string]any) rulekit.Result {
            // use the rulekit.IndexFuncArg helper to retrieve args and validate types.
            // rulekit.IndexFuncArg[any] will skip type validation.
            min, err := rulekit.IndexFuncArg[int64](args, "min")
            if err != nil {
                return rulekit.Result{Error: err}
            }

            max, err := rulekit.IndexFuncArg[int64](args, "max")
            if err != nil {
                return rulekit.Result{Error: err}
            }

            num := rand.IntN(max-min) + min
            return Result{
                Value: num,
            }
        },
    },
}

// call the function in a rule
rule, err := rulekit.Parse(`randomInt(10, 20) == 15`)
if err != nil { /* ... */ }

result1, err := rule.Eval(&rulekit.Ctx{
    Functions: customFuncs,
})
if err != nil { /* ... */ }

if rule.Pass() {
    // the random number is 15!
}

License

MIT

Image showing \

Documentation

Index

Constants

This section is empty.

Variables

View Source
var ErrInvalidOperation = errors.New("invalid operation")
View Source
var StdlibFuncs = map[string]*Function{
	"starts_with": {
		Args: []FunctionArg{
			{Name: "value"},
			{Name: "prefix"},
		},
		Eval: func(args map[string]any) Result {
			value, err := IndexFuncArg[any](args, "value")
			if err != nil {
				return Result{Error: err}
			}
			prefix, err := IndexFuncArg[any](args, "prefix")
			if err != nil {
				return Result{Error: err}
			}

			return Result{
				Value: strings.HasPrefix(fmt.Sprint(value), fmt.Sprint(prefix)),
			}
		},
	},
}

Functions

func IndexFuncArg

func IndexFuncArg[T any](args map[string]any, name string) (T, error)

func IndexKV

func IndexKV(m KV, key string) (any, bool)

IndexKV gets element key from a map, interpreting it as a path if it contains a period.

func SetDebugLevel

func SetDebugLevel(level int)

SetDebugLevel sets the debug verbosity level

func SetDebugWriter

func SetDebugWriter(w io.Writer)

func SetErrorVerbose

func SetErrorVerbose(verbose bool)

SetErrorVerbose enables or disables verbose error reporting

Types

type ArrayValue

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

func (*ArrayValue) Eval

func (a *ArrayValue) Eval(ctx *Ctx) Result

func (*ArrayValue) String

func (a *ArrayValue) String() string

type Ctx

type Ctx struct {
	KV        KV
	Macros    map[string]Rule
	Functions map[string]*Function
}

func (*Ctx) Eval

func (c *Ctx) Eval(r Rule) Result

func (*Ctx) Validate

func (c *Ctx) Validate() error

type ErrInvalidFunctionArg

type ErrInvalidFunctionArg struct {
	Name     string
	Expected string
	Got      string
}

func (*ErrInvalidFunctionArg) Error

func (e *ErrInvalidFunctionArg) Error() string

type ErrMissingFields

type ErrMissingFields struct {
	Fields set.Set[string]
}

func (ErrMissingFields) Error

func (e ErrMissingFields) Error() string

type FieldValue

type FieldValue string

func (FieldValue) Eval

func (f FieldValue) Eval(ctx *Ctx) Result

func (FieldValue) String

func (f FieldValue) String() string

type Function

type Function struct {
	// Args is an optional list of arguments that the function expects.
	// If set, rulekit will ensure validity of the arguments and pass them as a named map to the Eval function.
	Args []FunctionArg
	// Eval is the function that will be called with the arguments.
	// EvaluatedRule will be set by Rulekit.
	Eval func(map[string]any) Result
}

type FunctionArg

type FunctionArg struct {
	Name string
}

type FunctionValue

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

func (*FunctionValue) Eval

func (f *FunctionValue) Eval(ctx *Ctx) Result

func (*FunctionValue) String

func (f *FunctionValue) String() string

func (*FunctionValue) ValidateStdlibFnArgs

func (f *FunctionValue) ValidateStdlibFnArgs() error

type HexString

type HexString struct {
	Bytes []byte
	// contains filtered or unexported fields
}

HexString represents a hex-encoded string retaining the original input string

func ParseHexString

func ParseHexString(s string) (HexString, error)

func (HexString) String

func (h HexString) String() string

type KV

type KV = map[string]any

type LiteralValue

type LiteralValue[T any] struct {
	// contains filtered or unexported fields
}

func (*LiteralValue[T]) Eval

func (l *LiteralValue[T]) Eval(ctx *Ctx) Result

func (*LiteralValue[T]) String

func (l *LiteralValue[T]) String() string

type ParseError

type ParseError struct {
	Line       int
	Column     int
	Message    string
	Input      string
	Suggestion string
}

func (*ParseError) Error

func (e *ParseError) Error() string

type Result

type Result struct {
	Value         any
	EvaluatedRule Rule
	Error         error
}

func (Result) Fail

func (r Result) Fail() bool

Fail returns true if the rule is ok and returns a zero value. This is usually used for boolean rules.

func (Result) Ok

func (r Result) Ok() bool

Ok returns true if the rule was able to evaluate without error.

func (Result) Pass

func (r Result) Pass() bool

Pass returns true if the result is ok with a non-zero value. This is usually used for boolean rules.

type Rule

type Rule interface {
	// Evaluates the rule with the context
	Eval(*Ctx) Result
	// String representation of the rule
	String() string
}

func MustParse

func MustParse(str string) Rule

func Parse

func Parse(str string) (Rule, error)

Parse parses a rule expression and returns a Rule.

type RuleFunc

type RuleFunc func(*Ctx) Result

func (RuleFunc) Eval

func (f RuleFunc) Eval(ctx *Ctx) Result

func (RuleFunc) String

func (f RuleFunc) String() string

type ValueParseError

type ValueParseError struct {
	TokenType int
	Value     string
	Err       error
}

func (ValueParseError) Error

func (e ValueParseError) Error() string

Directories

Path Synopsis

Jump to

Keyboard shortcuts

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