roulette

package module
v0.0.0-...-e920351 Latest Latest
Warning

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

Go to latest
Published: May 13, 2017 License: MIT Imports: 16 Imported by: 0

README

Roulette

A text/template based package which triggers actions from rules defined in an xml file.

Coverage Status


Features

  • Builtin functions for writing simple rule expressions.
  • Supports injecting custom functions.
  • Can namespace a set of rules for custom types.
  • Allows setting priority of a rule.

This pacakge is used for firing business actions based on a textual decision tree. It uses the powerful control structures in text/template and xml parsing from encoding/xml to build the tree from a roulette xml file.

Installation

$ go get github.com/myntra/roulette

Usage

Overview

From examples/rules.xml

<roulette>
    <!--filterTypes="T1,T2,T3..."(required) allow one or all of the types for the rules group. * pointer filterting is not done .-->
    <!--filterStrict=true or false. rules group executed only when all types are present -->
    <!--prioritiesCount= "1" or "2" or "3"..."all". if 1 then execution stops after "n" top priority rules are executed. "all" executes all the rules.-->
    <!--dataKey="string" (required) root key from which user data can be accessed. -->
    <!--resultKey="string" key from which result.put function can be accessed. default value is "result".-->
    <!--workflow: "string" to group rulesets to the same workflow.-->

    <ruleset name="personRules" dataKey="MyData" resultKey="result" filterTypes="types.Person,types.Company" 
        filterStrict="false" prioritiesCount="all"  workflow="promotioncycle">

        <rule name="personFilter1" priority="3">
                <r>with .MyData</r>
                    <r>
                       le .types.Person.Vacations 5 |
                       and (gt .types.Person.Experience 6) (in .types.Person.Age 15 30) |
                       eq .types.Person.Position "SSE" |
                       .types.Person.SetAge 25
                    </r>
                <r>end</r>
        </rule>

        <rule name="personFilter2" priority="2">
                <r>with .MyData</r>
                    <r>
                       le .types.Person.Vacations 5 |
                       and (gt .types.Person.Experience 6) (in .types.Person.Age 15 30) |
                       eq .types.Person.Position "SSE"  |
                       .result.Put .types.Person
                    </r>
                <r>end</r>
        </rule>

        <rule name="personFilter3" priority="1">
            <r>with .MyData</r>
                    <r>
                    le .types.Person.Vacations 5 |
                    and (gt .types.Person.Experience 6) (in .types.Person.Age 15 30) |
                    eq .types.Person.Position "SSE"  |
                    eq .types.Company.Name "Myntra" |
                     .result.Put .types.Company |
                    </r>
            <r>end</r>
        </rule>
    </ruleset>

    <ruleset name="personRules2" dataKey="MyData" resultKey="result" filterTypes="types.Person,types.Company" 
    filterStrict="false" prioritiesCount="all" workflow="demotioncycle">
    <rule name="personFilter1" priority="1">
        <r>with .MyData</r>
            <r>
                eq .types.Company.Name "Myntra" | .types.Person.SetSalary 30000
            </r>
        <r>end</r>
    </rule>
    </ruleset>    
</roulette>

From examples/...

simple

...
   	p := types.Person{ID: 1, Age: 20, Experience: 7, Vacations: 5, Position: "SSE"}
	c := types.Company{Name: "Myntra"}

	config := roulette.TextTemplateParserConfig{}

	parser, err := roulette.NewParser(readFile("../rules.xml"), config)
	if err != nil {
		log.Fatal(err)
	}

	executor := roulette.NewSimpleExecutor(parser)
	executor.Execute(&p, &c, []string{"hello"}, false, 4, 1.23)

	if p.Age != 25 {
		log.Fatal("Expected Age to be 25")
	}


  ...

workflows

...

	p := types.Person{ID: 1, Age: 20, Experience: 7, Vacations: 5, Position: "SSE"}
	c := types.Company{Name: "Myntra"}

	config := roulette.TextTemplateParserConfig{
		WorkflowPattern: "demotion*",
	}
	// set the workflow pattern
	parser, err := roulette.NewParser(readFile("../rules.xml"), config)
	if err != nil {
		log.Fatal(err)
	}

	executor := roulette.NewSimpleExecutor(parser)
	executor.Execute(&p, &c, []string{"hello"}, false, 4, 1.23)

	if p.Salary != 30000 {
		log.Fatal("Expected Salary to be 30000")
	}

	if p.Age != 20 {
		log.Fatal("Expected Age to be 20")
	}
