dtree

package module
v0.5.0 Latest Latest
Warning

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

Go to latest
Published: Sep 7, 2021 License: MIT Imports: 11 Imported by: 5

README

CircleCI Go Report Card Coverage Status

GO-DTREE - Golang package for resolve decisions Tree

DTree allow you to define an Decision Tree in json

{
				"id": 1,
				"name": "Root"
			},
			{
				"id": 2,
				"name": "IsFirstTree",
                "parent_id": 1,
                "key":"isFirstTree",
				"operator": "eq",
				"value": true
			},
			{
				"id": 3,
				"name": "IsNotTheFirstTree",
                "parent_id": 1,
                "key":"isFirstTree",
				"operator": "eq",
				"value": false
            },
            {
				"id": 4,
				"name": "Welcome",
                "parent_id": 2,
            },
            {
				"id": 5,
				"name": "Congrats",
                "parent_id": 3,
			}

loaded

  tree, err :=dtree.LoadTree([]byte(jsonTree))

it will create :

If you want to programmaticaly build your tree, you can also use the CreateTree Method.

var myTree []Tree
// append your nodes on myTree and then
tree :=dtree.CreateTree(myTree)

Then we can resolve the decision Tree by passing another json, representing the needed value.

    request := []byte(`{
		"isFirstTree":     true
	}`)

    node, _ := t.ResolveJSON(request)

    fmt.Println(node.Name)
    // Output: Welcome

you can also define it programmatically,

    request := make(map[string]interface{}) 
    request["isFirstTree"] = true

    node, _ := t.Resolve(request)

    fmt.Println(node.Name)
    // Output: Welcome

but in this case be careful, to don't use int (not supported), only floats.

This one was a simple decision Tree. You can build more complexe with more nodes, with others operators than only equal.

Available Operators :

operator description
eq (or ==) equality (for string, bool, numbers, arrays)
ne (or !=) not equal (for string, bool, numbers, arrays)
gt (or >) gt (for string, numbers)
lt (or <) lt (for string, numbers)
gte (or >=) gte (for string, numbers)
lte (or <=) lte (for string, numbers)
contains does the string (defined on the value of the Tree) is contained on the json request
count count (only for arrays)
regexp do a regexp (only for string)
percent (or %) do a random selection based on percentages
ab A/B Test (if no userId provided, it will act as percent)

You can also define your own operators

string comparition

All the strings are case compared (without ToLower before the comparition)

s1 == s2

if you are not sure about the data, and you want to do ToLower comparition, if you two choice:

  • Write your own operator
  • Lower your tree data and json request, before to call Resolve.

Custom operators

You can define your own custom operators (on the example I do a len of an array pass on request, and i check if it matches the Value of the path of the node on the Tree (it is the code of the already implemented operator "count"))

f := func(t *TreeOptions) {
    t.Operators = make(map[string]dtree.Operator)
    t.Operators["len"] = func(requests map[string]interface{}, node *Tree) (*Tree, error) {
        if v1, ok := requests[node.Key]; ok {
            switch t1 := v1.(type) {
            case []interface{}:
                if t2, ok := node.Value.(float64); ok {
                    if len(t1) == int(t2) {
                        return node, nil
                    }
                    return nil, nil
                }

                return nil, ErrBadType
            default:
                return nil, ErrNotSupportedType
            }
        }
        return nil, nil
    }
}

Of course if you have really good operators that you want to add to DTREE, does not hesitate to do a PR.

Options :

By default if dtree cannot resolve one node (because bad parameters), we consider this node as false, and it continues. We can set the option StopIfConvertingError at true, on this case dtree will stop once it found an parsing error.

f := func(t *TreeOptions) {
    t.StopIfConvertingError = true
}

We can also define a fallback value. It means on this case, that if all others path are in false, it goes to this one.

"value": "fallback"

