Skip to content

Commit

Permalink
Add byte length comparison in do_if (#617)
Browse files Browse the repository at this point in the history
* Add bytes length comparison in do_if

* Fix misspell

* Add doc

* Rename funcs

* Add tests for 'Check' method

* Add tree building tests

* Fix tree building tests. Add tests for method 'IsEqualTo'

* Add tests for parsing

* Refactor. Edit error msg

* Make empty field selector valid

---------

Co-authored-by: george pogosyan <[email protected]>
  • Loading branch information
goshansmails and george pogosyan authored May 3, 2024
1 parent 4408aa0 commit e09ed91
Show file tree
Hide file tree
Showing 7 changed files with 533 additions and 27 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,5 @@ ozon*
dist/
tests-offsets
testdata

.idea/
51 changes: 51 additions & 0 deletions fd/util.go
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,9 @@ var (
"suffix": struct{}{},
"regex": struct{}{},
}
doIfBytesLengthCmpNodes = map[string]struct{}{
"byte_len_cmp": struct{}{},
}
)

func extractFieldOpVals(jsonNode *simplejson.Json) [][]byte {
Expand Down Expand Up @@ -248,6 +251,52 @@ func extractFieldOpNode(opName string, jsonNode *simplejson.Json) (pipeline.DoIf
return result, nil
}

func noRequiredFieldError(field string) error {
return fmt.Errorf("no required field: %s", field)
}

const (
fieldNameField = "field"
fieldNameCmpOp = "cmp_op"
fieldNameCmpValue = "value"
)

func extractByteLengthCmpOpNode(_ string, jsonNode *simplejson.Json) (pipeline.DoIfNode, error) {
fieldPathNode, has := jsonNode.CheckGet(fieldNameField)
if !has {
return nil, noRequiredFieldError(fieldNameField)
}
fieldPath, err := fieldPathNode.String()
if err != nil {
return nil, err
}

cmpOpNode, has := jsonNode.CheckGet(fieldNameCmpOp)
if !has {
return nil, noRequiredFieldError(fieldNameCmpOp)
}
cmpOp, err := cmpOpNode.String()
if err != nil {
return nil, err
}

cmpValueNode, has := jsonNode.CheckGet(fieldNameCmpValue)
if !has {
return nil, noRequiredFieldError(fieldNameCmpValue)
}
cmpValue, err := cmpValueNode.Int()
if err != nil {
return nil, err
}

result, err := pipeline.NewByteLengthCmpNode(fieldPath, cmpOp, cmpValue)
if err != nil {
return nil, fmt.Errorf("failed to init bytes length cmp op: %w", err)
}

return result, nil
}

func extractLogicalOpNode(opName string, jsonNode *simplejson.Json) (pipeline.DoIfNode, error) {
var result, operand pipeline.DoIfNode
var err error
Expand Down Expand Up @@ -278,6 +327,8 @@ func extractDoIfNode(jsonNode *simplejson.Json) (pipeline.DoIfNode, error) {
return extractLogicalOpNode(opName, jsonNode)
} else if _, has := doIfFieldOpNodes[opName]; has {
return extractFieldOpNode(opName, jsonNode)
} else if _, has := doIfBytesLengthCmpNodes[opName]; has {
return extractByteLengthCmpOpNode(opName, jsonNode)
}
return nil, fmt.Errorf("unknown op %q", opName)
}
Expand Down
87 changes: 84 additions & 3 deletions fd/util_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,18 +46,22 @@ type doIfTreeNode struct {

logicalOp string
operands []*doIfTreeNode

byteLenCmpOp string
cmpValue int
}

// nolint:gocritic
func buildDoIfTree(node *doIfTreeNode) (pipeline.DoIfNode, error) {
if node.fieldOp != "" {
switch {
case node.fieldOp != "":
return pipeline.NewFieldOpNode(
node.fieldOp,
node.fieldName,
node.caseSensitive,
node.values,
)
} else if node.logicalOp != "" {
case node.logicalOp != "":
operands := make([]pipeline.DoIfNode, 0)
for _, operandNode := range node.operands {
operand, err := buildDoIfTree(operandNode)
Expand All @@ -70,8 +74,11 @@ func buildDoIfTree(node *doIfTreeNode) (pipeline.DoIfNode, error) {
node.logicalOp,
operands,
)
case node.byteLenCmpOp != "":
return pipeline.NewByteLengthCmpNode(node.fieldName, node.byteLenCmpOp, node.cmpValue)
default:
return nil, errors.New("unknown type of node")
}
return nil, errors.New("unknown type of node")
}

func Test_extractDoIfChecker(t *testing.T) {
Expand Down Expand Up @@ -107,6 +114,12 @@ func Test_extractDoIfChecker(t *testing.T) {
"values": ["test-1", "test-2"],
"case_sensitive": false
},
{
"op": "byte_len_cmp",
"field": "msg",
"cmp_op": "gt",
"value": 100
},
{
"op": "or",
"operands": [
Expand Down Expand Up @@ -152,6 +165,11 @@ func Test_extractDoIfChecker(t *testing.T) {
values: [][]byte{[]byte("test-1"), []byte("test-2")},
caseSensitive: false,
},
{
byteLenCmpOp: "gt",
fieldName: "msg",
cmpValue: 100,
},
{
logicalOp: "or",
operands: []*doIfTreeNode{
Expand Down Expand Up @@ -187,6 +205,17 @@ func Test_extractDoIfChecker(t *testing.T) {
},
wantErr: false,
},
{
name: "ok_byte_len_cmp_op",
args: args{
cfgStr: `{"op":"byte_len_cmp","field":"data","cmp_op":"lt","value":10}`,
},
want: &doIfTreeNode{
byteLenCmpOp: "lt",
fieldName: "data",
cmpValue: 10,
},
},
{
name: "ok_single_val",
args: args{
Expand Down Expand Up @@ -259,6 +288,58 @@ func Test_extractDoIfChecker(t *testing.T) {
},
wantErr: true,
},
{
name: "error_byte_len_cmp_op_no_field",
args: args{
cfgStr: `{"op":"byte_len_cmp","cmp_op":"lt","value":10}`,
},
wantErr: true,
},
{
name: "error_byte_len_cmp_op_field_is_not_string",
args: args{
cfgStr: `{"op":"byte_len_cmp","field":123,"cmp_op":"lt","value":10}`,
},
wantErr: true,
},
{
name: "error_byte_len_cmp_op_no_cmp_op",
args: args{
cfgStr: `{"op":"byte_len_cmp","field":"data","value":10}`,
},
wantErr: true,
},
{
name: "error_byte_len_cmp_op_cmp_op_is_not_string",
args: args{
cfgStr: `{"op":"byte_len_cmp","field":"data","cmp_op":123,"value":10}`,
},
wantErr: true,
},
{
name: "error_byte_len_cmp_op_no_cmp_value",
args: args{
cfgStr: `{"op":"byte_len_cmp","field":"data","cmp_op":"lt"}`,
},
wantErr: true,
},
{
name: "error_byte_len_cmp_op_cmp_value_is_not_integer",
args: args{
cfgStr: `{"op":"byte_len_cmp","field":"data","cmp_op":"lt","value":"abc"}`,
},
wantErr: true,
},
{
name: "error_byte_len_cmp_op_invalid_cmp_op",
args: args{cfgStr: `{"op":"byte_len_cmp","field":"data","cmp_op":"ABC","value":10}`},
wantErr: true,
},
{
name: "error_byte_len_cmp_op_negative_cmp_value",
args: args{cfgStr: `{"op":"byte_len_cmp","field":"data","cmp_op":"lt","value":-1}`},
wantErr: true,
},
}
for _, tt := range tests {
tt := tt
Expand Down
3 changes: 3 additions & 0 deletions pipeline/README.idoc.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,6 @@ the chain of Match func calls are performed across the whole tree.

### Logical operations
@do-if-logical-op|description

### Byte length comparison op node
@do-if-byte-len-cmp-op-node
47 changes: 47 additions & 0 deletions pipeline/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,10 @@ the chain of Match func calls are performed across the whole tree.

<br>

**`ByteLenCmpOp`** Type of node where matching rules for byte lengths of fields are stored.

<br>

**`LogicalOp`** Type of node where logical rules for applying other rules are stored.

<br>
Expand Down Expand Up @@ -388,4 +392,47 @@ result:
<br>


### Byte length comparison op node
DoIf byte length comparison op node is considered to always be a leaf in the DoIf tree like DoIf field op node.
It contains operation that compares field length in bytes with certain value.

Params:
- `op` - must be `byte_len_cmp`. Required.
- `field` - name of the field to apply operation. Required.
- `cmp_op` - comparison operation name (see below). Required.
- `value` - integer value to compare length with. Required non-negative.

Example:
```yaml
pipelines:
test:
actions:
- type: discard
do_if:
op: byte_len_cmp
field: pod_id
cmp_op: lt
value: 5
```

result:
```
{"pod_id":""} # discarded
{"pod_id":123} # discarded
{"pod_id":12345} # not discarded
{"pod_id":123456} # not discarded
```

Possible values of field 'cmp_op': `lt`, `le`, `gt`, `ge`, `eq`, `ne`.
They denote corresponding comparison operations.

| Name | Op |
|------|----|
| `lt` | `<` |
| `le` | `<=` |
| `gt` | `>` |
| `ge` | `>=` |
| `eq` | `==` |
| `ne` | `!=` |

<br>*Generated using [__insane-doc__](https://github.com/vitkovskii/insane-doc)*
Loading

0 comments on commit e09ed91

Please sign in to comment.