...

callback

...
    count := 0
	callback := func(vals interface{}) {
		fmt.Println(vals)
		count++
	}

	config := roulette.TextTemplateParserConfig{
		Result: roulette.NewResultCallback(callback),
	}

	parser, err := roulette.NewParser(readFile("../rules.xml"), config)
	if err != nil {
		log.Fatal(err)
	}

	executor := roulette.NewSimpleExecutor(parser)
	executor.Execute(testValuesCallback...)
	if count != 2 {
		log.Fatalf("Expected 2 callbacks, got %d", count)
	}
...

queue

...
in := make(chan interface{})
	out := make(chan interface{})

	config := roulette.TextTemplateParserConfig{
		Result: roulette.NewResultQueue(),
	}

	// get rule results on a queue
	parser, err := roulette.NewParser(readFile("../rules.xml"), config)
	if err != nil {
		log.Fatal(err)
	}

	executor := roulette.NewQueueExecutor(parser)
	executor.Execute(in, out)

	//writer
	go func(in chan interface{}, values []interface{}) {

		for _, v := range values {
			in <- v
		}

	}(in, testValuesQueue)

	expectedResults := 2

read:
	for {
		select {
		case v := <-out:
			expectedResults--
			fmt.Println(v)
			switch tv := v.(type) {
			case types.Person:
				// do something
				if !(tv.ID == 4 || tv.ID == 3) {
					log.Fatal("Unexpected Result", tv)
				}
			}

			if expectedResults == 0 {
				break read
			}

			if expectedResults < 0 {
				log.Fatalf("received  %d more results", -1*expectedResults)
			}

		case <-time.After(time.Second * 5):
			log.Fatalf("received  %d less results", expectedResults)
		}
	}
...

Guide

Roulette XML file:

Tags and Attributes
Roulette

roulette is the root tag of the xml. It could contain a list of ruleset tags.

Ruleset

ruleset: a types namespaced tag with rule children. The attributes filterTypes and dataKey are required. To match ruleset , atleast one of the types from this list should be an input for the executor.

Attributes:

  • filterTypes: "T1,T2,T3..."(required) allow one or all of the types for the rules group. * pointer filterting is not done.

  • filterStrict: true or false. rules group executed only when all types are present.

  • prioritiesCount: "1" or "2" or "3"..."all". if 1 then execution stops after "n" top priority rules are executed. "all" executes all the rules

  • dataKey: "string" (required) root key from which user data can be accessed.

  • resultKey: "string" key from which result.put function can be accessed. default value is "result".

  • workflow: "string" to group rulesets to the same workflow. The parser can then be created with a wildcard pattern to filter out rilesets. "*", "?" glob pattern matching is expected.

Rule

The tag which holds the rule expression. The attributes name and priority are optional. The default value of priority is 0. There is no guarantee for order of execution if priority is not set.

Attributes:

  • name: name of the rule.

  • priority: priority rank of the rule within the ruleset.

Rule Expressions

Valid text/template expression. The delimeters can be changed from the default <r></r> using the parse api.

Defining Rules in XML
  • Write valid text/template control structures within the <rule>...</rule> tag.

  • Namespace rules by custom types. e.g:

    <ruleset filterTypes="Person,Company">...</ruleset>

  • Set priority of rules within namespace filterTypes.

  • Add custom functions to the parser using the method parser.AddFuncs. The function must have the signature:

    func(arg1,...,argN,prevVal ...bool)bool

    to allow rule execution status propagation.

  • Methods to be invoked from the rules file must also be of the above signature.

  • Invalid/Malformed rules are skipped and the error is logged.

  • The pipe | operator takes a previously evaluated value and passes it to the next function as the last argument.

  • For more information on go templating: text/template

Parsers

TextTemplateParser

Right now the package provides a single parser: TextTemplateParser. As the name suggests the parser is able to read xml wrapped over a valid text/template expression and executes it.

Results

Types which implements the roulette.Result. If a parser is initalised with a Result type, rule expressions with result.Put become valid. result.Put function accepts an interface{} type as a parameter.

ResultCallback

roulette.ResultCallback: An implementation of the roulette.Result interface which callbacks the provided function with result.Put value.

ResultQueue

roulette.ResultQueue: An implementation of the roulette.Result interface which puts the value received from result.Put on a channel.

Executors