We can also set an order, to define the order of the evaluation (but of course fallback will always be the last (even if you don't say so))

"order": "1"

The Node Tree as a parameter content, it's a interface{}, that allow you to put whatever you want.

Context :

You can give a context to the Tree, is mostly used for debugging, like this you will be able to know what are the path that your request takes inside the tree.

t.WithContext(context.Background())

v, _ := t.Resolve(request)

sliceOfString := dtree.GetNodePathFromContext(t.Context())
fmt.Println(sliceOfString)

// sliceofstring contains is a slice where each string is a node on the format `id : key value operator expectedvalue`
// example : 3 : productid 1234 gt 1230

Documentation

Index

Examples

Constants

This section is empty.

Variables

View Source
var ErrBadType = errors.New("types are different")

ErrBadType : Types between request and Tree are different, so we are unable to compare them

View Source
var ErrNoNode = errors.New("Node is nil")

ErrNoNode : No Node was sent

View Source
var ErrNoParentNode = errors.New("Node has no parent")

ErrNoParentNode : Node has no parent

View Source
var ErrNotSupportedType = errors.New("type not supported")

ErrNotSupportedType : Type in request are not supported

View Source
var ErrOperator = errors.New("unknow operator")

ErrOperator : unknow operator

View Source
var FallbackType = "fallback"

FallbackType the fallback value (by default = "fallback"), can be overrided

Functions

func GetNodePathFromContext

func GetNodePathFromContext(ctx context.Context) []string

GetNodePathFromContext gets the node path from the context

Types

type Operator

type Operator func(requests map[string]interface{}, node *Tree) (*Tree, error)

Operator represent a function that will evaluate a node

type Tree

type Tree struct {
	ID       int                    `json:"id"`
	Name     string                 `json:"name"`
	ParentID int                    `json:"parent_id"`
	Value    interface{}            `json:"value"`
	Operator string                 `json:"operator"`
	Key      string                 `json:"key"`
	Order    int                    `json:"order"`
	Content  interface{}            `json:"content"`
	Headers  map[string]interface{} `json:"headers"`
	// contains filtered or unexported fields
}

Tree represents a Tree

func CreateTree

func CreateTree(data []Tree) *Tree

CreateTree attach the nodes to the Tree

func LoadTree

func LoadTree(jsonTree []byte) (*Tree, error)

LoadTree gets a json on build the Tree related

Example
jsonTree := []byte(`[
		{
			"id": 1,
			"name": "root"
		},
		{
			"id": 2,
			"parent_id": 1,
			"key": "sayHello",
			"operator": "eq",
			"value": true
		},
		{
			"id": 3,
			"parent_id": 1,
			"key": "sayHello",
			"operator": "eq",
			"value": false
		},
		{
			"id": 4,
			"parent_id": 3,
			"Name": "Goodbye"
		},
		{
			"id": 5,
			"parent_id": 2,
			"key": "gender",
			"operator": "eq",
			"value": "F"
		},
		{
			"id": 6,
			"parent_id": 5,
			"Name": "Hello Miss"
		},
		{
			"id": 7,
			"parent_id": 2,
			"value": "fallback"
		},
		{
			"id": 8,
			"parent_id": 7,
			"Name": "Hello"
		},
		{
			"id": 9,
			"parent_id": 2,
			"key": "gender",
			"operator": "eq",
			"value": "M"
		},
		{
			"id": 10,
			"parent_id": 9,
			"key": "age",
			"operator": "gt",
			"value": 60
		},
		{
			"id": 11,
			"parent_id": 10,
			"Name": "Hello Sir"
		},
		{
			"id": 12,
			"parent_id": 9,
			"key": "age",
			"operator": "lte",
			"value": 60
		},
		{
			"id": 13,
			"parent_id": 12,
			"Name": "Hello dude"
		}
	]`)

t, err := LoadTree(jsonTree)
if err != nil {
	fmt.Println(err)
	os.Exit(-1)
}

request := make(map[string]interface{})
request["sayHello"] = true
request["gender"] = "M"
request["age"] = 35.0 //does not use int, the engine only support float (if you want  do a PR to include int, it's up to you)

/*request := []byte(`{
  		"sayHello": false,
  		"gender":   "M",
  		"age": 35
  	}`)

  v, _ := t.ResolveJSON(request)
*/t)
*/

v, _ := t.Resolve(request)

fmt.Println(v.Name)
// output : Hello dude
Output:

func (*Tree) AddNode

func (t *Tree) AddNode(node *Tree)

AddNode Add a new Node (leaf) to the Tree

func (*Tree) Context

func (t *Tree) Context() context.Context

Context returns the context

func (*Tree) GetChild

func (t *Tree) GetChild() []*Tree

GetChild get the nodes child of this one

func (*Tree) GetParent

func (t *Tree) GetParent() *Tree

GetParent get the parent node of this one

func (*Tree) Next

func (t *Tree) Next(jsonRequest map[string]interface{}, config *TreeOptions) (*Tree, error)

Next evaluate which will be the next Node according to the jsonRequest

func (*Tree) Resolve

func (t *Tree) Resolve(request map[string]interface{}, options ...func(t *TreeOptions)) (*Tree, error)

Resolve calculate which will be the selected node according to the map request

func (*Tree) ResolveJSON

func (t *Tree) ResolveJSON(jsonRequest []byte, options ...func(t *TreeOptions)) (*Tree, error)

ResolveJSON calculate which will be the selected node according to the jsonRequest

func (*Tree) ResolveJSONWithContext added in v0.3.1

func (t *Tree) ResolveJSONWithContext(ctx context.Context, jsonRequest []byte, options ...func(t *TreeOptions)) (*Tree, context.Context, error)

ResolveJSONWithContext calculate which will be the selected node according to the jsonRequest

func (*Tree) ResolveWithContext added in v0.3.1

func (t *Tree) ResolveWithContext(ctx context.Context, request map[string]interface{}, options ...func(t *TreeOptions)) (*Tree, context.Context, error)

ResolveWithContext calculate which will be the selected node according to the map request

func (*Tree) String added in v0.5.0

func (t *Tree) String() string

func (*Tree) ValueToDraw added in v0.5.0

func (t *Tree) ValueToDraw() string

func (*Tree) WithContext

func (t *Tree) WithContext(ctx context.Context) *Tree

WithContext returns a tree with a context

type TreeOptions

type TreeOptions struct {
	StopIfConvertingError    bool
	Operators                map[string]Operator
	OverrideExistingOperator bool
	// contains filtered or unexported fields
}

TreeOptions allow to extend the comparator

Directories

Path Synopsis

Jump to

Keyboard shortcuts

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