evaluator

package module
v0.0.2 Latest Latest
Warning

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

Go to latest
Published: Jan 25, 2026 License: MIT Imports: 6 Imported by: 0

README

Evaluator

The evaluator package implements a small expression language for querying Go structs. Expressions are represented as Go structs that can be combined using logical operators. Comparison expressions support both numeric and string values.

Why use this?

The Evaluator library allows you to:

  • Dynamic Filtering: Define filtering logic at runtime (e.g., from configuration files or user input) rather than hardcoding it.
  • Safe Querying: Expose a simple, safe query capability to end-users without exposing full SQL or code execution.
  • Portability: Serialize queries to JSON to store them in a database or send them over a network.
  • Type Safety: Works with standard Go structs and types.
  • Custom Logic: Extend the evaluator with custom functions using FunctionExpression.

Installation

To use the library in your Go project:

go get github.com/arran4/go-evaluator

To install the command-line tools:

go install github.com/arran4/go-evaluator/cmd/csvfilter@latest
go install github.com/arran4/go-evaluator/cmd/jsonlfilter@latest
go install github.com/arran4/go-evaluator/cmd/jsontest@latest
go install github.com/arran4/go-evaluator/cmd/yamltest@latest

Features

  • Equality and inequality checks (Is, IsNot)
  • Numeric and lexical comparisons (GT, GTE, LT, LTE)
  • Membership checks with Contains
  • Logical composition using And, Or and Not
  • Custom Functions: Execute arbitrary logic via FunctionExpression
  • JSON serialisation for easy storage or transmission of queries

Basic Usage

Create a query using the provided expression types and call Evaluate with your target struct:

q := evaluator.Query{
    Expression: &evaluator.AndExpression{Expressions: []evaluator.Query{
        {Expression: &evaluator.IsExpression{Field: "Name", Value: "bob"}},
        {Expression: &evaluator.GreaterThanExpression{Field: "Age", Value: 30}},
    }},
}

matched := q.Evaluate(&User{Name: "bob", Age: 35})

Integration Example

This example demonstrates how to integrate the evaluator into an application to filter a list of structs based on a dynamic query string (e.g., from user input).

package main

import (
	"fmt"
	"log"

	"github.com/arran4/go-evaluator/parser/simple"
)

type Product struct {
	Name     string
	Category string
	Price    float64
	InStock  bool
}

func main() {
	// 1. Data source
	products := []Product{
		{"Laptop", "Electronics", 999.99, true},
		{"Coffee Mug", "Kitchen", 12.50, true},
		{"Headphones", "Electronics", 49.99, false},
	}

	// 2. Query (could come from user input, API, config, etc.)
	// Find all Electronics under $1000
	queryString := `Category is "Electronics" and Price < 1000`

	// 3. Parse the query
	query, err := simple.Parse(queryString)
	if err != nil {
		log.Fatal(err)
	}

	// 4. Filter the list
	var filtered []Product
	for _, p := range products {
		if query.Evaluate(&p) {
			filtered = append(filtered, p)
		}
	}

	// 5. Use results
	for _, p := range filtered {
		fmt.Printf("Found: %s ($%.2f)\n", p.Name, p.Price)
	}
}

Custom Functions

You can execute arbitrary logic (like math, formatting, or lookups) by implementing the Function interface and using FunctionExpression.

// 1. Implement Function interface
type SumFunc struct{}
func (s *SumFunc) Call(args ...interface{}) (interface{}, error) {
    sum := 0.0
    for _, arg := range args {
        // ... type assertion and summing ...
    }
    return sum, nil
}

// 2. Use in Expression
expr := evaluator.FunctionExpression{
    Func: &SumFunc{},
    Args: []evaluator.Term{
        evaluator.Constant{Value: 10},
        evaluator.Constant{Value: 20},
    },
}

result, _ := expr.Evaluate(nil) // 30

JSON Queries

Queries can be marshalled to and from JSON. This is handy for configuration files or network APIs.

js := `{
  "Expression": {
    "Type": "Contains",
    "Expression": {
      "Field": "Tags",
      "Value": "go"
    }
  }
}`
var q evaluator.Query
if err := json.Unmarshal([]byte(js), &q); err != nil {
    log.Fatal(err)
}

Expression Guide

Each query expression implements the Expression interface. The table below lists the available types and their purpose:

Type Purpose
Is / IsNot Check equality or inequality of a field
GT / GTE Numeric or lexical "greater than" comparisons
LT / LTE Numeric or lexical "less than" comparisons
Contains Test that a slice field contains a value
And / Or / Not Compose other expressions logically
FunctionExpression Execute a custom Function implementation

Example usage:

evaluator.Query{Expression: &evaluator.NotExpression{Expression: evaluator.Query{
    Expression: &evaluator.IsExpression{Field: "Deleted", Value: true},
}}}

CLI Usage & Syntax

The command-line tools use a simple string syntax to define expressions.

Operators:

  • is, is not: Equality checks
  • >, >=, <, <=: Numeric/Lexical comparison
  • contains: Checks if a list contains a value
  • and, or, not: Logical operators
  • (...): Grouping

Values:

  • Strings: "value"
  • Numbers: 123, 45.67
  • Booleans: true, false

Examples:

  • Status is "active"
  • Age >= 18
  • Tags contains "admin"
  • (Role is "admin" or Role is "moderator") and Active is true

Command-line Tools

The project includes small utilities for working with common data formats.

csvfilter

Filters CSV rows based on headers.

Usage:

# Given data.csv:
# name,age,city
# alice,30,ny
# bob,25,sf

csvfilter -e 'age > 28' data.csv
# Output:
# name,age,city
# alice,30,ny
jsonlfilter

Filters newline-delimited JSON records.

Usage:

# Given logs.jsonl:
# {"level":"info", "msg":"started"}
# {"level":"error", "msg":"failed"}

jsonlfilter -e 'level is "error"' logs.jsonl
# Output:
# {"level":"error", "msg":"failed"}
jsontest

Evaluates a single JSON document (or multiple files). Returns exit code 0 on match, 1 otherwise.

Usage:

# Check if config.json is valid for production
jsontest -e 'environment is "production" and debug is false' config.json
if [ $? -eq 0 ]; then
    echo "Production ready"
fi
yamltest

Like jsontest but for YAML documents.

Usage:

yamltest -e 'replicas >= 3' deployment.yaml

Running Tests

Run go test ./... to execute the unit tests.

License

This project is licensed under the MIT License.

Documentation

Overview

Package evaluator provides a simple expression language for querying Go structs. Expressions are represented as Go structs which can be evaluated against arbitrary values or composed together using logical operators. The package also supports marshaling and unmarshaling expressions to and from JSON for storage or transmission.

Example (SimpleParser)

Example_simpleParser demonstrates parsing a string query.

package main

import (
	"fmt"
	"log"

	"github.com/arran4/go-evaluator/parser/simple"
)

func main() {
	queryString := `Category is "Electronics" and Price < 1000`
	query, err := simple.Parse(queryString)
	if err != nil {
		log.Fatal(err)
	}

	type Product struct {
		Name     string
		Category string
		Price    float64
	}

	result, err := query.Evaluate(&Product{
		Name:     "Laptop",
		Category: "Electronics",
		Price:    999.99,
	})
	if err != nil {
		log.Fatal(err)
	}
	fmt.Println(result)
}
Output:

true

Index

Examples

Constants

This section is empty.

Variables

This section is empty.

Functions

func Compare added in v0.0.2

func Compare(a, b interface{}) (int, error)

Compare returns an integer comparing two values. The result will be 0 if a==b, -1 if a < b, and +1 if a > b.

func IsTruthy added in v0.0.2

func IsTruthy(v interface{}) (bool, error)

IsTruthy checks if a value is considered "true" in the expression language. It tries to accept widely accepted truthy values, including parsing strings.

Types

type AndExpression

type AndExpression struct {
	Expressions []Query `json:"Expressions"`
}

AndExpression evaluates to true only if all child Expressions do as well.

func (AndExpression) Evaluate

func (e AndExpression) Evaluate(i interface{}, opts ...any) (bool, error)

type BoolType added in v0.0.2

type BoolType struct {
	Term Term
}

BoolType converts the term result to a boolean.

func (BoolType) Evaluate added in v0.0.2

func (b BoolType) Evaluate(i interface{}, opts ...any) (interface{}, error)

type Comparator added in v0.0.2

type Comparator interface {
	Compare(other interface{}) (int, error)
}

Comparator allows for custom comparison logic.

type ComparisonExpression added in v0.0.2

type ComparisonExpression struct {
	LHS       Term
	RHS       Term
	Operation string // eq, neq, gt, gte, lt, lte, contains, icontains
}

ComparisonExpression evaluates a comparison between two Terms.

func (ComparisonExpression) Evaluate added in v0.0.2

func (e ComparisonExpression) Evaluate(i interface{}, opts ...any) (bool, error)

