Skip to content

Commit

Permalink
feat(condition): Add MetaErr Inspector (#217)
Browse files Browse the repository at this point in the history
  • Loading branch information
jshlbrd authored Jul 26, 2024
1 parent af44e66 commit b9c685e
Show file tree
Hide file tree
Showing 3 changed files with 257 additions and 2 deletions.
13 changes: 11 additions & 2 deletions build/config/substation.libsonnet
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,15 @@
type: 'meta_condition',
settings: std.prune(std.mergePatch(default, $.helpers.abbv(settings))),
},
err(settings={}): {
local default = {
inspector: null,
error_messages: null,
},

type: 'meta_err',
settings: std.prune(std.mergePatch(default, $.helpers.abbv(settings))),
},
for_each(settings={}): {
local default = {
object: $.config.object,
Expand Down Expand Up @@ -1384,9 +1393,9 @@
conditional(condition, transform): {
local type = 'meta_switch',
local c = if std.objectHas(condition, 'type') then { operator: 'any', inspectors: [condition] } else condition,

type: type,
settings: { id: $.helpers.id(type, transform), cases: [{ condition: c, transform: transform }] },
settings: { id: $.helpers.id(type, transform), cases: [{ condition: c, transform: transform }] },
},
fmt: $.pattern.transform.format,
format: {
Expand Down
107 changes: 107 additions & 0 deletions condition/meta_err.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
package condition

import (
"context"
"encoding/json"
"fmt"
"regexp"

"github.com/brexhq/substation/config"
"github.com/brexhq/substation/message"

iconfig "github.com/brexhq/substation/internal/config"
"github.com/brexhq/substation/internal/errors"
)

type metaErrConfig struct {
// Inspector used to inspect the message. If the inspector
// throws an error, this inspector will return false.
Inspector config.Config `json:"inspector"`
// ErrorMessages are regular expressions that match error messages and determine
// if the error should be caught.
//
// This is optional and defaults to an empty list (all errors are caught).
ErrorMessages []string `json:"error_messages"`
}

func (c *metaErrConfig) Decode(in interface{}) error {
return iconfig.Decode(in, c)
}

func (c *metaErrConfig) Validate() error {
if c.Inspector.Type == "" {
return fmt.Errorf("inspector: %v", errors.ErrMissingRequiredOption)
}

return nil
}

func newMetaErr(ctx context.Context, cfg config.Config) (*metaErr, error) {
conf := metaErrConfig{}
if err := conf.Decode(cfg.Settings); err != nil {
return nil, err
}

if err := conf.Validate(); err != nil {
return nil, err
}

i, err := newInspector(ctx, conf.Inspector)
if err != nil {
return nil, fmt.Errorf("condition: meta_err: %v", err)
}

meta := metaErr{
conf: conf,
insp: i,
}

meta.errorMessages = make([]*regexp.Regexp, len(conf.ErrorMessages))
for i, em := range conf.ErrorMessages {
re, err := regexp.Compile(em)
if err != nil {
return nil, fmt.Errorf("condition: meta_err: %v", err)
}

meta.errorMessages[i] = re
}

return &meta, nil
}

type metaErr struct {
conf metaErrConfig

insp inspector
errorMessages []*regexp.Regexp
}

func (c *metaErr) Inspect(ctx context.Context, msg *message.Message) (bool, error) {
if msg.IsControl() {
return false, nil
}

match, err := c.insp.Inspect(ctx, msg)
if err != nil {
// Catch all errors.
if len(c.errorMessages) == 0 {
return false, nil
}

// Catch specific errors.
for _, re := range c.errorMessages {
if re.MatchString(err.Error()) {
return false, nil
}
}

return false, fmt.Errorf("condition: meta_err: %v", err)
}

return match, nil
}

func (c *metaErr) String() string {
b, _ := json.Marshal(c.conf)
return string(b)
}
139 changes: 139 additions & 0 deletions condition/meta_err_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
package condition

import (
"context"
"testing"

"github.com/brexhq/substation/config"
"github.com/brexhq/substation/message"
)

var _ inspector = &metaErr{}

var metaErrTests = []struct {
name string
cfg config.Config
data []byte
expected bool
}{
{
"catch_all",
config.Config{
Settings: map[string]interface{}{
"inspector": map[string]interface{}{
"settings": map[string]interface{}{
"object": map[string]interface{}{
"source_key": "a",
},
"inspector": map[string]interface{}{
"type": "string_starts_with",
"settings": map[string]interface{}{
"string": "c",
},
},
"type": "any",
},
"type": "meta_for_each",
},
},
},
[]byte(`{"a":"bcd"}`),
false,
},
{
"catch_one",
config.Config{
Settings: map[string]interface{}{
"error_messages": []string{"input must be an array"},
"inspector": map[string]interface{}{
"settings": map[string]interface{}{
"object": map[string]interface{}{
"source_key": "a",
},
"inspector": map[string]interface{}{
"type": "string_starts_with",
"settings": map[string]interface{}{
"string": "c",
},
},
"type": "any",
},
"type": "meta_for_each",
},
},
},
[]byte(`{"a":"bcd"}`),
false,
},
{
"no_error",
config.Config{
Settings: map[string]interface{}{
"inspector": map[string]interface{}{
"settings": map[string]interface{}{
"object": map[string]interface{}{
"source_key": "a",
},
"inspector": map[string]interface{}{
"type": "string_starts_with",
"settings": map[string]interface{}{
"string": "c",
},
},
"type": "any",
},
"type": "meta_for_each",
},
},
},
[]byte(`{"a":["bcd"]}`),
true,
},
}

func TestMetaErr(t *testing.T) {
ctx := context.TODO()

for _, test := range metaErrTests {
t.Run(test.name, func(t *testing.T) {
message := message.New().SetData(test.data)

insp, err := newMetaErr(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, %v", test.expected, check, string(test.data))
}
})
}
}

func benchmarkMetaErr(b *testing.B, insp *metaErr, message *message.Message) {
ctx := context.TODO()
for i := 0; i < b.N; i++ {
_, _ = insp.Inspect(ctx, message)
}
}

func BenchmarkMetaErr(b *testing.B) {
for _, test := range metaErrTests {
insp, err := newMetaErr(context.TODO(), test.cfg)
if err != nil {
b.Fatal(err)
}

b.Run(test.name,
func(b *testing.B) {
message := message.New().SetData(test.data)
benchmarkMetaErr(b, insp, message)
},
)
}
}

0 comments on commit b9c685e

Please sign in to comment.