Skip to content

Commit 4b4fcde

Browse files
authored
Update Remove operator to support removing the entire body, resource or attributes (#112)
1 parent 83c0ef5 commit 4b4fcde

File tree

12 files changed

+312
-32
lines changed

12 files changed

+312
-32
lines changed

docs/operators/remove.md

Lines changed: 87 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ The `remove` operator removes a field from a record.
88
| --- | --- | --- |
99
| `id` | `remove` | A unique identifier for the operator |
1010
| `output` | Next in pipeline | The connected operator(s) that will receive all outbound entries |
11-
| `field` | required | The [field](/docs/types/field.md) to remove.
11+
| `field` | required | The [field](/docs/types/field.md) to remove. if '$attributes' or '$resource' is specified, all fields of that type will be removed.
1212
| `on_error` | `send` | The behavior of the operator if it encounters an error. See [on_error](/docs/types/on_error.md) |
1313
| `if` | | An [expression](/docs/types/expression.md) that, when set, will be evaluated to determine whether this operator should be used for the given entry. This allows you to do easy conditional parsing without branching logic with routers. |
1414

@@ -175,6 +175,92 @@ Remove a value from resource
175175
}
176176
```
177177

178+
</td>
179+
</tr>
180+
</table>
181+
182+
<hr>
183+
184+
Remove all resource fields
185+
```yaml
186+
- type: remove
187+
field: $resource
188+
```
189+
190+
<table>
191+
<tr><td> Input Entry </td> <td> Output Entry </td></tr>
192+
<tr>
193+
<td>
194+
195+
```json
196+
{
197+
"resource": {
198+
"key1.0": "val",
199+
"key2.0": "val"
200+
},
201+
"attributes": { },
202+
"body": {
203+
"key": "val"
204+
},
205+
}
206+
```
207+
208+
</td>
209+
<td>
210+
211+
```json
212+
{
213+
"resource": { },
214+
"attributes": { },
215+
"body": {
216+
"key": "val"
217+
}
218+
}
219+
```
220+
221+
</td>
222+
</tr>
223+
</table>
224+
225+
<hr>
226+
227+
Remove all attributes
228+
```yaml
229+
- type: remove
230+
field: $attributes
231+
```
232+
233+
<table>
234+
<tr><td> Input Entry </td> <td> Output Entry </td></tr>
235+
<tr>
236+
<td>
237+
238+
```json
239+
{
240+
"resource": { },
241+
"attributes": {
242+
"key1.0": "val",
243+
"key2.0": "val"
244+
},
245+
"body": {
246+
"key": "val"
247+
},
248+
}
249+
```
250+
251+
</td>
252+
<td>
253+
254+
```json
255+
{
256+
"resource": { },
257+
"attributes": { },
258+
"body": {
259+
"key": "val"
260+
}
261+
}
262+
```
263+
178264
</td>
179265
</tr>
180266
</table>

entry/body_field.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -211,7 +211,7 @@ func (f BodyField) MarshalYAML() (interface{}, error) {
211211
func fromJSONDot(value string) BodyField {
212212
keys := strings.Split(value, ".")
213213

214-
if keys[0] == "$" || keys[0] == bodyPrefix {
214+
if keys[0] == "$" || keys[0] == BodyPrefix {
215215
keys = keys[1:]
216216
}
217217

@@ -221,7 +221,7 @@ func fromJSONDot(value string) BodyField {
221221
// toJSONDot returns the JSON dot notation for a field.
222222
func toJSONDot(field BodyField) string {
223223
if field.isRoot() {
224-
return bodyPrefix
224+
return BodyPrefix
225225
}
226226

227227
containsDots := false
@@ -233,7 +233,7 @@ func toJSONDot(field BodyField) string {
233233

234234
var b strings.Builder
235235
if containsDots {
236-
b.WriteString(bodyPrefix)
236+
b.WriteString(BodyPrefix)
237237
for _, key := range field.Keys {
238238
b.WriteString(`['`)
239239
b.WriteString(key)

entry/entry_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -234,7 +234,7 @@ func TestFieldFromString(t *testing.T) {
234234

235235
for _, tc := range cases {
236236
t.Run(tc.name, func(t *testing.T) {
237-
f, err := fieldFromString(tc.input)
237+
f, err := NewField(tc.input)
238238
if tc.expectedError {
239239
require.Error(t, err)
240240
return

entry/field.go

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,9 @@ import (
2020
)
2121

2222
const (
23-
attributesPrefix = "$attributes"
24-
resourcePrefix = "$resource"
25-
bodyPrefix = "$body"
23+
AttributesPrefix = "$attributes"
24+
ResourcePrefix = "$resource"
25+
BodyPrefix = "$body"
2626
)
2727

2828
// Field represents a potential field on an entry.
@@ -47,7 +47,7 @@ func (f *Field) UnmarshalJSON(raw []byte) error {
4747
if err != nil {
4848
return err
4949
}
50-
*f, err = fieldFromString(s)
50+
*f, err = NewField(s)
5151
return err
5252
}
5353

@@ -58,28 +58,28 @@ func (f *Field) UnmarshalYAML(unmarshal func(interface{}) error) error {
5858
if err != nil {
5959
return err
6060
}
61-
*f, err = fieldFromString(s)
61+
*f, err = NewField(s)
6262
return err
6363
}
6464

65-
func fieldFromString(s string) (Field, error) {
65+
func NewField(s string) (Field, error) {
6666
split, err := splitField(s)
6767
if err != nil {
6868
return Field{}, fmt.Errorf("splitting field: %s", err)
6969
}
7070

7171
switch split[0] {
72-
case attributesPrefix:
72+
case AttributesPrefix:
7373
if len(split) != 2 {
7474
return Field{}, fmt.Errorf("attributes cannot be nested")
7575
}
7676
return Field{AttributeField{split[1]}}, nil
77-
case resourcePrefix:
77+
case ResourcePrefix:
7878
if len(split) != 2 {
7979
return Field{}, fmt.Errorf("resource fields cannot be nested")
8080
}
8181
return Field{ResourceField{split[1]}}, nil
82-
case bodyPrefix, "$":
82+
case BodyPrefix, "$":
8383
return Field{BodyField{split[1:]}}, nil
8484
default:
8585
return Field{BodyField{split}}, nil

entry/field_test.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -257,19 +257,19 @@ func TestSplitField(t *testing.T) {
257257
}
258258

259259
func TestFieldFromStringInvalidSplit(t *testing.T) {
260-
_, err := fieldFromString("$resource[test]")
260+
_, err := NewField("$resource[test]")
261261
require.Error(t, err)
262262
require.Contains(t, err.Error(), "splitting field")
263263
}
264264

265265
func TestFieldFromStringWithResource(t *testing.T) {
266-
field, err := fieldFromString(`$resource["test"]`)
266+
field, err := NewField(`$resource["test"]`)
267267
require.NoError(t, err)
268268
require.Equal(t, "$resource.test", field.String())
269269
}
270270

271271
func TestFieldFromStringWithInvalidResource(t *testing.T) {
272-
_, err := fieldFromString(`$resource["test"]["key"]`)
272+
_, err := NewField(`$resource["test"]["key"]`)
273273
require.Error(t, err)
274274
require.Contains(t, err.Error(), "resource fields cannot be nested")
275275
}

operator/builtin/transformer/remove/config_test.go

Lines changed: 42 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,23 +27,47 @@ func TestGoldenConfig(t *testing.T) {
2727
Name: "remove_body",
2828
Expect: func() *RemoveOperatorConfig {
2929
cfg := defaultCfg()
30-
cfg.Field = entry.NewBodyField("nested")
30+
cfg.Field = newBodyField("nested")
3131
return cfg
3232
}(),
3333
},
3434
{
3535
Name: "remove_single_attribute",
3636
Expect: func() *RemoveOperatorConfig {
3737
cfg := defaultCfg()
38-
cfg.Field = entry.NewAttributeField("key")
38+
cfg.Field = newAttributeField("key")
3939
return cfg
4040
}(),
4141
},
4242
{
4343
Name: "remove_single_resource",
4444
Expect: func() *RemoveOperatorConfig {
4545
cfg := defaultCfg()
46-
cfg.Field = entry.NewResourceField("key")
46+
cfg.Field = newResourceField("key")
47+
return cfg
48+
}(),
49+
},
50+
{
51+
Name: "remove_entire_resource",
52+
Expect: func() *RemoveOperatorConfig {
53+
cfg := defaultCfg()
54+
cfg.Field.allResource = true
55+
return cfg
56+
}(),
57+
},
58+
{
59+
Name: "remove_entire_body",
60+
Expect: func() *RemoveOperatorConfig {
61+
cfg := defaultCfg()
62+
cfg.Field.Field = entry.NewBodyField()
63+
return cfg
64+
}(),
65+
},
66+
{
67+
Name: "remove_entire_attributes",
68+
Expect: func() *RemoveOperatorConfig {
69+
cfg := defaultCfg()
70+
cfg.Field.allAttributes = true
4771
return cfg
4872
}(),
4973
},
@@ -58,3 +82,18 @@ func TestGoldenConfig(t *testing.T) {
5882
func defaultCfg() *RemoveOperatorConfig {
5983
return NewRemoveOperatorConfig("move")
6084
}
85+
86+
func newBodyField(keys ...string) rootableField {
87+
field := entry.NewBodyField(keys...)
88+
return rootableField{Field: field}
89+
}
90+
91+
func newResourceField(key string) rootableField {
92+
field := entry.NewResourceField(key)
93+
return rootableField{Field: field}
94+
}
95+
96+
func newAttributeField(key string) rootableField {
97+
field := entry.NewAttributeField(key)
98+
return rootableField{Field: field}
99+
}

operator/builtin/transformer/remove/remove.go

Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -27,18 +27,18 @@ func init() {
2727
operator.Register("remove", func() operator.Builder { return NewRemoveOperatorConfig("") })
2828
}
2929

30-
// NewRemoveOperatorConfig creates a new restructure operator config with default values
30+
// NewRemoveOperatorConfig creates a new remove operator config with default values
3131
func NewRemoveOperatorConfig(operatorID string) *RemoveOperatorConfig {
3232
return &RemoveOperatorConfig{
3333
TransformerConfig: helper.NewTransformerConfig(operatorID, "remove"),
3434
}
3535
}
3636

37-
// RemoveOperatorConfig is the configuration of a restructure operator
37+
// RemoveOperatorConfig is the configuration of a remove operator
3838
type RemoveOperatorConfig struct {
3939
helper.TransformerConfig `mapstructure:",squash" yaml:",inline"`
4040

41-
Field entry.Field `mapstructure:"field" json:"field" yaml:"field"`
41+
Field rootableField `mapstructure:"field" json:"field" yaml:"field"`
4242
}
4343

4444
// Build will build a Remove operator from the supplied configuration
@@ -47,7 +47,8 @@ func (c RemoveOperatorConfig) Build(context operator.BuildContext) ([]operator.O
4747
if err != nil {
4848
return nil, err
4949
}
50-
if c.Field == entry.NewNilField() {
50+
51+
if c.Field.Field == entry.NewNilField() {
5152
return nil, fmt.Errorf("remove: field is empty")
5253
}
5354

@@ -62,17 +63,27 @@ func (c RemoveOperatorConfig) Build(context operator.BuildContext) ([]operator.O
6263
// RemoveOperator is an operator that deletes a field
6364
type RemoveOperator struct {
6465
helper.TransformerOperator
65-
Field entry.Field
66+
Field rootableField
6667
}
6768

68-
// Process will process an entry with a restructure transformation.
69+
// Process will process an entry with a remove transformation.
6970
func (p *RemoveOperator) Process(ctx context.Context, entry *entry.Entry) error {
7071
return p.ProcessWith(ctx, entry, p.Transform)
7172
}
7273

7374
// Transform will apply the restructure operations to an entry
7475
func (p *RemoveOperator) Transform(entry *entry.Entry) error {
75-
_, exist := entry.Delete(p.Field)
76+
if p.Field.allAttributes {
77+
entry.Attributes = nil
78+
return nil
79+
}
80+
81+
if p.Field.allResource {
82+
entry.Resource = nil
83+
return nil
84+
}
85+
86+
_, exist := entry.Delete(p.Field.Field)
7687
if !exist {
7788
return fmt.Errorf("remove: field does not exist")
7889
}

0 commit comments

Comments
 (0)