gorule

package module
v0.1.0 Latest Latest
Warning

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

Go to latest
Published: May 17, 2024 License: Apache-2.0 Imports: 6 Imported by: 0

README

Go Report Card

gorule

A powerful, lightweight and flexible rule engine written in Go. gorule allows you to define and evaluate complex business rules in an efficient and easy-to-use manner.

Table of Contents

Introduction

gorule is designed to simplify the process of defining and evaluating business rules within your Go applications. Whether you need to validate data, enforce policies, or build complex decision-making workflows, gorule has you covered.

Features

  • Easy-to-Use: Define the rules in human readable form
  • High Performance: Optimized for performance to handle large sets of rules efficiently.
  • Extensible: Rule engine that works for deeply nested JSON objects INCLUDING array fields (i.e vector conditions support)
  • Comprehensive Documentation: Detailed documentation to help you get started quickly.

Installation

To install gorule, use go get:

go get github.com/praks-1529/gorule

Quick start

package main

import (
	"fmt"

	"github.com/praks-1529/gorule"
)

// Given a transaction data, categorizes it as risky or not based on the amount and type of transaction
func main() {
	// Step-1: Create a parser
	parser := gorule.NewRuleParser("IF: { amount >= 10000 && type == \"CREDIT_CARD\" }")

	// Step-2: Create a rule
	rule, err := parser.ParseRule()
	if err != nil {
		fmt.Println("Error:", err)
		return
	}

	// Step-3: Evaluate the rule across data
	riskyTxn := []byte(`
	{
        "amount" : 10000,
		"type":"CREDIT_CARD",
	}
	`)
	nonRiskyTxn := []byte(`
	{
        "amount" : 9999,
		"type":"CREDIT_CARD",
	}
	`)

	re := gorule.NewRuleEngine()
	result1, err := re.Evaluate(rule, riskyTxn)
	result2, err := re.Evaluate(rule, nonRiskyTxn)
	if err != nil {
		fmt.Println("Error:", err)
		return
	}
	fmt.Println("Rule evaluation result:", result1, result2)
}

Output

Rule evaluation result: [true] [false]

See examples for more advanced use cases

Rule Types

gorule supports various types of rules to cater to different use cases.

Scalar Rule Scalar Condition

Scalar rule with scalar condition (aka SRSC) evaluates a single set of condition on a JSON object. These kind of rule is applicable if the data on which the rule is evaluated is a simple JSON object.

Example

In the example below, the transaction is categorized as risky or not based on the amount and type of transaction. Full example here

// Define a rule to categorize a transaction as risky if the amount is greater than or equal to 10000 and the type is "CREDIT_CARD"
parser := gorule.NewRuleParser("IF: { transaction.amount >= 10000 && transaction.type == \"CREDIT_CARD\" }")
Scalar Rule Vector Condition

Scalar rule with vector conditions (aka SRVC) evaluate set of conditions and combines (using &&) the evaluation result of each evaluation to give a final result. The vector condition is very useful when the decision has to be made by iterating over a array JSON field.

Example

In the example below, a transaction is marked valid only if all the attributes of a transaction is "VALID". Full example here

// Below example categorizes the transactions as risky or not based on multiple attributes of the transaction
parser := gorule.NewRuleParser("IF: { FOR: i=0:transaction.attributes.size() { transaction.attributes[i].type == \"VALID\" } }")

Vector Rule Scalar Condition

This rule (aka VRSC) is same as SRVC, with the only difference that it can evaluate multiple rules at once. This symatically is calling SRVC in a loop. This rule is useful when the decision depends on iterating over JSON objects and evaluating rule for each object.

Example

In the example below, we want categorize the each transaction in the transactions array as risky or not based on the amount and type of transaction. Full example here

// Below example iterates over each transactions and categorizes the transactions as risky or not based on the amount and type of transaction
parser := gorule.NewRuleParser("FOR: i=0:transactions.size() IF: { transactions[i].amount > 10000 && transactions[i].type == \"CREDIT_CARD\" }")

Supported data types

  • Integers
  • Float
  • String
  • Boolean

Supported operators

Operator Description Precendence
&& Logical AND 2
|| Logical OR 2
== Equal to 1
>= Greater than or equal to 1
> Greater than 1
>= Lesser than or equal to 1
> Lesser than 1

Contributing

Contributions are always welcome.

Contact

For questions or support, please open an issue on GitHub

Documentation

Overview

File: actions.go Implements the action

File: condition.go Represents one condition ex. a == b

File: constants.go Stores all the constant shared within packages

File: context.go Represents a Context which is used during rule evaluation

File: engine.go Rule engine to evaluate the rule for the given data

File: operator.go Defines all the supported operators

File: parser.go Implements the rule parser

File: parser_utils.go Common utility functions

File: rule.go Implement the rule interface

Index

Constants