An executor takes a parser and tests an incoming values against the rulesets. Executors implement the roulette.SimpleExecute and roulette.QueueExecute interfaces. The result is then caught by a struct which implements the roulette.Result interface.

SimpleExecutor

A simple implementation of the roulette.SimpleExecute interface which has a parser with nil Result set. This is mainly used to directly modify the input object. The executor ignores rule expressions which contain result.Put.


parser,err := NewTextTemplateParser(data, nil,"")
// or parser, err := roulette.NewSimpleParser(data,nil,"")
executor := roulette.NewSimpleExecutor(parser)
executor.Execute(t1,t2)
SimpleExecutor with Callback

An implementation of the roulette.SimpleExecute interface. which accepts a parser initialized with roulette.ResultCallback.

    config := roulette.TextTemplateParserConfig{}

	parser, err := roulette.NewParser(readFile("../rules.xml"), config)
	if err != nil {
		log.Fatal(err)
	}

	executor := roulette.NewSimpleExecutor(parser)
	executor.Execute(...)
QueueExecutor

An implementation of the roulette.QueueExecute interface. which accepts the roulette.ResultQueue type. The Execute method expects an input and an output channel to write values and read results respectively.


in := make(chan interface{})
		out := make(chan interface{})

		config := roulette.TextTemplateParserConfig{
			Result: roulette.NewResultQueue(),
		}

		// get rule results on a queue
		parser, err := roulette.NewParser(readFile("../rules.xml"), config)
		if err != nil {
			log.Fatal(err)
		}

		executor := roulette.NewQueueExecutor(parser)
		executor.Execute(in, out)

For concrete examples of the above please see the examples directory.

Builtin Functions

Apart from the built-in functions from text/template, the following functions are available.

Default functions reside in funcmap.go. They have been sourced and modified from src/text/template/funcs.go to make them work with pipelines and keep the expression uncluttered. The idea is to keep the templating language readable and easy to write.

Function Usage
in val >= minVal && val <= maxval, e.g. in 2 1 4 => true
gt > op, e.g. gt 1 2
ge >= op, e.g. ge 1 2
lt <= op, e.g. lt 1 2
le < op, e.g. le 1 2
eq == op, e.g. eq "hello" "world"
ne != op, e.g.ne "hello" "world"
not !op , e.g.not 1
and op1 && op2, e.g. and (expr1) (expr2)
or op1 // op2, e.g. or (expr1) (expr2)
result.Put result.Put Value where result is the defined resultKey
  • pipe operator | : Usage: the output of fn1 is the last argument of fn2, e.g. fn1 1 2| fn2 1 2

The functions from the excellent package sprig are also available.

Attributions

The roulette.png image is sourced from https://thenounproject.com/term/roulette/143243/ with a CC license.

Documentation

Overview

Package roulette is used for firing business actions based on a textual decision tree. It uses the powerful control structures in text/template and parsing from encoding/xml to build the tree from an rules xml file.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func IsTrue

func IsTrue(val interface{}) (truth, ok bool)

IsTrue reports whether the value is 'true', in the sense of not the zero of its type, and whether the value has a meaningful truth value. This is the definition of truth used by if and other such actions.

Types

type Parser

type Parser interface {
	Execute(vals interface{})
	GetResult() Result
}

Parser interface provides methods for the executor to update the tree, execute the values and get result.

func NewParser

func NewParser(data []byte, config ...TextTemplateParserConfig) (Parser, error)

NewParser returns a TextTemplateParser with default config

func NewTextTemplateParser

func NewTextTemplateParser(data []byte, config TextTemplateParserConfig) (Parser, error)

NewTextTemplateParser returns a new roulette format xml parser.

type QueueExecute

type QueueExecute interface {
	Execute(in <-chan interface{}, out chan<- interface{}) // in channel to write, out channel to read.
	CloseResult()
}

QueueExecute interface provides methods to retrieve a parser and a method which executes on the incoming values on the input channel.

func NewQueueExecutor

func NewQueueExecutor(parser Parser) QueueExecute

NewQueueExecutor returns a new QueueExecutor

type QueueExecutor

type QueueExecutor struct {
	Parser Parser
}

QueueExecutor implements the QueueExecute

func (*QueueExecutor) CloseResult

func (q *QueueExecutor) CloseResult()

CloseResult closes the result channel

func (*QueueExecutor) Execute

func (q *QueueExecutor) Execute(in <-chan interface{}, out chan<- interface{})

Execute ...

type Result

type Result interface {
	Put(val interface{}, prevVal ...bool) bool
	Get() interface{}
}

