Skip to content
1 change: 1 addition & 0 deletions internal/fields/model.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ 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"`
Expand Down
5 changes: 5 additions & 0 deletions internal/fields/testdata/constant-keyword-invalid.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"foo": {
"constant": "wrong"
}
}
5 changes: 5 additions & 0 deletions internal/fields/testdata/constant-keyword-valid.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"foo": {
"constant": "correct"
}
}
3 changes: 3 additions & 0 deletions internal/fields/testdata/fields/fields.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,6 @@
fields:
- name: request_parameters
type: flattened
- name: constant
type: constant_keyword
value: correct
49 changes: 43 additions & 6 deletions internal/fields/validate.go
Original file line number Diff line number Diff line change
Expand Up @@ -293,19 +293,28 @@ func (v *Validator) parseElementValue(key string, definition FieldDefinition, va

var valid bool
switch definition.Type {
case "date", "ip", "constant_keyword", "keyword", "text":
case "constant_keyword":
var valStr string
valStr, valid = val.(string)
if !valid || definition.Pattern == "" {
if !valid {
break
}

valid, err := regexp.MatchString(definition.Pattern, valStr)
if err != nil {
return errors.Wrap(err, "invalid pattern")
if err := ensureConstantKeywordValueMatches(key, valStr, definition.Value); err != nil {
return err
}
if err := ensurePatternMatches(key, valStr, definition.Pattern); err != nil {
return err
}
case "date", "ip", "keyword", "text":
var valStr string
valStr, valid = val.(string)
if !valid {
return fmt.Errorf("field %q's value, %s, does not match the expected pattern: %s", key, valStr, definition.Pattern)
break
}

if err := ensurePatternMatches(key, valStr, definition.Pattern); err != nil {
return err
}
case "float", "long", "double":
_, valid = val.(float64)
Expand All @@ -331,3 +340,31 @@ func ensureSingleElementValue(val interface{}) (interface{}, bool) {
}
return nil, false // false: empty array, can't deduce single value type
}

// ensurePatternMatches validates the document's field value matches the field
// definitions regular expression pattern.
func ensurePatternMatches(key, value, pattern string) error {
if pattern == "" {
return nil
}
valid, err := regexp.MatchString(pattern, value)
if err != nil {
return errors.Wrap(err, "invalid pattern")
}
if !valid {
return fmt.Errorf("field %q's value, %s, does not match the expected pattern: %s", key, value, pattern)
}
return nil
}

// ensureConstantKeywordValueMatches validates the document's field value
// matches the definition's constant_keyword value.
func ensureConstantKeywordValueMatches(key, value, constantKeywordValue string) error {
if constantKeywordValue == "" {
return nil
}
if value != constantKeywordValue {
return fmt.Errorf("field %q's value %q does not match the declared constant_keyword value %q", key, value, constantKeywordValue)
}
return nil
}
14 changes: 14 additions & 0 deletions internal/fields/validate_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,20 @@ func TestValidate_WithNumericKeywordFields(t *testing.T) {
require.Empty(t, errs)
}

func TestValidate_constant_keyword(t *testing.T) {
validator, err := CreateValidatorForDataStream("testdata")
require.NoError(t, err)
require.NotNil(t, validator)

e := readSampleEvent(t, "testdata/constant-keyword-invalid.json")
errs := validator.ValidateDocumentBody(e)
require.NotEmpty(t, errs)

e = readSampleEvent(t, "testdata/constant-keyword-valid.json")
errs = validator.ValidateDocumentBody(e)
require.Empty(t, errs)
}

func Test_parseElementValue(t *testing.T) {
for _, test := range []struct {
key string
Expand Down