View Source
const (
	// IndexKey ...
	IndexKey string = "_INDEX_KEY"
	// IndexCurrentValue ..
	IndexCurrentValue string = "_INDEX_CURRENT_VALUE"
	// StartIndexValue ...
	StartIndexValue string = "_START_INDEX_VALUE"
	// EndIndexValue ...
	EndIndexValue string = "_END_INDEX_VALUE"
)

Variables

This section is empty.

Functions

func EvaluateOperation

func EvaluateOperation(operand1 interface{}, operand2 interface{}, optor Operator) bool

EvaluateOperation is used to evaluate supported operations TODO: To make this more generic based on reflect package

func StringToInterface

func StringToInterface(value string) interface{}

StringToInterface takes a string and returns the actual type wrapped in interface

Types

type Action

type Action interface {
}

Action defining generic actions

type Condition

type Condition interface {
	GetOperator() Operator
	Evaluate(ctx Context) (interface{}, error)
	GetValue() interface{}
	// contains filtered or unexported methods
}

Condition represents the interface for condition

type ConditionType

type ConditionType int

ConditionType represents the condition type

const (
	// ScalarConditionType represents a binary condition which is one dimensional (ex. a == b && c == d)
	ScalarConditionType ConditionType = 1
	// VectorConditionType represents iterative condition (ex. for i=0:N (result = result && a[i]))
	VectorConditionType ConditionType = 2
)

type Context

type Context interface {
	SetValue(key string, value ContextValue)
	GetValue(key string) ContextValue
	KeyExists(key string) bool
}

Context represents the structure that is used to hold the evaluation context

func NewContext

func NewContext() Context

NewContext returns a fresh context

type ContextValue

type ContextValue interface{}

ContextValue represents the type of the value held inside context

type MalformedRuleError

type MalformedRuleError struct {
}

MalformedRuleError raised when the input data is malformed in some way

func (*MalformedRuleError) Error

func (_rt *MalformedRuleError) Error() string

type Operator

type Operator string

Operator represents the type of operator

const (
	// AndOperator for representing logical AND (works for bool ONLY)
	AndOperator Operator = "&&"
	// OrOperator for representing logical OR (works on bool ONLY)
	OrOperator Operator = "||"
	// EqualOperator for representing logical == (works for int, string, bool. float)
	EqualOperator Operator = "=="
	// GreaterThanOrEqualOperator for representing logical >= (works for int, float)
	GreaterThanOrEqualOperator Operator = ">="
	// GreaterOperator for representing logical >= (works for int, float)
	GreaterOperator Operator = ">"
	// LesserThanOrEqualOperator for representing logical <= (works for int, float)
	LesserThanOrEqualOperator Operator = "<="
	// LesserOperator for representing logical <= (works for int, float)
	LesserOperator Operator = "<"
	// NilOperator for representing NIL operator
	NilOperator Operator = "NIL"
)

type Rule

type Rule interface {
	GetType() RuleType
	Evaluate(ctx Context) (interface{}, error)
	BuildContext(ipData []byte, ctx Context) error
}

Rule represent the common interface for any rule

type RuleContext

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

RuleContext represents the core context used during rule evaluation

func (*RuleContext) GetValue

func (_ctx *RuleContext) GetValue(key string) ContextValue

GetValue gets a value from the context

func (*RuleContext) KeyExists

func (_ctx *RuleContext) KeyExists(key string) bool

KeyExists checks if key exists on the context

func (*RuleContext) SetValue

func (_ctx *RuleContext) SetValue(key string, value ContextValue)

SetValue sets a value inside context

type RuleEngine

type RuleEngine struct {
}

RuleEngine represents a service to evaluate the rule

func NewRuleEngine

func NewRuleEngine() *RuleEngine

NewRuleEngine returns a fresh rule engine instance

func (*RuleEngine) Evaluate

func (_re *RuleEngine) Evaluate(fgRule Rule, jsonData []byte) (interface{}, error)

Evaluate evaluates a rule for the given rule and jsonData args:

fgRule: The rule to evaluate
jsonData: The data to be used during evaluation

Return

bool: Evaluation result (i.e true/false)
error: Any error during evaluation

type RuleParser

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

RuleParser service to parse the rule

func NewRuleParser

func NewRuleParser(ip string) *RuleParser

NewRuleParser returns the fresh instance of RuleParser

func (*RuleParser) ParseRule

func (_p *RuleParser) ParseRule() (Rule, error)

ParseRule main entry to parse the rule args:

s (string): The input rule to parse

returns Rule: Parsed rule error: Error any dound while parsing TODO: Handle all cases - For now simplest case a && b && c

type RuleType

type RuleType int

RuleType represents types of rule

const (
	// ScalarRuleType represents a simple rule ex. IF: (a==b && c==d)) THEN: ()
	ScalarRuleType RuleType = 1
	// VectorRuleType represents iterative rule ex: FOR i=0:a.size(): IF (a[i].type == a) THEN: ()
	VectorRuleType RuleType = 2
)

