-
Notifications
You must be signed in to change notification settings - Fork 15
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* feat: add number inspector * test: add missing unit tests for negation, and data processing * fix: looser interpretation when processing data * refactor: replace number inspector with bitmath * fix: update bitmath comments/errors
- Loading branch information
1 parent
667ceb3
commit 4721ffa
Showing
4 changed files
with
306 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,103 @@ | ||
package condition | ||
|
||
import ( | ||
"context" | ||
"fmt" | ||
"strconv" | ||
|
||
"golang.org/x/exp/slices" | ||
|
||
"github.com/brexhq/substation/config" | ||
"github.com/brexhq/substation/internal/errors" | ||
) | ||
|
||
// bitmath evaluates data using bitwith math operations. | ||
// | ||
// This inspector supports the data and object handling patterns. | ||
type inspBitmath struct { | ||
condition | ||
Options inspBitmathOptions `json:"options"` | ||
} | ||
|
||
type inspBitmathOptions struct { | ||
// Type is the bitmath evaluation Type used during inspection. | ||
// | ||
// Must be one of: | ||
// | ||
// - and | ||
// | ||
// - or | ||
// | ||
// - not | ||
// | ||
// - xor | ||
Type string `json:"type"` | ||
// Value is the number that is used for comparison during inspection. | ||
Value int64 `json:"value"` | ||
} | ||
|
||
// Creates a new bitmath inspector. | ||
func newInspBitmath(_ context.Context, cfg config.Config) (c inspBitmath, err error) { | ||
if err = config.Decode(cfg.Settings, &c); err != nil { | ||
return inspBitmath{}, err | ||
} | ||
|
||
// validate option.type | ||
if !slices.Contains( | ||
[]string{ | ||
"and", | ||
"or", | ||
"not", | ||
"xor", | ||
}, | ||
c.Options.Type) { | ||
return inspBitmath{}, fmt.Errorf("condition: bitmath: type %q: %v", c.Options.Type, errors.ErrInvalidOption) | ||
} | ||
|
||
return c, nil | ||
} | ||
|
||
func (c inspBitmath) String() string { | ||
return toString(c) | ||
} | ||
|
||
// Inspect evaluates encapsulated data with the bitmath inspector. | ||
func (c inspBitmath) Inspect(ctx context.Context, capsule config.Capsule) (output bool, err error) { | ||
var check int64 | ||
if c.Key == "" { | ||
check, err = strconv.ParseInt(string(capsule.Data()), 10, 64) | ||
if err != nil { | ||
return false, fmt.Errorf("condition: bitmath: invalid data: %v", err) | ||
} | ||
} else { | ||
check = capsule.Get(c.Key).Int() | ||
} | ||
|
||
var matched bool | ||
switch c.Options.Type { | ||
case "and": | ||
if check&c.Options.Value != 0 { | ||
matched = true | ||
} | ||
case "or": | ||
if check|c.Options.Value != 0 { | ||
matched = true | ||
} | ||
case "not": | ||
if ^check != 0 { | ||
matched = true | ||
} | ||
case "xor": | ||
if check^c.Options.Value != 0 { | ||
matched = true | ||
} | ||
default: | ||
return false, fmt.Errorf("condition: bitmath: type %s: %v", c.Options.Type, errors.ErrInvalidOption) | ||
} | ||
|
||
if c.Negate { | ||
return !matched, nil | ||
} | ||
|
||
return matched, nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,188 @@ | ||
package condition | ||
|
||
import ( | ||
"context" | ||
"testing" | ||
|
||
"github.com/brexhq/substation/config" | ||
) | ||
|
||
var _ Inspector = inspBitmath{} | ||
|
||
var bitmathTests = []struct { | ||
name string | ||
cfg config.Config | ||
test []byte | ||
expected bool | ||
}{ | ||
{ | ||
"pass xor", | ||
config.Config{ | ||
Type: "bitmath", | ||
Settings: map[string]interface{}{ | ||
"key": "foo", | ||
"options": map[string]interface{}{ | ||
"type": "xor", | ||
"value": 3, | ||
}, | ||
}, | ||
}, | ||
[]byte(`{"foo":"0"}`), | ||
true, | ||
}, | ||
{ | ||
"fail xor", | ||
config.Config{ | ||
Type: "bitmath", | ||
Settings: map[string]interface{}{ | ||
"key": "foo", | ||
"options": map[string]interface{}{ | ||
"type": "xor", | ||
"value": 42, | ||
}, | ||
}, | ||
}, | ||
[]byte(`{"foo":"42"}`), | ||
false, | ||
}, | ||
{ | ||
"!fail xor", | ||
config.Config{ | ||
Type: "bitmath", | ||
Settings: map[string]interface{}{ | ||
"key": "foo", | ||
"negate": true, | ||
"options": map[string]interface{}{ | ||
"type": "xor", | ||
"value": 42, | ||
}, | ||
}, | ||
}, | ||
[]byte(`{"foo":"42"}`), | ||
true, | ||
}, | ||
{ | ||
"pass or", | ||
config.Config{ | ||
Type: "bitmath", | ||
Settings: map[string]interface{}{ | ||
"key": "foo", | ||
"options": map[string]interface{}{ | ||
"type": "or", | ||
"value": -1, | ||
}, | ||
}, | ||
}, | ||
[]byte(`{"foo":"0"}`), | ||
true, | ||
}, | ||
{ | ||
"!pass or", | ||
config.Config{ | ||
Type: "bitmath", | ||
Settings: map[string]interface{}{ | ||
"key": "foo", | ||
"negate": true, | ||
"options": map[string]interface{}{ | ||
"type": "or", | ||
"value": 1, | ||
}, | ||
}, | ||
}, | ||
[]byte(`{"foo":"0"}`), | ||
false, | ||
}, | ||
{ | ||
"pass and", | ||
config.Config{ | ||
Type: "bitmath", | ||
Settings: map[string]interface{}{ | ||
"key": "foo", | ||
"options": map[string]interface{}{ | ||
"type": "and", | ||
"value": 0x0001, | ||
}, | ||
}, | ||
}, | ||
[]byte(`{"foo":"570506001"}`), | ||
true, | ||
}, | ||
{ | ||
"fail and", | ||
config.Config{ | ||
Type: "bitmath", | ||
Settings: map[string]interface{}{ | ||
"key": "foo", | ||
"options": map[string]interface{}{ | ||
"type": "and", | ||
"value": 0x0002, | ||
}, | ||
}, | ||
}, | ||
[]byte(`{"foo":"570506001"}`), | ||
false, | ||
}, | ||
{ | ||
"pass data", | ||
config.Config{ | ||
Type: "bitmath", | ||
Settings: map[string]interface{}{ | ||
"options": map[string]interface{}{ | ||
"type": "or", | ||
"value": 1, | ||
}, | ||
}, | ||
}, | ||
[]byte(`0001`), | ||
true, | ||
}, | ||
} | ||
|
||
func TestBitmath(t *testing.T) { | ||
ctx := context.TODO() | ||
capsule := config.NewCapsule() | ||
|
||
for _, test := range bitmathTests { | ||
t.Run(test.name, func(t *testing.T) { | ||
capsule.SetData(test.test) | ||
|
||
insp, err := newInspBitmath(ctx, test.cfg) | ||
if err != nil { | ||
t.Fatal(err) | ||
} | ||
|
||
check, err := insp.Inspect(ctx, capsule) | ||
if err != nil { | ||
t.Error(err) | ||
} | ||
|
||
if test.expected != check { | ||
t.Errorf("expected %v, got %v", test.expected, check) | ||
} | ||
}) | ||
} | ||
} | ||
|
||
func benchmarkBitmathByte(b *testing.B, inspector inspBitmath, capsule config.Capsule) { | ||
ctx := context.TODO() | ||
for i := 0; i < b.N; i++ { | ||
_, _ = inspector.Inspect(ctx, capsule) | ||
} | ||
} | ||
|
||
func BenchmarkBitmathByte(b *testing.B) { | ||
capsule := config.NewCapsule() | ||
for _, test := range bitmathTests { | ||
insp, err := newInspBitmath(context.TODO(), test.cfg) | ||
if err != nil { | ||
b.Fatal(err) | ||
} | ||
|
||
b.Run(test.name, | ||
func(b *testing.B) { | ||
capsule.SetData(test.test) | ||
benchmarkBitmathByte(b, insp, capsule) | ||
}, | ||
) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters