Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix matching of leaf fields for objects #2054

Merged
merged 10 commits into from
Sep 4, 2024
3 changes: 3 additions & 0 deletions internal/fields/testdata/fields/fields.yml
Original file line number Diff line number Diff line change
Expand Up @@ -88,3 +88,6 @@
- array
- name: user.group.id
type: keyword
- name: attributes
type: object
object_type: keyword
4 changes: 4 additions & 0 deletions internal/fields/testdata/no-subobjects.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"attributes.id": "foo",
"attributes.status": "ok"
}
6 changes: 6 additions & 0 deletions internal/fields/testdata/subobjects.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"attributes": {
"id": "42",
"status": "ok"
}
}
21 changes: 19 additions & 2 deletions internal/fields/validate.go
Original file line number Diff line number Diff line change
Expand Up @@ -985,6 +985,11 @@ func validSubField(def FieldDefinition, extraPart string) bool {
fieldType := def.Type
if def.Type == "object" && def.ObjectType != "" {
fieldType = def.ObjectType

// Match leaf fields for objects that don't have a wildcard at the end.
if parts := strings.Split(extraPart, "."); len(parts) == 2 && parts[0] == "" {
return true
}
}

subFields := []string{".lat", ".lon", ".values", ".counts"}
Expand Down Expand Up @@ -1114,7 +1119,7 @@ func (v *Validator) parseSingleElementValue(key string, definition FieldDefiniti
return fmt.Errorf("the IP %q is not one of the allowed test IPs (see: https://github.com/elastic/elastic-package/blob/main/internal/fields/_static/allowed_geo_ips.txt)", valStr)
}
// Groups should only contain nested fields, not single values.
case "group", "nested":
case "group", "nested", "object":
switch val := val.(type) {
case map[string]any:
// This is probably an element from an array of objects,
Expand All @@ -1138,7 +1143,19 @@ func (v *Validator) parseSingleElementValue(key string, definition FieldDefiniti
// The document contains a null, let's consider this like an empty array.
return nil
default:
return fmt.Errorf("field %q is a group of fields, it cannot store values", key)
switch {
case definition.Type == "object" && definition.ObjectType != "":
// This is the leaf element of an object without wildcards in the name, adapt the definition and try again.
definition.Name = definition.Name + ".*"
definition.Type = definition.ObjectType
definition.ObjectType = ""
return v.parseSingleElementValue(key, definition, val, doc)
case definition.Type == "object" && definition.ObjectType == "":
// Legacy mapping, abiguous definition not allowed by recent versions of the spec, ignore it.
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
// Legacy mapping, abiguous definition not allowed by recent versions of the spec, ignore it.
// Legacy mapping, ambiguous definition not allowed by recent versions of the spec, ignore it.

return nil
}

return fmt.Errorf("field %q is a group of fields of type %s, it cannot store values", key, definition.Type)
}
// Numbers should have been parsed as float64, otherwise they are not numbers.
case "float", "long", "double":
Expand Down
25 changes: 25 additions & 0 deletions internal/fields/validate_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,25 @@ func TestValidate_WithFlattenedFields(t *testing.T) {
require.Empty(t, errs)
}

func TestValidate_ObjectTypeWithoutWildcard(t *testing.T) {
validator, err := CreateValidatorForDirectory("testdata",
WithDisabledDependencyManagement())
require.NoError(t, err)
require.NotNil(t, validator)

t.Run("subobjects", func(t *testing.T) {
e := readSampleEvent(t, "testdata/subobjects.json")
errs := validator.ValidateDocumentBody(e)
require.Empty(t, errs)
})

t.Run("no-subobjects", func(t *testing.T) {
e := readSampleEvent(t, "testdata/no-subobjects.json")
errs := validator.ValidateDocumentBody(e)
require.Empty(t, errs)
})
}

func TestValidate_WithNumericKeywordFields(t *testing.T) {
validator, err := CreateValidatorForDirectory("testdata",
WithNumericKeywordFields([]string{
Expand Down Expand Up @@ -824,6 +843,12 @@ func TestCompareKeys(t *testing.T) {
searchedKey: "example.foo",
expected: true,
},
{
key: "example",
def: FieldDefinition{Type: "object", ObjectType: "keyword"},
searchedKey: "example.foo",
expected: true,
},
{
key: "example.foo",
searchedKey: "example.*",
Expand Down