Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 46 additions & 12 deletions internal/fields/model.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,22 +9,25 @@ import (
"strings"

"gopkg.in/yaml.v3"

"github.com/elastic/elastic-package/internal/common"
)

// FieldDefinition describes a single field with its properties.
type FieldDefinition struct {
Name string `yaml:"name"`
Description string `yaml:"description"`
Type string `yaml:"type"`
Value string `yaml:"value"` // The value to associate with a constant_keyword field.
Pattern string `yaml:"pattern"`
Unit string `yaml:"unit"`
MetricType string `yaml:"metric_type"`
External string `yaml:"external"`
Index *bool `yaml:"index"`
DocValues *bool `yaml:"doc_values"`
Fields FieldDefinitions `yaml:"fields,omitempty"`
MultiFields []FieldDefinition `yaml:"multi_fields,omitempty"`
Name string `yaml:"name"`
Description string `yaml:"description"`
Type string `yaml:"type"`
Value string `yaml:"value"` // The value to associate with a constant_keyword field.
AllowedValues AllowedValues `yaml:"allowed_values"`
Pattern string `yaml:"pattern"`
Unit string `yaml:"unit"`
MetricType string `yaml:"metric_type"`
External string `yaml:"external"`
Index *bool `yaml:"index"`
DocValues *bool `yaml:"doc_values"`
Fields FieldDefinitions `yaml:"fields,omitempty"`
MultiFields []FieldDefinition `yaml:"multi_fields,omitempty"`
}

func (orig *FieldDefinition) Update(fd FieldDefinition) {
Expand All @@ -40,6 +43,9 @@ func (orig *FieldDefinition) Update(fd FieldDefinition) {
if fd.Value != "" {
orig.Value = fd.Value
}
if len(fd.AllowedValues) > 0 {
orig.AllowedValues = fd.AllowedValues
}
if fd.Pattern != "" {
orig.Pattern = fd.Pattern
}
Expand Down Expand Up @@ -182,3 +188,31 @@ func cleanNested(parent *FieldDefinition) (base []FieldDefinition) {
parent.Fields = nested
return base
}

// AllowedValues is the list of allowed values for a field.
type AllowedValues []AllowedValue

// Allowed returns true if a given value is allowed.
func (avs AllowedValues) IsAllowed(value string) bool {
if len(avs) == 0 {
// No configured allowed values, any value is allowed.
return true
}
return common.StringSliceContains(avs.Values(), value)
}

// Values returns the list of allowed values.
func (avs AllowedValues) Values() []string {
var values []string
for _, v := range avs {
values = append(values, v.Name)
}
return values
}

// AllowedValue is one of the allowed values for a field.
type AllowedValue struct {
Name string `yaml:"name"`
Description string `yaml:"description"`
ExpectedEventTypes []string `yaml:"expected_event_types"`
}
15 changes: 15 additions & 0 deletions internal/fields/validate.go
Original file line number Diff line number Diff line change
Expand Up @@ -391,6 +391,9 @@ func (v *Validator) parseSingleElementValue(key string, definition FieldDefiniti
if err := ensurePatternMatches(key, valStr, definition.Pattern); err != nil {
return err
}
if err := ensureAllowedValues(key, valStr, definition.AllowedValues); err != nil {
return err
}
// Normal text fields should be of type string.
// If a pattern is provided, it checks if the value matches.
case "keyword", "text":
Expand All @@ -402,6 +405,9 @@ func (v *Validator) parseSingleElementValue(key string, definition FieldDefiniti
if err := ensurePatternMatches(key, valStr, definition.Pattern); err != nil {
return err
}
if err := ensureAllowedValues(key, valStr, definition.AllowedValues); err != nil {
return err
}
// Dates are expected to be formatted as strings or as seconds or milliseconds
// since epoch.
// If it is a string and a pattern is provided, it checks if the value matches.
Expand Down Expand Up @@ -531,3 +537,12 @@ func ensureConstantKeywordValueMatches(key, value, constantKeywordValue string)
}
return nil
}

// ensureAllowedValues validates that the document's field value
// is one of the allowed values.
func ensureAllowedValues(key, value string, allowedValues AllowedValues) error {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: in theory, you don't need to pass key here as it's just used to render the error message :)

Copy link
Member Author

@jsoriano jsoriano Apr 6, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Umm, but error message is rendered here, key is used in this function for that.

I would be fine with refactoring this error generation, but in a different PR, making the other ensure methods consistent with this.

if !allowedValues.IsAllowed(value) {
return fmt.Errorf("field %q's value %q is not one of the allowed values (%s)", key, value, strings.Join(allowedValues.Values(), ", "))
}
return nil
}
48 changes: 48 additions & 0 deletions internal/fields/validate_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -300,6 +300,54 @@ func Test_parseElementValue(t *testing.T) {
},
fail: true,
},
// allowed values
{
key: "allowed values",
value: "configuration",
definition: FieldDefinition{
Type: "keyword",
AllowedValues: AllowedValues{
{
Name: "configuration",
},
{
Name: "network",
},
},
},
},
{
key: "not allowed value",
value: "display",
definition: FieldDefinition{
Type: "keyword",
AllowedValues: AllowedValues{
{
Name: "configuration",
},
{
Name: "network",
},
},
},
fail: true,
},
{
key: "not allowed value in array",
value: []string{"configuration", "display"},
definition: FieldDefinition{
Type: "keyword",
AllowedValues: AllowedValues{
{
Name: "configuration",
},
{
Name: "network",
},
},
},
fail: true,
},
// fields shouldn't be stored in groups
{
key: "host",
Expand Down