Documentation ¶
Overview ¶
Package cel provides an implementation of the Indigo evaluator and compiler interfaces backed by Google's cel-go rules engine.
See https://github.com/google/cel-go and https://opensource.google/projects/cel for more information about CEL.
The rule expressions you write must conform to the CEL spec: https://github.com/google/cel-spec.
Google has a nice tutorial here: https://codelabs.developers.google.com/codelabs/cel-go/index.html#0
Working with Protocol Buffers ¶
While it is possible to use CEL with "native" simple types, it is built on protocol buffers. CEL does not support Go structs, so if you need to use native types to access fields in a struct, you must first "flatten" the fields into plain values to pass to CEL. See the makeStudentData() function in the tests in this package for an example of "flatting" a struct to individual data elements.
Organizing your input data using protocol buffers gives you the benefit of being able to move data between Go code and CEL expressions without needing to translate or reorganize the data. There a number of examples (they start with proto) that show how to use protocol buffers in Indigo. They use the protocol buffer definitions in indigo/testdata/proto, and there's a Makefile in that directory that shows how to generate the Go types.
Protocol Buffer Fields in Expressions ¶
When refererring to fields of a protocol buffer in an expression, the field names are the proto names, NOT the generated Go names. For example, in the student.proto file, a field called "enrollment_date" is defined. When the Go struct is generated, this field name is now called "EnrollmentDate". In the CEL expression, you must use the "enrollment_date" name, as in the protocol buffer message definition.
In student.proto:
message Student { google.protobuf.Timestamp enrollment_date = 7; }
In Go code:
s := school.Student{} s.EnrollmentDate = ×tamp.Timestamp{ Seconds: time.Date(2010, 5, 1, 12, 12, 59, 0, time.FixedZone("UTC-8", -8*60*60)).Unix() }
In the CEL expression:
rule.Expr = ` now - student.enrollment_date > duration("4320h")`
Protocol Buffer Enums ¶
The student protocol buffer definition includes an enum type for a student's status. When referring to enum values in the CEL expression, use the full protocol buffer name:
In student.proto:
message Student { status_type status = 4; ... enum status_type { ENROLLED=0; PROBATION=1; } ... }
In Go Code:
s:= school.Student{} s.Status = school.Student_PROBATION
In the CEL expression:
rule.Expr = `student.Status == testdata.school.Student.status_type.PROBATION`
Protocol Buffer Timestamps ¶
The examples demonstrate how to convert to/from the Go time.Time type and the protocol buffer timestamp.
This package has generated types for google/protobuf/timestamp.proto:
import "google.golang.org/protobuf/types/known/timestamppb"
To create a new timestamp:
now := timestamppb.Now()
To convert a Go time.Time to a proto timestamp:
prototime := timestamppb.New(time.Date(2009, time.November, 10, 23, 0, 0, 0, time.UTC))
To convert from a proto time to Go time:
goTime := pbtime.AsTime()
Protocol Buffer Durations ¶
This package has generated types for google/protobuf/duration.proto:
import "google.golang.org/protobuf/types/known/durationpb"
To convert a Go duration to a proto duration:
protodur := durationpb.New(godur)
To convert back from a protocol buffer duration to a Go duration:
goDur := protodur.AsDuration()
Example ¶
package main import ( "context" "fmt" "github.com/ezachrisen/indigo" "github.com/ezachrisen/indigo/cel" ) func main() { //Step 1: Create a schema schema := indigo.Schema{ Elements: []indigo.DataElement{ {Name: "message", Type: indigo.String{}}, }, } // Step 2: Create rules rule := indigo.Rule{ ID: "hello_check", Schema: schema, ResultType: indigo.Bool{}, Expr: `message == "hello world"`, } // Step 3: Create an Indigo engine and give it an evaluator // In this case, CEL engine := indigo.NewEngine(cel.NewEvaluator()) // Step 4: Compile the rule err := engine.Compile(&rule) if err != nil { fmt.Println(err) return } data := map[string]interface{}{ "message": "hello world", } // Step 5: Evaluate and check the results results, err := engine.Eval(context.Background(), &rule, data) if err != nil { fmt.Println(err) } else { fmt.Println(results.ExpressionPass) } }
Output: true
Example (Alarms) ¶
Example_alarms illustrates using the FailAction option to only return true rules from evaluation
package main import ( "context" "fmt" "github.com/ezachrisen/indigo" "github.com/ezachrisen/indigo/cel" ) func main() { sysmetrics := indigo.Schema{ Elements: []indigo.DataElement{ {Name: "cpu_utilization", Type: indigo.Int{}}, {Name: "disk_free_space", Type: indigo.Int{}}, {Name: "memory_utilization", Type: indigo.Int{}}, }, } rule := indigo.Rule{ ID: "alarm_check", Rules: map[string]*indigo.Rule{}, } // Setting this option so we only get back // rules that evaluate to 'true' rule.EvalOptions.DiscardFail = indigo.Discard rule.Rules["cpu_alarm"] = &indigo.Rule{ ID: "cpu_alarm", Schema: sysmetrics, Expr: "cpu_utilization > 90", } rule.Rules["disk_alarm"] = &indigo.Rule{ ID: "disk_alarm", Schema: sysmetrics, Expr: "disk_free_space < 70", } rule.Rules["memory_alarm"] = &indigo.Rule{ ID: "memory_alarm", Schema: sysmetrics, Expr: "memory_utilization > 90", } engine := indigo.NewEngine(cel.NewEvaluator()) err := engine.Compile(&rule) if err != nil { fmt.Println(err) return } data := map[string]interface{}{ "cpu_utilization": 99, "disk_free_space": 85, "memory_utilization": 89, } results, err := engine.Eval(context.Background(), &rule, data) if err != nil { fmt.Println(err) } for k := range results.Results { fmt.Println(k) } }
Output: cpu_alarm
Example (AlarmsTwoLevel) ¶
Example_alarms illustrates using the FailAction option to only return true rules from evaluation with a multi-level hierarchy
package main import ( "context" "fmt" "github.com/ezachrisen/indigo" "github.com/ezachrisen/indigo/cel" ) func main() { sysmetrics := indigo.Schema{ Elements: []indigo.DataElement{ {Name: "cpu_utilization", Type: indigo.Int{}}, {Name: "disk_free_space", Type: indigo.Int{}}, {Name: "memory_utilization", Type: indigo.Int{}}, {Name: "memory_mb_remaining", Type: indigo.Int{}}, }, } rule := indigo.Rule{ ID: "alarm_check", Rules: map[string]*indigo.Rule{}, } // Setting this option so we only get back // rules that evaluate to 'true' rule.EvalOptions.DiscardFail = indigo.Discard rule.Rules["cpu_alarm"] = &indigo.Rule{ ID: "cpu_alarm", Schema: sysmetrics, Expr: "cpu_utilization > 90", } rule.Rules["disk_alarm"] = &indigo.Rule{ ID: "disk_alarm", Schema: sysmetrics, Expr: "disk_free_space < 70", } memory_alarm := &indigo.Rule{ ID: "memory_alarm", Rules: map[string]*indigo.Rule{}, EvalOptions: indigo.EvalOptions{ DiscardFail: indigo.KeepAll, TrueIfAny: true, }, } memory_alarm.Rules["memory_utilization_alarm"] = &indigo.Rule{ ID: "memory_utilization_alarm", Schema: sysmetrics, Expr: "memory_utilization > 90", } memory_alarm.Rules["memory_remaining_alarm"] = &indigo.Rule{ ID: "memory_remaining_alarm", Schema: sysmetrics, Expr: "memory_mb_remaining < 16", } rule.Rules["memory_alarm"] = memory_alarm engine := indigo.NewEngine(cel.NewEvaluator()) err := engine.Compile(&rule) if err != nil { fmt.Println(err) return } data := map[string]interface{}{ "cpu_utilization": 99, "disk_free_space": 85, "memory_utilization": 89, "memory_mb_remaining": 7, } results, err := engine.Eval(context.Background(), &rule, data) if err != nil { fmt.Println(err) } for k := range results.Results { fmt.Println(k) } }
Output: cpu_alarm memory_alarm
Example (Basic) ¶
package main import ( "context" "fmt" "github.com/ezachrisen/indigo" "github.com/ezachrisen/indigo/cel" ) func main() { //Step 1: Create a schema schema := indigo.Schema{ Elements: []indigo.DataElement{ {Name: "x", Type: indigo.Int{}}, {Name: "y", Type: indigo.String{}}, }, } // Step 2: Create rules rule := indigo.Rule{ Schema: schema, ResultType: indigo.Bool{}, Expr: `x > 10 && y != "blue"`, } // Step 3: Create an Indigo engine and give it an evaluator // In this case, CEL engine := indigo.NewEngine(cel.NewEvaluator()) // Step 4: Compile the rule err := engine.Compile(&rule) if err != nil { fmt.Println(err) return } data := map[string]interface{}{ "x": 11, "y": "red", } // Step 5: Evaluate and check the results results, err := engine.Eval(context.Background(), &rule, data) if err != nil { fmt.Println(err) return } fmt.Println(results.ExpressionPass) }
Output: true
Example (Indigo) ¶
package main import ( "context" "fmt" "github.com/ezachrisen/indigo" "github.com/ezachrisen/indigo/cel" "github.com/ezachrisen/indigo/testdata/school" ) func main() { // In this example we're going to determine which, if any, // communications the administration should send to the student. education := indigo.Schema{ ID: "education", Elements: []indigo.DataElement{ {Name: "s", Type: indigo.Proto{Message: &school.Student{}}}, }, } data := map[string]interface{}{ "s": &school.Student{ Id: 927312, Age: 21, Credits: 16, Gpa: 3.1, Attrs: map[string]string{"major": "Accounting", "home_town": "Chicago"}, Status: school.Student_ENROLLED, Grades: []float64{3, 3, 4, 2, 3, 3.5, 4}, }, } root := indigo.NewRule("root", "") root.Schema = education root.Rules["accounting_honors"] = &indigo.Rule{ ID: "accounting_honors", Schema: education, Expr: `s.attrs.exists(k, k == "major" && s.attrs[k] == "Accounting") && s.gpa > 3`, } root.Rules["arts_honors"] = &indigo.Rule{ ID: "arts_honors", Schema: education, Expr: `s.attrs.exists(k, k == "major" && s.attrs[k] == "Arts") && s.gpa > 3`, } root.Rules["last_3_grades_above_3"] = &indigo.Rule{ ID: "last_3_grades_above_3", Schema: education, Expr: `size(s.grades) >=3 && s.grades[size(s.grades)-1] >= 3.0 && s.grades[size(s.grades)-2] >= 3.0 && s.grades[size(s.grades)-3] >= 3.0 `, } engine := indigo.NewEngine(cel.NewEvaluator()) err := engine.Compile(root) if err != nil { fmt.Printf("Error adding rule %v", err) return } results, err := engine.Eval(context.Background(), root, data) if err != nil { fmt.Printf("Error evaluating: %v", err) return } for k, v := range results.Results { fmt.Printf("%s? %t\n", k, v.ExpressionPass) } }
Output: accounting_honors? true arts_honors? false last_3_grades_above_3? true
Example (List) ¶
package main import ( "context" "fmt" "github.com/ezachrisen/indigo" "github.com/ezachrisen/indigo/cel" ) func main() { //Step 1: Create a schema schema := indigo.Schema{ Elements: []indigo.DataElement{ {Name: "grades", Type: indigo.List{ValueType: indigo.Float{}}}, }, } // Step 2: Create rules rule := indigo.Rule{ Schema: schema, ResultType: indigo.Bool{}, Expr: `size(grades) > 3`, } // Step 3: Create an Indigo engine and give it an evaluator // In this case, CEL engine := indigo.NewEngine(cel.NewEvaluator()) // Step 4: Compile the rule err := engine.Compile(&rule) if err != nil { fmt.Println(err) return } data := map[string]interface{}{ "grades": []float64{3.4, 3.6, 3.8, 2.9}, } // Step 5: Evaluate and check the results results, err := engine.Eval(context.Background(), &rule, data) if err != nil { fmt.Println(err) } else { fmt.Println("Is size(grades) > 3? ", results.ExpressionPass) } // Check the value of a specific element rule.Expr = `grades[1] == 3.6` engine.Compile(&rule) results, _ = engine.Eval(context.Background(), &rule, data) fmt.Println("Is element 1 == 3.6? ", results.ExpressionPass) // Check if the list contains a value rule.Expr = `grades.exists(g, g < 3.0)` engine.Compile(&rule) results, _ = engine.Eval(context.Background(), &rule, data) fmt.Println("Any grades below 3.0? ", results.ExpressionPass) }
Output: Is size(grades) > 3? true Is element 1 == 3.6? true Any grades below 3.0? true
Example (Manual) ¶
Example_manual demonstrates evaluating multiple rules and processing the results manually
package main import ( "context" "fmt" "github.com/ezachrisen/indigo" "github.com/ezachrisen/indigo/cel" "github.com/ezachrisen/indigo/testdata/school" ) func main() { // In this example we're going to determine which, if any, // communications the administration should send to the student. education := indigo.Schema{ Elements: []indigo.DataElement{ {Name: "s", Type: indigo.Proto{Message: &school.Student{}}}, }, } data := map[string]interface{}{ "s": &school.Student{ Id: 927312, Age: 21, Credits: 16, Gpa: 3.1, Attrs: map[string]string{"major": "Accounting", "home_town": "Chicago"}, Status: school.Student_ENROLLED, Grades: []float64{3, 3, 4, 2, 3, 3.5, 4}, }, } accounting_honors := indigo.Rule{ Schema: education, Expr: `s.attrs.exists(k, k == "major" && s.attrs[k] == "Accounting") && s.gpa > 3`, } arts_honors := indigo.Rule{ Schema: education, Expr: `s.attrs.exists(k, k == "major" && s.attrs[k] == "Arts") && s.gpa > 3`, } last_3_grades_3_or_above := indigo.Rule{ Schema: education, Expr: `size(s.grades) >=3 && s.grades[size(s.grades)-1] >= 3.0 && s.grades[size(s.grades)-2] >= 3.0 && s.grades[size(s.grades)-3] >= 3.0 `, } engine := indigo.NewEngine(cel.NewEvaluator()) err := engine.Compile(&accounting_honors) if err != nil { fmt.Printf("Error adding rule %v", err) return } results, err := engine.Eval(context.Background(), &accounting_honors, data) if err != nil { fmt.Printf("Error evaluating: %v", err) return } fmt.Println("accounting_honors?", results.ExpressionPass) err = engine.Compile(&arts_honors) if err != nil { fmt.Printf("Error adding rule %v", err) return } results, err = engine.Eval(context.Background(), &arts_honors, data) if err != nil { fmt.Printf("Error evaluating: %v", err) return } fmt.Println("arts_honors?", results.ExpressionPass) err = engine.Compile(&last_3_grades_3_or_above) if err != nil { fmt.Printf("Error adding rule %v", err) return } results, err = engine.Eval(context.Background(), &last_3_grades_3_or_above, data) if err != nil { fmt.Printf("Error evaluating: %v", err) return } fmt.Println("last_3_grades_above_3?", results.ExpressionPass) }
Output: accounting_honors? true arts_honors? false last_3_grades_above_3? true
Example (Map) ¶
package main import ( "context" "fmt" "github.com/ezachrisen/indigo" "github.com/ezachrisen/indigo/cel" ) func main() { schema := indigo.Schema{ Elements: []indigo.DataElement{ {Name: "flights", Type: indigo.Map{KeyType: indigo.String{}, ValueType: indigo.String{}}}, }, } rule := indigo.Rule{ Schema: schema, ResultType: indigo.Bool{}, Expr: `flights.exists(k, flights[k] == "Delayed")`, } engine := indigo.NewEngine(cel.NewEvaluator()) err := engine.Compile(&rule) if err != nil { fmt.Println(err) return } data := map[string]interface{}{ "flights": map[string]string{"UA1500": "On Time", "DL232": "Delayed", "AA1622": "Delayed"}, } // Step 5: Evaluate and check the results results, err := engine.Eval(context.Background(), &rule, data) if err != nil { fmt.Println(err) } else { fmt.Println("Are any flights delayed?", results.ExpressionPass) } }
Output: Are any flights delayed? true
Example (NativeTimestampComparison) ¶
package main import ( "context" "fmt" "github.com/ezachrisen/indigo" "github.com/ezachrisen/indigo/cel" "google.golang.org/protobuf/types/known/timestamppb" ) func main() { schema := indigo.Schema{ Elements: []indigo.DataElement{ {Name: "then", Type: indigo.String{}}, {Name: "now", Type: indigo.Timestamp{}}, }, } data := map[string]interface{}{ "then": "1972-01-01T10:00:20.021-05:00", //"2018-08-03T16:00:00-07:00", "now": timestamppb.Now(), } rule := indigo.Rule{ ID: "time_check", Schema: schema, Expr: `now > timestamp(then)`, } engine := indigo.NewEngine(cel.NewEvaluator()) err := engine.Compile(&rule) if err != nil { fmt.Printf("Error adding rule %v", err) return } results, err := engine.Eval(context.Background(), &rule, data) if err != nil { fmt.Printf("Error evaluating: %v", err) return } fmt.Println(results.ExpressionPass) }
Output: true
Example (ProtoBasic) ¶
Demonstrates basic protocol buffer usage in a rule
package main import ( "context" "fmt" "github.com/ezachrisen/indigo" "github.com/ezachrisen/indigo/cel" "github.com/ezachrisen/indigo/testdata/school" ) func main() { education := indigo.Schema{ Elements: []indigo.DataElement{ {Name: "student", Type: indigo.Proto{Message: &school.Student{}}}, }, } data := map[string]interface{}{ "student": &school.Student{ Age: 21, }, } rule := indigo.Rule{ Schema: education, Expr: `student.age > 21`, } engine := indigo.NewEngine(cel.NewEvaluator()) err := engine.Compile(&rule) if err != nil { fmt.Printf("Error adding rule %v", err) return } results, err := engine.Eval(context.Background(), &rule, data) if err != nil { fmt.Printf("Error evaluating: %v", err) return } fmt.Println(results.ExpressionPass) }
Output: false
Example (ProtoConstruction) ¶
Demonstrate constructing a proto message in an expression
package main import ( "context" "fmt" "github.com/ezachrisen/indigo" "github.com/ezachrisen/indigo/cel" "github.com/ezachrisen/indigo/testdata/school" ) func main() { education := indigo.Schema{ Elements: []indigo.DataElement{ {Name: "s", Type: indigo.Proto{Message: &school.Student{}}}, {Name: "student_suspension", Type: indigo.Proto{Message: &school.Student_Suspension{}}}, {Name: "studentSummary", Type: indigo.Proto{Message: &school.StudentSummary{}}}, }, } data := map[string]interface{}{ "s": &school.Student{ Grades: []float64{3.0, 2.9, 4.0, 2.1}, Suspensions: []*school.Student_Suspension{ &school.Student_Suspension{Cause: "Cheating"}, &school.Student_Suspension{Cause: "Fighting"}, }, }, } rule := indigo.Rule{ ID: "create_summary", Schema: education, ResultType: indigo.Proto{Message: &school.StudentSummary{}}, Expr: ` testdata.school.StudentSummary { gpa: s.gpa, risk_factor: 2.0 + 3.0, tenure: duration("12h") }`, } engine := indigo.NewEngine(cel.NewEvaluator()) err := engine.Compile(&rule) if err != nil { fmt.Printf("Error adding rule %v", err) return } result, err := engine.Eval(context.Background(), &rule, data) if err != nil { // fmt.Printf("Error evaluating: %v", err) return } summary := result.Value.(*school.StudentSummary) fmt.Printf("%T\n", summary) fmt.Printf("%0.0f\n", summary.RiskFactor) }
Output: *school.StudentSummary 5
Example (ProtoConstructionConditional) ¶
Demonstrate using the ? : operator to conditionally construct a proto message
package main import ( "context" "fmt" "github.com/ezachrisen/indigo" "github.com/ezachrisen/indigo/cel" "github.com/ezachrisen/indigo/testdata/school" ) func main() { education := indigo.Schema{ Elements: []indigo.DataElement{ {Name: "student", Type: indigo.Proto{Message: &school.Student{}}}, {Name: "student_suspension", Type: indigo.Proto{Message: &school.Student_Suspension{}}}, {Name: "studentSummary", Type: indigo.Proto{Message: &school.StudentSummary{}}}, }, } data := map[string]interface{}{ "student": &school.Student{ Gpa: 4.0, Grades: []float64{3.0, 2.9, 4.0, 2.1}, Suspensions: []*school.Student_Suspension{ &school.Student_Suspension{Cause: "Cheating"}, &school.Student_Suspension{Cause: "Fighting"}, }, }, } rule := indigo.Rule{ ID: "create_summary", Schema: education, ResultType: indigo.Proto{Message: &school.StudentSummary{}}, Expr: ` student.gpa > 3.0 ? testdata.school.StudentSummary { gpa: student.gpa, risk_factor: 0.0 } : testdata.school.StudentSummary { gpa: student.gpa, risk_factor: 2.0 + 3.0, tenure: duration("12h") } `, } engine := indigo.NewEngine(cel.NewEvaluator()) err := engine.Compile(&rule) if err != nil { fmt.Printf("Error adding rule %v", err) return } results, err := engine.Eval(context.Background(), &rule, data) if err != nil { fmt.Printf("Error evaluating: %v", err) return } // The result is a fully-formed school.StudentSummary message. // There is no need to convert it. fmt.Printf("%T\n", results.Value) summ := results.Value.(*school.StudentSummary) fmt.Println(summ.RiskFactor) }
Output: *school.StudentSummary 0
Example (ProtoDurationCalculation) ¶
Demonstrates writing rules on timestamps and durations
package main import ( "context" "fmt" "time" "github.com/ezachrisen/indigo" "github.com/ezachrisen/indigo/cel" "google.golang.org/protobuf/types/known/timestamppb" "github.com/ezachrisen/indigo/testdata/school" ) func main() { education := indigo.Schema{ Elements: []indigo.DataElement{ {Name: "s", Type: indigo.Proto{Message: &school.Student{}}}, {Name: "now", Type: indigo.Timestamp{}}, {Name: "ssum", Type: indigo.Proto{Message: &school.StudentSummary{}}}, }, } data := map[string]interface{}{ "s": &school.Student{ EnrollmentDate: timestamppb.New(time.Date(2009, time.November, 10, 23, 0, 0, 0, time.UTC)), }, "now": timestamppb.Now(), } rule := indigo.Rule{ ID: "", Schema: education, Expr: `// 2,400h = 100 days * 24 hours now - s.enrollment_date > duration("2400h") `, } engine := indigo.NewEngine(cel.NewEvaluator()) err := engine.Compile(&rule) if err != nil { fmt.Printf("Error adding rule %v", err) return } results, err := engine.Eval(context.Background(), &rule, data) if err != nil { fmt.Printf("Error evaluating: %v", err) return } fmt.Println(results.ExpressionPass) }
Output: true
Example (ProtoDurationComparison) ¶
Demonstrates conversion between protobuf durations (google.protobuf.Duration) and time.Duration
package main import ( "context" "fmt" "time" "github.com/ezachrisen/indigo" "github.com/ezachrisen/indigo/cel" "google.golang.org/protobuf/types/known/durationpb" "github.com/ezachrisen/indigo/testdata/school" ) func main() { education := indigo.Schema{ Elements: []indigo.DataElement{ {Name: "smry", Type: indigo.Proto{Message: &school.StudentSummary{}}}, }, } godur, _ := time.ParseDuration("10h") data := map[string]interface{}{ "smry": &school.StudentSummary{ Tenure: durationpb.New(godur), }, } rule := indigo.Rule{ ID: "tenure_check", Schema: education, Expr: `smry.tenure > duration("1h")`, } engine := indigo.NewEngine(cel.NewEvaluator()) err := engine.Compile(&rule) if err != nil { fmt.Printf("Error adding rule %v", err) return } results, err := engine.Eval(context.Background(), &rule, data) if err != nil { fmt.Printf("Error evaluating: %v", err) return } fmt.Println(results.ExpressionPass) }
Output: true
Example (ProtoEnum) ¶
Demonstrates using a protocol buffer enum value in a rule
package main import ( "context" "fmt" "github.com/ezachrisen/indigo" "github.com/ezachrisen/indigo/cel" "github.com/ezachrisen/indigo/testdata/school" ) func main() { education := indigo.Schema{ Elements: []indigo.DataElement{ {Name: "student", Type: indigo.Proto{Message: &school.Student{}}}, }, } data := map[string]interface{}{ "student": &school.Student{ Status: school.Student_ENROLLED, }, } rule := indigo.Rule{ Schema: education, Expr: `student.status == testdata.school.Student.status_type.PROBATION`, } engine := indigo.NewEngine(cel.NewEvaluator()) err := engine.Compile(&rule) if err != nil { fmt.Printf("Error adding rule %v", err) return } results, err := engine.Eval(context.Background(), &rule, data) if err != nil { fmt.Printf("Error evaluating: %v", err) return } fmt.Println(results.ExpressionPass) }
Output: false
Example (ProtoExistsOperator) ¶
Demonstrates using the CEL exists function to check for a value in a slice
package main import ( "context" "fmt" "github.com/ezachrisen/indigo" "github.com/ezachrisen/indigo/cel" "github.com/ezachrisen/indigo/testdata/school" ) func main() { education := indigo.Schema{ Elements: []indigo.DataElement{ {Name: "student", Type: indigo.Proto{Message: &school.Student{}}}, }, } data := map[string]interface{}{ "student": &school.Student{ Grades: []float64{3.0, 2.9, 4.0, 2.1}, }, } rule := indigo.Rule{ ID: "grade_check", Schema: education, Expr: `student.grades.exists(g, g < 2.0)`, } engine := indigo.NewEngine(cel.NewEvaluator()) err := engine.Compile(&rule) if err != nil { fmt.Printf("Error adding rule %v", err) return } results, err := engine.Eval(context.Background(), &rule, data) if err != nil { fmt.Printf("Error evaluating: %v", err) return } fmt.Println(results.ExpressionPass) }
Output: false
Example (ProtoNestedMessages) ¶
Demonstrates using the exists macro to inspect the value of nested messages in the list
package main import ( "context" "fmt" "github.com/ezachrisen/indigo" "github.com/ezachrisen/indigo/cel" "github.com/ezachrisen/indigo/testdata/school" ) func main() { education := indigo.Schema{ Elements: []indigo.DataElement{ {Name: "x", Type: indigo.Proto{Message: &school.Student{}}}}, } data := map[string]interface{}{ "x": &school.Student{ Grades: []float64{3.0, 2.9, 4.0, 2.1}, Suspensions: []*school.Student_Suspension{ &school.Student_Suspension{Cause: "Cheating"}, &school.Student_Suspension{Cause: "Fighting"}, }, }, } // Check if the student was ever suspended for fighting rule := indigo.Rule{ ID: "fighting_check", Schema: education, Expr: `x.suspensions.exists(s, s.cause == "Fighting")`, } engine := indigo.NewEngine(cel.NewEvaluator()) err := engine.Compile(&rule) if err != nil { fmt.Printf("Error adding rule %v", err) return } results, err := engine.Eval(context.Background(), &rule, data) if err != nil { fmt.Printf("Error evaluating: %v", err) return } fmt.Println(results.Value) }
Output: true
Example (ProtoOneof) ¶
Demonstrates using a protocol buffer oneof value in a rule
package main import ( "context" "fmt" "github.com/ezachrisen/indigo" "github.com/ezachrisen/indigo/cel" "github.com/ezachrisen/indigo/testdata/school" ) func main() { education := indigo.Schema{ Elements: []indigo.DataElement{ {Name: "student", Type: indigo.Proto{Message: &school.Student{}}}, }, } data := map[string]interface{}{ "student": &school.Student{ Status: school.Student_ENROLLED, HousingAddress: &school.Student_OnCampus{ &school.Student_CampusAddress{ Building: "Hershey", Room: "308", }, }, }, } rule := indigo.Rule{ Schema: education, Expr: `has(student.on_campus) && student.on_campus.building == "Hershey"`, } engine := indigo.NewEngine(cel.NewEvaluator()) err := engine.Compile(&rule) if err != nil { fmt.Printf("Error adding rule %v", err) return } results, err := engine.Eval(context.Background(), &rule, data) if err != nil { fmt.Printf("Error evaluating: %v", err) return } fmt.Println(results.ExpressionPass) }
Output: true
Example (ProtoTimestampAndDurationComparison) ¶
Demonstrates writing rules on timestamps and durations
package main import ( "context" "fmt" "time" "github.com/ezachrisen/indigo" "github.com/ezachrisen/indigo/cel" "google.golang.org/protobuf/types/known/durationpb" "google.golang.org/protobuf/types/known/timestamppb" "github.com/ezachrisen/indigo/testdata/school" ) func main() { education := indigo.Schema{ Elements: []indigo.DataElement{ {Name: "s", Type: indigo.Proto{Message: &school.Student{}}}, {Name: "now", Type: indigo.Timestamp{}}, {Name: "ssum", Type: indigo.Proto{Message: &school.StudentSummary{}}}, }, } data := map[string]interface{}{ "s": &school.Student{ EnrollmentDate: timestamppb.New(time.Date(2009, time.November, 10, 23, 0, 0, 0, time.UTC)), }, "ssum": &school.StudentSummary{ Tenure: durationpb.New(time.Duration(time.Hour * 24 * 451)), }, "now": timestamppb.Now(), } rule := indigo.Rule{ ID: "", Schema: education, Expr: `s.enrollment_date < now && // 12,000h = 500 days * 24 hours ssum.tenure > duration("12000h") `, } engine := indigo.NewEngine(cel.NewEvaluator()) err := engine.Compile(&rule) if err != nil { fmt.Printf("Error adding rule %v", err) return } results, err := engine.Eval(context.Background(), &rule, data) if err != nil { fmt.Printf("Error evaluating: %v", err) return } fmt.Println(results.ExpressionPass) }
Output: false
Example (ProtoTimestampComparison) ¶
Demonstrates conversion between protobuf timestamps (google.protobuf.Timestamp) and time.Time
package main import ( "context" "fmt" "time" "github.com/ezachrisen/indigo" "github.com/ezachrisen/indigo/cel" "google.golang.org/protobuf/types/known/timestamppb" "github.com/ezachrisen/indigo/testdata/school" ) func main() { education := indigo.Schema{ Elements: []indigo.DataElement{ {Name: "student", Type: indigo.Proto{Message: &school.Student{}}}, {Name: "now", Type: indigo.Timestamp{}}, }, } data := map[string]interface{}{ "student": &school.Student{ // Make a protobuf timestamp from a time.Time EnrollmentDate: timestamppb.New(time.Date(2009, time.November, 10, 23, 0, 0, 0, time.UTC)), Grades: []float64{3.0, 2.9, 4.0, 2.1}, }, "now": timestamppb.Now(), } // The rule will return the earlier of the two dates (enrollment date or now) rule := indigo.Rule{ ID: "grade_check", Schema: education, ResultType: indigo.Timestamp{}, Expr: `student.enrollment_date < now ? student.enrollment_date : now `, } engine := indigo.NewEngine(cel.NewEvaluator()) err := engine.Compile(&rule) if err != nil { fmt.Printf("Error adding rule %v", err) return } results, err := engine.Eval(context.Background(), &rule, data) if err != nil { fmt.Printf("Error evaluating: %v", err) return } if ts, ok := results.Value.(time.Time); ok { fmt.Printf("Gotime is %v\n", ts) } }
Output: Gotime is 2009-11-10 23:00:00 +0000 UTC
Example (ProtoTimestampPart) ¶
Demonstrates writing rules on timestamps and durations
package main import ( "context" "fmt" "time" "github.com/ezachrisen/indigo" "github.com/ezachrisen/indigo/cel" "google.golang.org/protobuf/types/known/timestamppb" "github.com/ezachrisen/indigo/testdata/school" ) func main() { education := indigo.Schema{ Elements: []indigo.DataElement{ {Name: "s", Type: indigo.Proto{Message: &school.Student{}}}, }, } data := map[string]interface{}{ "s": &school.Student{ EnrollmentDate: timestamppb.New(time.Date(2022, time.April, 8, 23, 0, 0, 0, time.UTC)), }, } rule := indigo.Rule{ ID: "", ResultType: indigo.Bool{}, Schema: education, Expr: `s.enrollment_date.getDayOfWeek() == 5 // Friday`, } engine := indigo.NewEngine(cel.NewEvaluator()) err := engine.Compile(&rule) if err != nil { fmt.Printf("Error adding rule %v", err) return } results, err := engine.Eval(context.Background(), &rule, data) if err != nil { fmt.Printf("Error evaluating: %v", err) return } fmt.Println(results.Value) }
Output: true
Example (ProtoTimestampPartTZ) ¶
Demonstrates writing rules on timestamps and durations
package main import ( "context" "fmt" "time" "github.com/ezachrisen/indigo" "github.com/ezachrisen/indigo/cel" "google.golang.org/protobuf/types/known/timestamppb" "github.com/ezachrisen/indigo/testdata/school" ) func main() { education := indigo.Schema{ Elements: []indigo.DataElement{ {Name: "s", Type: indigo.Proto{Message: &school.Student{}}}, }, } data := map[string]interface{}{ "s": &school.Student{ EnrollmentDate: timestamppb.New(time.Date(2022, time.April, 8, 23, 0, 0, 0, time.UTC)), }, } rule := indigo.Rule{ ID: "", ResultType: indigo.Bool{}, Schema: education, Expr: `s.enrollment_date.getDayOfWeek("Asia/Kolkata") == 6 // Saturday`, } engine := indigo.NewEngine(cel.NewEvaluator()) err := engine.Compile(&rule) if err != nil { fmt.Printf("Error adding rule %v", err) return } results, err := engine.Eval(context.Background(), &rule, data) if err != nil { fmt.Printf("Error evaluating: %v", err) return } fmt.Println(results.Value) }
Output: true
Example (StopIfParentNegative) ¶
package main import ( "context" "fmt" "github.com/ezachrisen/indigo" "github.com/ezachrisen/indigo/cel" "github.com/ezachrisen/indigo/testdata/school" ) func main() { education := indigo.Schema{ ID: "education", Elements: []indigo.DataElement{ {Name: "s", Type: indigo.Proto{Message: &school.Student{}}}, }, } data := map[string]interface{}{ "s": &school.Student{ Id: 927312, Age: 21, Credits: 16, Gpa: 3.1, Attrs: map[string]string{"major": "Accounting", "home_town": "Chicago"}, Status: school.Student_ENROLLED, Grades: []float64{3, 3, 4, 2, 3, 3.5, 4}, }, } root := indigo.NewRule("root", "") root.Schema = education accounting := indigo.NewRule("accounting_majors_only", `s.attrs.exists(k, k == "major" && s.attrs[k] == "Accounting")`) accounting.Schema = education accounting.EvalOptions.StopIfParentNegative = true root.Rules[accounting.ID] = accounting accounting.Rules["honors"] = &indigo.Rule{ ID: "honors", Schema: education, Expr: "s.gpa > 3.0", } accounting.Rules["at_risk"] = &indigo.Rule{ ID: "at_risk", Schema: education, Expr: "s.gpa < 2.0", } accounting.Rules["rookie"] = &indigo.Rule{ ID: "rookie", Schema: education, Expr: "s.credits < 5", } engine := indigo.NewEngine(cel.NewEvaluator()) err := engine.Compile(root) if err != nil { fmt.Printf("Error adding rule %v", err) return } // fmt.Println(root) results, err := engine.Eval(context.Background(), root, data) if err != nil { fmt.Printf("Error evaluating: %v", err) return } for k, v := range results.Results["accounting_majors_only"].Results { fmt.Printf("%s? %t\n", k, v.ExpressionPass) } //fmt.Println(results) }
Output: rookie? false honors? true at_risk? false
Index ¶
Examples ¶
- Package
- Package (Alarms)
- Package (AlarmsTwoLevel)
- Package (Basic)
- Package (Indigo)
- Package (List)
- Package (Manual)
- Package (Map)
- Package (NativeTimestampComparison)
- Package (ProtoBasic)
- Package (ProtoConstruction)
- Package (ProtoConstructionConditional)
- Package (ProtoDurationCalculation)
- Package (ProtoDurationComparison)
- Package (ProtoEnum)
- Package (ProtoExistsOperator)
- Package (ProtoNestedMessages)
- Package (ProtoOneof)
- Package (ProtoTimestampAndDurationComparison)
- Package (ProtoTimestampComparison)
- Package (ProtoTimestampPart)
- Package (ProtoTimestampPartTZ)
- Package (StopIfParentNegative)
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
This section is empty.
Types ¶
type CelOption ¶ added in v0.6.8
type CelOption func(e *Evaluator)
func FixedSchema ¶ added in v0.6.8
FixedSchema mandates that the evaluator will use this schema for all compilations and evaluations. Schemas set on rules will be ignored. CEL's process to create a celgo.Env from a schema is time consuming; setting the FixedSchema option reduces compilation time by 25% or more. The schema will be evaluated the first time compilation runs.
type Evaluator ¶ added in v0.6.2
type Evaluator struct {
// contains filtered or unexported fields
}
Evaluator implements the indigo.ExpressionEvaluator and indigo.ExpressionCompiler interfaces. It uses the CEL-Go package to compile and evaluate rules.
func NewEvaluator ¶
NewEvaluator creates a new CEL Evaluator. The evaluator contains internal data used to facilitate CEL expression evaluation.
func (*Evaluator) Compile ¶ added in v0.6.2
func (e *Evaluator) Compile(expr string, s indigo.Schema, resultType indigo.Type, collectDiagnostics bool, _ bool) (interface{}, error)
Compile checks a rule, prepares a compiled CELProgram, and stores the program in rule.Program. CELProgram contains the compiled program used to evaluate the rules, and if we're collecting diagnostics, CELProgram also contains the CEL AST to provide type and symbol information in diagnostics.
Any errors in compilation are returned with a nil program
func (*Evaluator) Evaluate ¶ added in v0.6.2
func (*Evaluator) Evaluate(data map[string]interface{}, expr string, _ indigo.Schema, _ interface{}, evalData interface{}, expectedResultType indigo.Type, returnDiagnostics bool) (interface{}, *indigo.Diagnostics, error)
Evaluate a rule against the input data. Called by indigo.Engine.Evaluate for the rule and its children.