SkillAgentSearch skills...

Roulette

A text/template based rules engine

Install / Use

/learn @myntra/Roulette
About this skill

Quality Score

0/100

Supported Platforms

Universal

README

<p align="center"> <img src="https://cdn.rawgit.com/myntra/roulette/master/images/roulette.png" height="118" width="130" /> <h3 align="center">Roulette</h3> <p align="center">A text/template based package which triggers actions from rules defined in an xml file.</p> <p align="center"> <img src="http://badges.github.io/stability-badges/dist/experimental.svg"/> <a href='https://coveralls.io/github/myntra/roulette?branch=master'><img src='https://coveralls.io/repos/github/myntra/roulette/badge.svg?branch=master' alt='Coverage Status' /></a> <a href="https://travis-ci.org/myntra/roulette"><img src="https://travis-ci.org/myntra/roulette.svg?branch=master"></a> <a href="https://godoc.org/github.com/myntra/roulette"><img src="https://godoc.org/github.com/myntra/roulette?status.svg"></a> <a href="https://goreportcard.com/report/github.com/myntra/roulette"><img src="https://goreportcard.com/badge/github.com/myntra/roulette"></a> </p> </p>
<!-- TOC --> <!-- /TOC -->

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 f

View on GitHub
GitHub Stars43
CategoryDevelopment
Updated6mo ago
Forks10

Languages

Go

Security Score

87/100

Audited on Sep 7, 2025

No findings