type ScalarCondition

type ScalarCondition struct {
	Type          ConditionType `json:"type"`
	Operator      Operator      `json:"optor"`
	Operand1      Condition     `json:"o1"`
	Operand2      Condition     `json:"o2"`
	Value         interface{}   `json:"value"`
	HasArrayIndex bool          `json:"has_array_index"`
}

ScalarCondition Condition represents one evaluatable binary expression (ex: a == b) Format: { a == b && c == d ) }

func (*ScalarCondition) Evaluate

func (_c *ScalarCondition) Evaluate(ctx Context) (interface{}, error)

Evaluate does the evaluation of the condition and returns the result

func (*ScalarCondition) GetOperand1

func (_c *ScalarCondition) GetOperand1() Condition

GetOperand1 returns the underlying operand-1

func (*ScalarCondition) GetOperand2

func (_c *ScalarCondition) GetOperand2() Condition

GetOperand2 returns the underlying operand-2

func (*ScalarCondition) GetOperator

func (_c *ScalarCondition) GetOperator() Operator

GetOperator returns the underlying operator in the condition

func (*ScalarCondition) GetValue

func (_c *ScalarCondition) GetValue() interface{}

GetValue returns data stored in the condition

type ScalarRule

type ScalarRule struct {
	Type       RuleType    `json:"type"`
	If         Condition   `json:"if"`
	Then       Action      `json:"then"`
	StartIndex interface{} `json:"start_index"`
	IndexKey   interface{} `json:"index_key"`
}

ScalarRule represents a structure of the If rule (created by parsing the user provided rules)

func (*ScalarRule) BuildContext

func (_fgr *ScalarRule) BuildContext(ipData []byte, ctx Context) error

BuildContext builds the context

func (*ScalarRule) Evaluate

func (_fgr *ScalarRule) Evaluate(ctx Context) (interface{}, error)

Evaluate evalates the rule

func (*ScalarRule) GetType

func (_fgr *ScalarRule) GetType() RuleType

GetType gets the type of rule

type SyntaxError

type SyntaxError struct {
	Expected Token
	Found    Token
	Index    int
}

SyntaxError raised when there is a syntax error

func (*SyntaxError) Error

func (_rt *SyntaxError) Error() string

type Token

type Token string

Token represents a valid token.

const (
	// IfToken represents start of If rule
	IfToken Token = "IF:"
	// ThenToken represents start of Then block
	ThenToken Token = "THEN:"
	// OpenBraceToken represents start of block
	OpenBraceToken Token = "("
	// CloseBraceToken represents close of block
	CloseBraceToken Token = ")"
	// CurlyOpenBraceToken represents start of a condition/action
	CurlyOpenBraceToken Token = "{"
	// CurlyCloseBraceToken represents close of block
	CurlyCloseBraceToken Token = "}"
	// ColonToken represents delimiter
	ColonToken Token = ":"
	// ForToken represents start if For rule
	ForToken Token = "FOR:"
)

type VectorCondition

type VectorCondition struct {
	Type       ConditionType `json:"type"`
	Operator   Operator      `json:"optor"`
	SCondition Condition     `json:"scalar_condition"`
	Value      interface{}   `json:"value"`
	StartIndex interface{}   `json:"start_index"`
	EndIndex   interface{}   `json:"end_index"`
	IndexKey   string        `json:"index_key"`
}

VectorCondition represents collection of Conditions joined by operator Format: { FOR i=initialValue:finalValue SCALAR_CONDITION }

func (*VectorCondition) Evaluate

func (_c *VectorCondition) Evaluate(ctx Context) (interface{}, error)

Evaluate does the evaluation of the condition and returns the result

func (*VectorCondition) GetOperator

func (_c *VectorCondition) GetOperator() Operator

GetOperator returns the underlying operator in the condition

func (*VectorCondition) GetValue

func (_c *VectorCondition) GetValue() interface{}

GetValue returns data stored in the condition

type VectorRule

type VectorRule struct {
	Type       RuleType    `json:"type"`
	SRule      Rule        `json:"scalar_rule"`
	StartIndex interface{} `json:"start_index"`
	EndIndex   interface{} `json:"end_index"`
	IndexKey   interface{} `json:"index_key"`
}

VectorRule represents a collection of Simple rules

func (*VectorRule) BuildContext

func (_fgr *VectorRule) BuildContext(ipData []byte, ctx Context) error

BuildContext builds the context

func (*VectorRule) Evaluate

func (_fgr *VectorRule) Evaluate(ctx Context) (interface{}, error)

Evaluate evalates the rule

func (*VectorRule) GetType

func (_fgr *VectorRule) GetType() RuleType

GetType gets the type of rule

Jump to

Keyboard shortcuts

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