rule

package module
v0.0.3 Latest Latest
Warning

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

Go to latest
Published: Mar 25, 2025 License: MIT Imports: 9 Imported by: 0

README

go-rule logo

go-rule

Go Report Card Go Version Coverage Status Go Reference

A lightweight rule parsing and evaluation library for Go. Define human-readable queries (e.g. score gt 100 and active eq true) and evaluate them against real-world data. Supports parentheses, logical operators (and, or, not), type annotations ([i64], [f64], [d], etc.), function calls, and more.

Key features

  • Simple query language (operators like eq, gt, lt, pr, in, co, sw, ew, etc.).
  • Built on ANTLR4 grammar.
  • Support for typed values ([i64]"123", [f64]"123.45", [d]"12.34") and typed comparisons (strict type checks).
  • Handle decimals via shopspring/decimal.
  • Evaluate queries with dynamic data (like JSON objects, struct fields, or custom function results).
  • Easily embed in your own projects and run rule-based filtering or validations.

Table of Contents

  1. Installation
  2. Quick Start Example
  3. Usage
  4. Advanced Examples
  5. Testing
  6. Full API Documentation
  7. License

Installation

go get github.com/sky93/go-rule

Quick Start Example

There's a self-contained example in _example/simple/main.go.

Below is a short version:

package main

import (
    "fmt"
    "log"

    "github.com/sky93/go-rule"
)

func main() {
    // A simple query
    input := `book_pages gt 100 and (language eq "en" or language eq "fr") and price pr and in_stock eq true`

    // Parse the query
    parsedRule, err := rule.ParseQuery(input, nil)
    if err != nil {
        log.Fatalf("Parse error: %v", err)
    }

    fmt.Println("Discovered parameters:")
    // Prepare evaluations with actual data
    values := []rule.Evaluation{
        {
            Param:  parsedRule.Params[0], // e.g. "book_pages"
            Result: 150,
        },
        {
            Param:  parsedRule.Params[1], // e.g. "language"
            Result: "en",
        },
        {
            Param:  parsedRule.Params[2], // e.g. "price"
            Result: 10.0,
        },
        {
            Param:  parsedRule.Params[3], // e.g. "in_stock"
            Result: true,
        },
    }

    // Evaluate
    ok, err := parsedRule.Evaluate(values)
    if err != nil {
        log.Fatalf("Evaluation error: %v", err)
    }
    fmt.Printf("Evaluation => %v\n", ok)
}

Output:

Discovered parameters:
 ... (various parameter info) ...
Evaluation => true

Usage

Parsing Queries

Use rule.ParseQuery(queryString, config) to parse a textual rule:

import "github.com/sky93/go-rule"

ruleSet, err := rule.ParseQuery(
    `(usr_id eq 100 or usr_id eq 101) and amount gt [d]"12.34"`, 
    nil,
)
// ruleSet is a `Rule` struct with an internal expression tree + discovered Params.
  • queryString can contain:
    • Comparison: attrName operator value
    • Logical: ( ... ) and/or ( ... ), plus not
    • Type annotation: [i64]"123", [f64]"123.45", [d]"12.34", [ui] (and more)
    • Presence: someField pr (true if field is present)
    • String operators: co (contains), sw (starts with), ew (ends with), in

Parsing Errors
If the syntax is invalid, ParseQuery returns an error. For example, unbalanced parentheses or unknown tokens.

Evaluating a Parsed Rule

Once parsed, you get a rule.Rule that contains:

  • Params: The discovered parameters (name, operator, typed expression, etc.).

To evaluate:

  1. Identify each parameter in ruleSet.Params.
  2. Construct a slice of rule.Evaluation items, each linking a Param from ruleSet.Params to an actual Result from your data.
  3. Call ruleSet.Evaluate(evals []Evaluation) => returns (bool, error).
ok, err := ruleSet.Evaluate([]rule.Evaluation{
    {
        Param:  ruleSet.Params[0], // param with Name="age"
        Result: 20,
    },
    {
        Param:  ruleSet.Params[1], // param with Name="can_drive"
        Result: true,
    },
})
fmt.Println(ok)   // => true
fmt.Println(err)  // => nil
Working with Typed Values

You can specify a type annotation in the query, for example [i64]"123", [f64]"123.45", [d]"12.34".

Strict Type Checking:
If a query param is [f64]"123.45", the library enforces that your provided Result is float64, otherwise you'll get a type mismatch error.

Supported type annotations:

Annotation Meaning / Go Type
[i64] int64
[ui64] uint64
[i] int
[ui] uint
[i32] int32
[ui32] uint32
[f64] float64
[f32] float32
[d] decimal.Decimal
[s] string
Function Calls

