Skip to content

Commit 79c22bc

Browse files
authored
Check allowed values for fields (#771)
Fields can include a list of allowed values. Check that the values of these fields are between the allowed ones.
1 parent dc7ae2b commit 79c22bc

File tree

3 files changed

+109
-12
lines changed

3 files changed

+109
-12
lines changed

internal/fields/model.go

Lines changed: 46 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -9,22 +9,25 @@ import (
99
"strings"
1010

1111
"gopkg.in/yaml.v3"
12+
13+
"github.com/elastic/elastic-package/internal/common"
1214
)
1315

1416
// FieldDefinition describes a single field with its properties.
1517
type FieldDefinition struct {
16-
Name string `yaml:"name"`
17-
Description string `yaml:"description"`
18-
Type string `yaml:"type"`
19-
Value string `yaml:"value"` // The value to associate with a constant_keyword field.
20-
Pattern string `yaml:"pattern"`
21-
Unit string `yaml:"unit"`
22-
MetricType string `yaml:"metric_type"`
23-
External string `yaml:"external"`
24-
Index *bool `yaml:"index"`
25-
DocValues *bool `yaml:"doc_values"`
26-
Fields FieldDefinitions `yaml:"fields,omitempty"`
27-
MultiFields []FieldDefinition `yaml:"multi_fields,omitempty"`
18+
Name string `yaml:"name"`
19+
Description string `yaml:"description"`
20+
Type string `yaml:"type"`
21+
Value string `yaml:"value"` // The value to associate with a constant_keyword field.
22+
AllowedValues AllowedValues `yaml:"allowed_values"`
23+
Pattern string `yaml:"pattern"`
24+
Unit string `yaml:"unit"`
25+
MetricType string `yaml:"metric_type"`
26+
External string `yaml:"external"`
27+
Index *bool `yaml:"index"`
28+
DocValues *bool `yaml:"doc_values"`
29+
Fields FieldDefinitions `yaml:"fields,omitempty"`
30+
MultiFields []FieldDefinition `yaml:"multi_fields,omitempty"`
2831
}
2932

3033
func (orig *FieldDefinition) Update(fd FieldDefinition) {
@@ -40,6 +43,9 @@ func (orig *FieldDefinition) Update(fd FieldDefinition) {
4043
if fd.Value != "" {
4144
orig.Value = fd.Value
4245
}
46+
if len(fd.AllowedValues) > 0 {
47+
orig.AllowedValues = fd.AllowedValues
48+
}
4349
if fd.Pattern != "" {
4450
orig.Pattern = fd.Pattern
4551
}
@@ -182,3 +188,31 @@ func cleanNested(parent *FieldDefinition) (base []FieldDefinition) {
182188
parent.Fields = nested
183189
return base
184190
}
191+
192+
// AllowedValues is the list of allowed values for a field.
193+
type AllowedValues []AllowedValue
194+
195+
// Allowed returns true if a given value is allowed.
196+
func (avs AllowedValues) IsAllowed(value string) bool {
197+
if len(avs) == 0 {
198+
// No configured allowed values, any value is allowed.
199+
return true
200+
}
201+
return common.StringSliceContains(avs.Values(), value)
202+
}
203+
204+
// Values returns the list of allowed values.
205+
func (avs AllowedValues) Values() []string {
206+
var values []string
207+
for _, v := range avs {
208+
values = append(values, v.Name)
209+
}
210+
return values
211+
}
212+
213+
// AllowedValue is one of the allowed values for a field.
214+
type AllowedValue struct {
215+
Name string `yaml:"name"`
216+
Description string `yaml:"description"`
217+
ExpectedEventTypes []string `yaml:"expected_event_types"`
218+
}

internal/fields/validate.go

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -391,6 +391,9 @@ func (v *Validator) parseSingleElementValue(key string, definition FieldDefiniti
391391
if err := ensurePatternMatches(key, valStr, definition.Pattern); err != nil {
392392
return err
393393
}
394+
if err := ensureAllowedValues(key, valStr, definition.AllowedValues); err != nil {
395+
return err
396+
}
394397
// Normal text fields should be of type string.
395398
// If a pattern is provided, it checks if the value matches.
396399
case "keyword", "text":
@@ -402,6 +405,9 @@ func (v *Validator) parseSingleElementValue(key string, definition FieldDefiniti
402405
if err := ensurePatternMatches(key, valStr, definition.Pattern); err != nil {
403406
return err
404407
}
408+
if err := ensureAllowedValues(key, valStr, definition.AllowedValues); err != nil {
409+
return err
410+
}
405411
// Dates are expected to be formatted as strings or as seconds or milliseconds
406412
// since epoch.
407413
// If it is a string and a pattern is provided, it checks if the value matches.
@@ -531,3 +537,12 @@ func ensureConstantKeywordValueMatches(key, value, constantKeywordValue string)
531537
}
532538
return nil
533539
}
540+
541+
// ensureAllowedValues validates that the document's field value
542+
// is one of the allowed values.
543+
func ensureAllowedValues(key, value string, allowedValues AllowedValues) error {
544+
if !allowedValues.IsAllowed(value) {
545+
return fmt.Errorf("field %q's value %q is not one of the allowed values (%s)", key, value, strings.Join(allowedValues.Values(), ", "))
546+
}
547+
return nil
548+
}

internal/fields/validate_test.go

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -300,6 +300,54 @@ func Test_parseElementValue(t *testing.T) {
300300
},
301301
fail: true,
302302
},
303+
// allowed values
304+
{
305+
key: "allowed values",
306+
value: "configuration",
307+
definition: FieldDefinition{
308+
Type: "keyword",
309+
AllowedValues: AllowedValues{
310+
{
311+
Name: "configuration",
312+
},
313+
{
314+
Name: "network",
315+
},
316+
},
317+
},
318+
},
319+
{
320+
key: "not allowed value",
321+
value: "display",
322+
definition: FieldDefinition{
323+
Type: "keyword",
324+
AllowedValues: AllowedValues{
325+
{
326+
Name: "configuration",
327+
},
328+
{
329+
Name: "network",
330+
},
331+
},
332+
},
333+
fail: true,
334+
},
335+
{
336+
key: "not allowed value in array",
337+
value: []string{"configuration", "display"},
338+
definition: FieldDefinition{
339+
Type: "keyword",
340+
AllowedValues: AllowedValues{
341+
{
342+
Name: "configuration",
343+
},
344+
{
345+
Name: "network",
346+
},
347+
},
348+
},
349+
fail: true,
350+
},
303351
// fields shouldn't be stored in groups
304352
{
305353
key: "host",

0 commit comments

Comments
 (0)