Result interface provides methods to get/put an interface{} value from a rule.

type ResultCallback

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

ResultCallback holds the out channel

func NewResultCallback

func NewResultCallback(fn func(interface{})) *ResultCallback

NewResultCallback return a new ResultCallback

func (ResultCallback) Get

func (q ResultCallback) Get() interface{}

Get the callback function.

func (ResultCallback) Put

func (q ResultCallback) Put(val interface{}, prevVal ...bool) bool

Put receives a value and put's it on the parser's out channel

type ResultQueue

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

ResultQueue holds the out channel

func NewResultQueue

func NewResultQueue() ResultQueue

NewResultQueue returns a new ResultQueue

func (ResultQueue) Get

func (q ResultQueue) Get() interface{}

Get ...

func (ResultQueue) Put

func (q ResultQueue) Put(val interface{}, prevVal ...bool) bool

Put receives a value and put's it on the parser's out channel

type Rule

type Rule struct {
	Name     string `xml:"name,attr"`
	Priority int    `xml:"priority,attr"`
	Expr     string `xml:",innerxml"`
	// contains filtered or unexported fields
}

Rule is a single rule expression. A rule expression is a valid go text/template

type Ruleset

type Ruleset interface {
	Execute(vals interface{}) error
}

Ruleset ...

type SimpleExecute

type SimpleExecute interface {
	Execute(vals ...interface{})
}

SimpleExecute interface provides methods to retrieve a parser and a method which executes on the incoming values.

func NewSimpleExecutor

func NewSimpleExecutor(parser Parser) SimpleExecute

NewSimpleExecutor returns a new SimpleExecutor

type SimpleExecutor

type SimpleExecutor struct {
	Parser Parser
}

SimpleExecutor implements the SimpleExecute interface

func (*SimpleExecutor) Execute

func (s *SimpleExecutor) Execute(vals ...interface{})

Execute executes rules in order of priority. one(true): executes in order of priority until a high priority rule is successful, after which execution stops

type TextTemplateParser

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

TextTemplateParser holds the rules from a rule file

func (TextTemplateParser) Execute

func (p TextTemplateParser) Execute(vals interface{})

Execute executes the parser's rulesets

func (TextTemplateParser) GetResult

func (p TextTemplateParser) GetResult() Result

GetResult returns the parser's result.

type TextTemplateParserConfig

type TextTemplateParserConfig struct {
	Userfuncs                 template.FuncMap
	DelimLeft                 string
	DelimRight                string
	WorkflowPattern           string // filter rulesets based on the pattern
	Result                    Result
	IsWildcardWorkflowPattern bool
	LogLevel                  string //info, debug, warn, error, fatal. default is info
	LogPath                   string //stdout, /path/to/file . default is stdout
}

TextTemplateParserConfig sets the optional config for the TextTemplateParser

type TextTemplateRuleset

type TextTemplateRuleset struct {
	Name            string `xml:"name,attr"`
	FilterTypes     string `xml:"filterTypes,attr"`
	FilterStrict    bool   `xml:"filterStrict,attr"`
	DataKey         string `xml:"dataKey,attr"`
	ResultKey       string `xml:"resultKey,attr"`
	Rules           []Rule `xml:"rule"`
	PrioritiesCount string `xml:"prioritiesCount,attr"`
	Workflow        string `xml:"workflow,attr"`
	// contains filtered or unexported fields
}

TextTemplateRuleset is a collection of rules for a valid go type

func (TextTemplateRuleset) Execute

func (t TextTemplateRuleset) Execute(vals interface{}) error

Execute ...

func (TextTemplateRuleset) Len

func (t TextTemplateRuleset) Len() int

sort rules by priority

func (TextTemplateRuleset) Less

func (t TextTemplateRuleset) Less(i, j int) bool

func (TextTemplateRuleset) Swap

func (t TextTemplateRuleset) Swap(i, j int)

type XMLData

type XMLData struct {
	Name     xml.Name              `xml:"roulette"`
	Rulesets []TextTemplateRuleset `xml:"ruleset"`
}

XMLData contains the parsed roulette xml tree

Directories

Path Synopsis
examples
Package log is a custom wrapper over logger so that a global configuration can be set and used across all the packages Use this instead of the standard library "log" package.
Package log is a custom wrapper over logger so that a global configuration can be set and used across all the packages Use this instead of the standard library "log" package.

Jump to

Keyboard shortcuts

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