You can have queries like:

get_author("Song of Myself") eq "Walt Whitman"

Here:

  • get_author is the function name.
  • "Song of Myself" is function argument.
  • eq is equal operator.
  • "Walt Whitman" is the compare value.

ParseQuery sets Parameter.InputType = FunctionCall with Parameter.FunctionArguments.
For final evaluation, you must supply a single numeric/string/boolean (etc.) Result for Param:

// If we have: get_author("Song of Myself") eq "Walt Whitman"
param := ruleSet.Params[0]
// param.FunctionArguments => e.g. [ {ArgTypeString, Value: "Song of Myself"} ]

ok, err := ruleSet.Evaluate([]rule.Evaluation{
  {
    Param:  param,
    Result: "Walt Whitman", // the real function result
  },
})
// => true
Supported Operators
Operator Meaning
eq equals
ne not equals
gt greater than
lt less than
ge greater or equal
le less or equal
co contains (substring)
sw starts with
ew ends with
in "in" check (substring)
pr present (non-nil check)

Logical: and, or, plus optional not prefix.
Parentheses: ( expr )

Debug/Logging

You can pass a config with DebugMode=true:

ruleSet, err := rule.ParseQuery(`age gt 18`, &rule.Config{DebugMode: true})
if err != nil {
    log.Fatal(err)
}
// Evaluate => will print debug messages to stdout

Advanced Examples

See ruleEngine_test.go for in-depth test scenarios:

  • Decimal usage
  • Complex boolean logic
  • Strict type checking
  • Absent parameters vs. pr
  • Nested parentheses
  • Function calls with arguments
  • Error handling

Also, _example/simple/main.go shows a small command-line usage.


Testing

This repository includes unit tests in ruleEngine_test.go. Run them with:

go test ./...

You’ll see coverage for query parsing, expression evaluation, typed operators, error handling, etc.


Full API Documentation

Browse all public functions, types, and methods on pkg.go.dev or read the doc comments in the source code.


License

This project is licensed under the MIT License.
Copyright © 2025 Sepehr Mohaghegh.

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files ... full MIT license text.

Logo Attribution & Dependencies

Documentation

Index

Examples

Constants

This section is empty.

Variables

View Source
var (
	// ErrorUnknownDecimalOperator is returned when a decimal comparison operator is unrecognized.
	ErrorUnknownDecimalOperator = errors.New("unknown decimal operator")

	// ErrorTypeMismatch is returned when two values fail a strict type check.
	ErrorTypeMismatch = errors.New("compare type mismatch")

	// ErrorUnknownStringOperator is returned when a string-based operator (co, sw, ew, in, pr) is unknown.
	ErrorUnknownStringOperator = errors.New("unknown string operator")

	// ErrorNoExpression is returned when the expression tree is nil or missing.
	ErrorNoExpression = errors.New("there is no expression")

	// ErrorInvalidValue is returned for invalid numeric/string parse attempts.
	ErrorInvalidValue = errors.New("invalid value")

	// ErrorUnknownType is returned for unknown or unsupported type annotations.
	ErrorUnknownType = errors.New("invalid value")

	// ErrorInvalidFunctionCall is returned when a function call parse is incomplete or malformed.
	ErrorInvalidFunctionCall = errors.New("invalid function call")

	// ErrorInvalidExpression is returned when the parse tree visitor hits an unexpected node.
	ErrorInvalidExpression = errors.New("invalid expression")

	// ErrorInvalidOperator is returned for an unknown or unsupported operator in a comparison.
	ErrorInvalidOperator = errors.New("invalid operator")

	// ErrorSyntaxError is used for general syntax errors in the input query.
	ErrorSyntaxError = errors.New("syntax error")
)

Error variables used throughout the package:

Functions

This section is empty.

Types

type ArgumentType

type ArgumentType int

ArgumentType indicates how to interpret a Parameter or FunctionArgument's value (string, int, decimal, etc.).

