diff --git a/build/config/substation.libsonnet b/build/config/substation.libsonnet index 90c6be88..a8e63d6d 100644 --- a/build/config/substation.libsonnet +++ b/build/config/substation.libsonnet @@ -25,8 +25,15 @@ num: $.condition.number, number: { default: { - object: $.config.object, - value: null, + object: $.config.object, + value: null, + }, + eq(settings={}): $.condition.number.equal_to(settings=settings), + equal_to(settings={}): { + local default = $.condition.number.default, + + type: 'number_equal_to', + settings: std.prune(std.mergePatch(default, $.helpers.abbv(settings))), }, lt(settings={}): $.condition.number.less_than(settings=settings), less_than(settings={}): { diff --git a/build/config/substation_test.jsonnet b/build/config/substation_test.jsonnet index 78eb7566..ade5344a 100644 --- a/build/config/substation_test.jsonnet +++ b/build/config/substation_test.jsonnet @@ -9,9 +9,10 @@ local inspector = sub.condition.format.json(); { condition: { number: { + equal_to: sub.condition.number.equal_to({obj: {src: src}, value: 1}), less_than: sub.condition.number.less_than({obj: {src: src}, value: 1}), greater_than: sub.condition.number.greater_than({obj: {src: src}, value: 1}), - } + }, }, transform: { send: { diff --git a/condition/condition.go b/condition/condition.go index 60215390..d629758a 100644 --- a/condition/condition.go +++ b/condition/condition.go @@ -58,6 +58,8 @@ func newInspector(ctx context.Context, cfg config.Config) (inspector, error) { / case "network_ip_valid": return newNetworkIPValid(ctx, cfg) // Number inspectors. + case "number_equal_to": + return newNumberEqualTo(ctx, cfg) case "number_less_than": return newNumberLessThan(ctx, cfg) case "number_greater_than": diff --git a/condition/number_equal_to.go b/condition/number_equal_to.go new file mode 100644 index 00000000..ff133720 --- /dev/null +++ b/condition/number_equal_to.go @@ -0,0 +1,51 @@ +package condition + +import ( + "context" + "encoding/json" + "strconv" + + "github.com/brexhq/substation/config" + "github.com/brexhq/substation/message" +) + +func newNumberEqualTo(_ context.Context, cfg config.Config) (*numberEqualTo, error) { + conf := numberConfig{} + if err := conf.Decode(cfg.Settings); err != nil { + return nil, err + } + insp := numberEqualTo{ + conf: conf, + } + return &insp, nil +} + +type numberEqualTo struct { + conf numberConfig +} + +func (insp *numberEqualTo) Inspect(ctx context.Context, msg *message.Message) (output bool, err error) { + if msg.IsControl() { + return false, nil + } + + if insp.conf.Object.SourceKey == "" { + f, err := strconv.ParseFloat(string(msg.Data()), 64) + if err != nil { + return false, err + } + + return insp.match(f), nil + } + v := msg.GetValue(insp.conf.Object.SourceKey) + return insp.match(v.Float()), nil +} + +func (c *numberEqualTo) match(f float64) bool { + return f == c.conf.Value +} + +func (c *numberEqualTo) String() string { + b, _ := json.Marshal(c.conf) + return string(b) +} diff --git a/condition/number_equal_to_test.go b/condition/number_equal_to_test.go new file mode 100644 index 00000000..6275ac98 --- /dev/null +++ b/condition/number_equal_to_test.go @@ -0,0 +1,158 @@ +package condition + +import ( + "context" + "testing" + + "github.com/brexhq/substation/config" + "github.com/brexhq/substation/message" +) + +var _ inspector = &numberEqualTo{} + +var numberEqualToTests = []struct { + name string + cfg config.Config + test []byte + expected bool +}{ + // Integers + { + "pass", + config.Config{ + Settings: map[string]interface{}{ + "object": map[string]interface{}{ + "source_key": "foo", + }, + "value": 14, + }, + }, + []byte(`{"foo":14}`), + true, + }, + { + "fail", + config.Config{ + Settings: map[string]interface{}{ + "value": 10, + }, + }, + []byte(`1`), + false, + }, + { + "pass", + config.Config{ + Settings: map[string]interface{}{ + "object": map[string]interface{}{ + "source_key": "foo", + }, + "value": 0, + }, + }, + []byte(`{"foo":0}`), + true, + }, + { + "fail", + config.Config{ + Settings: map[string]interface{}{ + "value": 5, + }, + }, + []byte(`15`), + false, + }, + // Floats + { + "fail", + config.Config{ + Settings: map[string]interface{}{ + "value": 1, + }, + }, + []byte(`1.5`), + false, + }, + { + "fail", + config.Config{ + Settings: map[string]interface{}{ + "value": 0.1, + }, + }, + []byte(`1.5`), + false, + }, + { + "pass", + config.Config{ + Settings: map[string]interface{}{ + "object": map[string]interface{}{ + "source_key": "foo", + }, + "value": 1.1, + }, + }, + []byte(`{"foo":1.1}`), + true, + }, + { + "pass", + config.Config{ + Settings: map[string]interface{}{ + "value": 1.4, + }, + }, + []byte(`1.4`), + true, + }, +} + +func TestNumberEqualTo(t *testing.T) { + ctx := context.TODO() + + for _, test := range numberEqualToTests { + t.Run(test.name, func(t *testing.T) { + message := message.New().SetData(test.test) + insp, err := newNumberEqualTo(ctx, test.cfg) + if err != nil { + t.Fatal(err) + } + + check, err := insp.Inspect(ctx, message) + if err != nil { + t.Error(err) + } + + if test.expected != check { + t.Errorf("expected %v, got %v", test.expected, check) + t.Errorf("settings: %+v", test.cfg) + t.Errorf("test: %+v", string(test.test)) + } + }) + } +} + +func benchmarkNumberEqualTo(b *testing.B, insp *numberEqualTo, message *message.Message) { + ctx := context.TODO() + for i := 0; i < b.N; i++ { + _, _ = insp.Inspect(ctx, message) + } +} + +func BenchmarkNumberEqualTo(b *testing.B) { + for _, test := range numberEqualToTests { + insp, err := newNumberEqualTo(context.TODO(), test.cfg) + if err != nil { + b.Fatal(err) + } + + b.Run(test.name, + func(b *testing.B) { + message := message.New().SetData(test.test) + benchmarkNumberEqualTo(b, insp, message) + }, + ) + } +}