type Constant added in v0.0.2

type Constant struct {
	Value interface{}
}

Constant represents a constant value term.

func (Constant) Evaluate added in v0.0.2

func (c Constant) Evaluate(i interface{}, opts ...any) (interface{}, error)

type ContainsExpression

type ContainsExpression struct {
	Field string
	Value interface{}
}

ContainsExpression checks whether a slice field contains the given Value, or if a string field contains the given substring.

func (ContainsExpression) Evaluate

func (e ContainsExpression) Evaluate(i interface{}, opts ...any) (bool, error)

type Context added in v0.0.2

type Context struct {
	Functions map[string]Function
	Variables map[string]interface{}
}

Context holds execution context for the evaluator, including variables and functions.

func GetContext added in v0.0.2

func GetContext(opts ...any) *Context

GetContext extracts the Context from the variadic options, or returns a default one.

type Expression

type Expression interface {
	// Evaluate returns true if the expression matches the supplied value.
	Evaluate(i interface{}, opts ...any) (bool, error)
}

Expression represents a single boolean expression that can be evaluated against a struct value.

type Field added in v0.0.2

type Field struct {
	Name string
}

Field represents a field lookup term.

func (Field) Evaluate added in v0.0.2

func (f Field) Evaluate(i interface{}, opts ...any) (interface{}, error)

type Function added in v0.0.2

type Function interface {
	Call(args ...interface{}) (interface{}, error)
}

Function defines the interface for a function that can be called by FunctionExpression.

type FunctionExpression added in v0.0.2

type FunctionExpression struct {
	Name string
	Func Function
	Args []Term
}

FunctionExpression represents a function call.

func (FunctionExpression) Evaluate added in v0.0.2

func (f FunctionExpression) Evaluate(i interface{}, opts ...any) (interface{}, error)
Example

ExampleFunctionExpression_Evaluate demonstrates how to use the FunctionExpression to execute custom logic within the evaluator.

// 1. Define a custom function
// This function sums all numeric arguments
sumFunc := &SumFunction{}

// 2. Build the expression tree
// We want to calculate: Sum(10, 20)
expr := evaluator.FunctionExpression{
	Func: sumFunc,
	Args: []evaluator.Term{
		evaluator.Constant{Value: 10},
		evaluator.Constant{Value: 20},
	},
}

// 3. Evaluate the expression
result, err := expr.Evaluate(nil)
if err != nil {
	log.Fatal(err)
}
fmt.Printf("Sum: %v\n", result)

// 4. Nested usage
// Calculate: Sum(Sum(5, 5), 10) -> Sum(10, 10) -> 20
nestedExpr := evaluator.FunctionExpression{
	Func: sumFunc,
	Args: []evaluator.Term{
		evaluator.FunctionExpression{
			Func: sumFunc,
			Args: []evaluator.Term{
				evaluator.Constant{Value: 5},
				evaluator.Constant{Value: 5},
			},
		},
		evaluator.Constant{Value: 10},
	},
}

resultNested, err := nestedExpr.Evaluate(nil)
if err != nil {
	log.Fatal(err)
}
fmt.Printf("Nested Sum: %v\n", resultNested)
Output:

Sum: 30
Nested Sum: 20

type Getter added in v0.0.2

type Getter interface {
	Get(name string) (interface{}, error)
}

Getter interface allows for dynamic field retrieval.

type GreaterThanExpression

type GreaterThanExpression struct {
	Field string
	Value interface{}
	// contains filtered or unexported fields
}

GreaterThanExpression compares Field to Value and succeeds when the field is greater than the provided value.

func (*GreaterThanExpression) Evaluate

func (e *GreaterThanExpression) Evaluate(i interface{}, opts ...any) (bool, error)

type GreaterThanOrEqualExpression

type GreaterThanOrEqualExpression struct {
	Field string
	Value interface{}
	// contains filtered or unexported fields
}

GreaterThanOrEqualExpression succeeds when Field is greater than or equal to Value.

func (*GreaterThanOrEqualExpression) Evaluate

func (e *GreaterThanOrEqualExpression) Evaluate(i interface{}, opts ...any) (bool, error)

type IContainsExpression added in v0.0.2

type IContainsExpression struct {
	Field string
	Value interface{}
}

IContainsExpression checks whether a string field contains the given substring (case-insensitive).

func (IContainsExpression) Evaluate added in v0.0.2

func (e IContainsExpression) Evaluate(i interface{}, opts ...any) (bool, error)