const (
	// ArgTypeUnknown means the type could not be determined or does not match known annotations.
	ArgTypeUnknown ArgumentType = iota

	// ArgTypeString indicates a string type.
	ArgTypeString

	// ArgTypeInteger indicates a standard int type (no specific bit width).
	ArgTypeInteger

	// ArgTypeUnsignedInteger indicates a standard uint type (no specific bit width).
	ArgTypeUnsignedInteger

	// ArgTypeFloat64 indicates a float64 type.
	ArgTypeFloat64

	// ArgTypeBoolean indicates a bool type.
	ArgTypeBoolean

	// ArgTypeNull indicates a nil or null value.
	ArgTypeNull

	// ArgTypeList indicates some bracketed list syntax, e.g. [1,2,3].
	ArgTypeList

	// ArgTypeFloat32 indicates a float32 type.
	ArgTypeFloat32

	// ArgTypeInteger32 indicates a 32-bit integer type (int32).
	ArgTypeInteger32

	// ArgTypeInteger64 indicates a 64-bit integer type (int64).
	ArgTypeInteger64

	// ArgTypeUnsignedInteger64 indicates a 64-bit unsigned integer type (uint64).
	ArgTypeUnsignedInteger64

	// ArgTypeUnsignedInteger32 indicates a 32-bit unsigned integer type (uint32).
	ArgTypeUnsignedInteger32

	// ArgTypeDecimal indicates a decimal.Decimal type (from shopspring/decimal).
	ArgTypeDecimal
)

func (ArgumentType) String

func (at ArgumentType) String() string

String returns the string representation of the ArgumentType (for debugging).

type Config

type Config struct {
	DebugMode bool
}

Config controls optional ParseQuery() behaviors.

Fields:

  • DebugMode: if true, evaluation debug lines are printed to stdout

type Evaluation

type Evaluation struct {
	Param  Parameter
	Result any
}

Evaluation couples a parsed Parameter with an actual value for runtime evaluation. The Evaluate() method will iterate over these pairs to resolve the final query result.

type FunctionArgument

type FunctionArgument struct {
	ArgumentType ArgumentType // The determined type (ArgTypeString, ArgTypeInteger, etc.)
	Value        any          // The actual parsed value
}

FunctionArgument represents a single argument in a function call, e.g. get_author("abc", 123).

type InputType

type InputType int

InputType indicates if a Parameter is for a function call (FunctionCall) or a direct attribute expression (Expression).

const (
	// FunctionCall indicates the Parameter references a user-defined function plus arguments, e.g. get_user("abc").
	FunctionCall InputType = iota

	// Expression indicates a standard attribute-based comparison, e.g. age gt 30.
	Expression
)

func (InputType) String

func (it InputType) String() string

String returns the string representation of the InputType, either "FunctionCall" or "Expression".

type Parameter

type Parameter struct {
	Name              string
	InputType         InputType
	FunctionArguments []FunctionArgument

	Expression ArgumentType
	// contains filtered or unexported fields
}

Parameter holds all information needed to represent a single condition or function invocation within a query. For example, "age gt 18" or "get_author("Book") eq "Walt Whitman".

Fields:

  • id: unique ID for internal evaluation references
  • Name: the attribute name or function name
  • InputType: either Expression or FunctionCall
  • FunctionArguments: slice of arguments if it's a function call
  • strictTypeCheck: indicates if type annotations must be strictly enforced
  • Expression: if InputType == Expression, describes the type annotation discovered
  • operator: the SCIM-like operator (eq, gt, etc.)
  • compareValue: the RHS value for the comparison (number, string, decimal, etc.)

type Rule

type Rule struct {
	Params []Parameter
	// contains filtered or unexported fields
}

Rule represents the final compiled query. After parsing, a Rule contains:

  • Params: all discovered parameters
  • exprTree: the root of the expression tree for logical ops
  • debugMode: flag enabling debug prints during evaluation

func ParseQuery

func ParseQuery(input string, config *Config) (Rule, error)

ParseQuery takes a SCIM-like query (e.g. `age gt 30 and (lang eq "en" or lang eq "fr")`) and compiles it into a Rule object. Optional config can enable DebugMode.

If parsing fails due to syntax errors or other issues, an error is returned. Otherwise, the returned Rule can be used for Evaluate().

Example

Example usage test (just to show quick usage)

rule, err := ParseQuery(`(active eq true) and (score gt 50)`, nil)
if err != nil {
	fmt.Println("Error:", err)
	return
}
// Supply actual param values:
evals := []Evaluation{
	{
		Param:  rule.Params[0], // active
		Result: true,
	},
	{
		Param:  rule.Params[1], // score
		Result: 75,
	},
}
ok, err := rule.Evaluate(evals)
if err != nil {
	fmt.Println("Error:", err)
	return
}
fmt.Println(ok)
Output:

true

func (*Rule) Evaluate

func (g *Rule) Evaluate(values []Evaluation) (bool, error)

Evaluate applies the stored exprTree logic to a slice of Evaluation structs. Each Evaluation links a Parameter in g.Params to a real runtime value. The result is a bool.

Directories

Path Synopsis
_example
simple command
internal

Jump to

Keyboard shortcuts

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