type If added in v0.0.2

type If struct {
	Condition Term
	Then      Term
	Else      Term
}

If evaluates Condition. If true, evaluates Then, else evaluates Else.

func (If) Evaluate added in v0.0.2

func (e If) Evaluate(i interface{}, opts ...any) (interface{}, error)

type IsExpression

type IsExpression struct {
	Field string
	Value interface{}
}

IsExpression succeeds when the specified Field equals Value.

func (IsExpression) Evaluate

func (e IsExpression) Evaluate(i interface{}, opts ...any) (bool, error)

type IsNotExpression

type IsNotExpression struct {
	Field string
	Value interface{}
}

IsNotExpression succeeds when the specified Field does not equal Value.

func (IsNotExpression) Evaluate

func (e IsNotExpression) Evaluate(i interface{}, opts ...any) (bool, error)

type LessThanExpression

type LessThanExpression struct {
	Field string
	Value interface{}
	// contains filtered or unexported fields
}

LessThanExpression succeeds when Field is strictly less than Value.

func (*LessThanExpression) Evaluate

func (e *LessThanExpression) Evaluate(i interface{}, opts ...any) (bool, error)

type LessThanOrEqualExpression

type LessThanOrEqualExpression struct {
	Field string
	Value interface{}
	// contains filtered or unexported fields
}

LessThanOrEqualExpression succeeds when Field is less than or equal to Value.

func (*LessThanOrEqualExpression) Evaluate

func (e *LessThanOrEqualExpression) Evaluate(i interface{}, opts ...any) (bool, error)

type NotExpression

type NotExpression struct {
	Expression Query `json:"Expression"`
}

NotExpression inverts the result of a single child Expression.

func (NotExpression) Evaluate

func (e NotExpression) Evaluate(i interface{}, opts ...any) (bool, error)

type OrExpression

type OrExpression struct {
	Expressions []Query `json:"Expressions"`
}

OrExpression evaluates to true if any of the child Expressions do.

func (OrExpression) Evaluate

func (e OrExpression) Evaluate(i interface{}, opts ...any) (bool, error)

type Query

type Query QueryRaw

Query wraps QueryRaw and provides evaluation and JSON unmarshalling helpers.

Example (UnmarshalJSON)

ExampleQuery_unmarshalJSON shows how to unmarshal a query from JSON.

js := `{
        "Expression": {
            "Type": "Contains",
            "Expression": {
                "Field": "Tags",
                "Value": "go"
            }
        }
    }`
var q evaluator.Query
_ = json.Unmarshal([]byte(js), &q)
type Post struct{ Tags []string }
v, err := q.Evaluate(&Post{Tags: []string{"go", "news"}})
if err != nil {
	log.Fatal(err)
}
fmt.Println(v)
Output:

true

func (*Query) Evaluate

func (q *Query) Evaluate(i interface{}, opts ...any) (bool, error)
Example

ExampleQuery_Evaluate demonstrates manual construction of a query and evaluation.

q := evaluator.Query{
	Expression: &evaluator.AndExpression{Expressions: []evaluator.Query{
		{Expression: &evaluator.IsExpression{Field: "Name", Value: "bob"}},
		{Expression: &evaluator.GreaterThanExpression{Field: "Age", Value: 30}},
	}},
}

type User struct {
	Name string
	Age  int
}

v, err := q.Evaluate(&User{Name: "bob", Age: 35})
if err != nil {
	log.Fatal(err)
}
fmt.Println(v)
Output:

true

func (Query) MarshalJSON

func (q Query) MarshalJSON() ([]byte, error)

func (*Query) UnmarshalJSON

func (q *Query) UnmarshalJSON(data []byte) error

type QueryRaw

type QueryRaw struct {
	Expression        Expression      `json:"-"`
	ExpressionRawJson json.RawMessage `json:"Expression"`
}

QueryRaw is the JSON representation of a query. ExpressionRawJson stores the raw JSON for the underlying expression and is resolved during unmarshalling.

type Self added in v0.0.2

type Self struct{}

Self represents the input value itself.

func (Self) Evaluate added in v0.0.2

func (s Self) Evaluate(i interface{}, opts ...any) (interface{}, error)

type Term added in v0.0.2

type Term interface {
	Evaluate(i interface{}, opts ...any) (interface{}, error)
}

Directories

Path Synopsis
cmd
csvfilter command
evaluator command
jsonlfilter command
jsontest command
yamltest command
internal
lib
parser

Jump to

Keyboard shortcuts

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