diff --git a/libbeat/common/mapval/compiled_schema.go b/libbeat/common/mapval/compiled_schema.go index 603d734c3be8..054565550d68 100644 --- a/libbeat/common/mapval/compiled_schema.go +++ b/libbeat/common/mapval/compiled_schema.go @@ -20,7 +20,7 @@ package mapval import "github.com/elastic/beats/libbeat/common" type flatValidator struct { - path Path + path path isDef IsDef } @@ -31,7 +31,7 @@ type CompiledSchema []flatValidator func (cs CompiledSchema) Check(actual common.MapStr) *Results { results := NewResults() for _, pv := range cs { - actualV, actualKeyExists := pv.path.GetFrom(actual) + actualV, actualKeyExists := pv.path.getFrom(actual) if !pv.isDef.optional || pv.isDef.optional && actualKeyExists { var checkRes *Results diff --git a/libbeat/common/mapval/core.go b/libbeat/common/mapval/core.go index 46ea7083d86b..7359d52d1e73 100644 --- a/libbeat/common/mapval/core.go +++ b/libbeat/common/mapval/core.go @@ -37,10 +37,12 @@ func Optional(id IsDef) IsDef { return id } -// Map is the type used to define schema definitions for Compile. +// Map is the type used to define schema definitions for Compile and to represent an arbitrary +// map of values of any type. type Map map[string]interface{} -// Slice is a convenience []interface{} used to declare schema defs. +// Slice is a convenience []interface{} used to declare schema defs. You would typically nest this inside +// a Map as a value, and it would be able to match against any type of non-empty slice. type Slice []interface{} // Validator is the result of Compile and is run against the map you'd like to test. @@ -56,7 +58,7 @@ func Compose(validators ...Validator) Validator { combined := NewResults() for _, r := range results { - r.EachResult(func(path Path, vr ValueResult) bool { + r.EachResult(func(path path, vr ValueResult) bool { combined.record(path, vr) return true }) diff --git a/libbeat/common/mapval/doc.go b/libbeat/common/mapval/doc.go new file mode 100644 index 000000000000..5fdf652f16ec --- /dev/null +++ b/libbeat/common/mapval/doc.go @@ -0,0 +1,23 @@ +// Licensed to Elasticsearch B.V. under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. Elasticsearch B.V. licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +/* +Package mapval is used to validate JSON-like nested map data-structure against a set of expectations. Its key features are allowing custom, function defined validators for values, and allowing the composition of multiple validation specs. + +See the example below for more details. Most key functions include detailed examples of their use within this documentation. +*/ +package mapval diff --git a/libbeat/common/mapval/doc_test.go b/libbeat/common/mapval/doc_test.go new file mode 100644 index 000000000000..7b2e655b62b1 --- /dev/null +++ b/libbeat/common/mapval/doc_test.go @@ -0,0 +1,206 @@ +// Licensed to Elasticsearch B.V. under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. Elasticsearch B.V. licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package mapval + +import ( + "fmt" + "strings" + + "github.com/elastic/beats/libbeat/common" +) + +func Example() { + // Let's say we want to validate this map + data := common.MapStr{"foo": "bar", "baz": "bot", "count": 1} + + // We can validate the data by creating a mapval.Validator + // Validators are functions created by compiling the special mapval.Map + // type. This is a map[string]interface{} that can be compiled + // into a series of checks. + // + // Literal values in a mapval.Map are checked for equality. + // More complex checks can be done using values of the mapval.IsDef + // type. In this case, we're using an IsDef to see if the "foo" key + // contains the string "a", and we're using a literal to check that the + // "baz" key contains the exact value "bot". + validator := MustCompile(Map{ + "foo": IsStringContaining("a"), + "baz": "bot", + }) + + // When being used in test-suites, you should use mapvaltest.Test to execute the validator + // This produces easy to read test output, and outputs one failed assertion per failed matcher + // See the docs for mapvaltest for more info + // mapvaltest.Test(t, validator, data) + + // If you need more control than mapvaltest.Test provides, you can use the results directly + results := validator(data) + + // The Results.Valid property indicates if the validator passed + fmt.Printf("Results.Valid: %t\n", results.Valid) + + // Results.Errors() returns one error per failed match + fmt.Printf("There were %d errors\n", len(results.Errors())) + + // Results.Fields is a map of paths defined in the input mapval.Map to the result of their validation + // This is useful if you need more control + fmt.Printf("Over %d fields\n", len(results.Fields)) + + // You may be thinking that the validation above should have failed since there was an + // extra key, 'count', defined that was encountered. By default mapval does not + // consider extra data to be an error. To change that behavior, wrap the validator + // in mapval.Strict() + strictResults := Strict(validator)(data) + + fmt.Printf("Strict Results.Valid: %t\n", strictResults.Valid) + + // You can check an exact field for an error + fmt.Printf("For the count field specifically .Valid is: %t\n", strictResults.Fields["count"][0].Valid) + + // And get error objects for each error + for _, err := range strictResults.Errors() { + fmt.Println(err) + } + + // And even get a new Results object with only invalid fields included + strictResults.DetailedErrors() +} + +func ExampleCompose() { + // Composition is useful when you need to share common validation logic between validators. + // Let's imagine that we want to validate maps describing pets. + + pets := []common.MapStr{ + {"name": "rover", "barks": "often", "fur_length": "long"}, + {"name": "lucky", "barks": "rarely", "fur_length": "short"}, + {"name": "pounce", "meows": "often", "fur_length": "short"}, + {"name": "peanut", "meows": "rarely", "fur_length": "long"}, + } + + // We can see that all pets have the "fur_length" property, but that only cats meow, and dogs bark. + // We can concisely encode this in mapval using mapval.Compose. + // We can also see that both "meows" and "barks" contain the same enums of values. + // We'll start by creating a composed IsDef using the IsAny composition, which creates a new IsDef that is + // a logical 'or' of its IsDef arguments + + isFrequency := IsAny(IsEqual("often"), IsEqual("rarely")) + + petValidator := MustCompile(Map{ + "name": IsNonEmptyString, + "fur_length": IsAny(IsEqual("long"), IsEqual("short")), + }) + dogValidator := Compose( + petValidator, + MustCompile(Map{"barks": isFrequency}), + ) + catValidator := Compose( + petValidator, + MustCompile(Map{"meows": isFrequency}), + ) + + for _, pet := range pets { + var petType string + if dogValidator(pet).Valid { + petType = "dog" + } else if catValidator(pet).Valid { + petType = "cat" + } + fmt.Printf("%s is a %s\n", pet["name"], petType) + } + + // Output: + // rover is a dog + // lucky is a dog + // pounce is a cat + // peanut is a cat +} + +func ExampleOptional() { + dataNoError := common.MapStr{"foo": "bar"} + dataError := common.MapStr{"foo": "bar", "error": true} + + validator := MustCompile(Map{"foo": "bar", "error": Optional(IsEqual(true))}) + + // Both inputs pass + fmt.Printf("Validator classifies both maps as true: %t", validator(dataNoError).Valid && validator(dataError).Valid) + + // Output: + // Validator classifies both maps as true: true +} + +func ExampleIs() { + // More advanced validations can be used with built-in and custom functions. + // These are represented with the IfDef type + + data := common.MapStr{"foo": "bar", "count": 1} + + // Values can also be tested programatically if a mapval.IsDef is used as a value + // Here we'll define a custom IsDef using the mapval DSL, then validate it. + // The Is() function is the preferred way to costruct IsDef objects. + startsWithB := Is("starts with b", func(path path, v interface{}) *Results { + vStr, ok := v.(string) + if !ok { + return SimpleResult(path, false, "Expected a string, got a %t", v) + } + + if strings.HasPrefix(vStr, "b") { + return ValidResult(path) + } + + return SimpleResult(path, false, "Expected string to start with b, got %v", vStr) + }) + + funcValidator := MustCompile(Map{"foo": startsWithB}) + + funcValidatorResult := funcValidator(data) + + fmt.Printf("Valid: %t", funcValidatorResult.Valid) + + // Output: + // Valid: true +} + +func ExampleMap() { + v := MustCompile(Map{ + "foo": IsStringContaining("a"), + "baz": "bot", + }) + + data := common.MapStr{ + "foo": "bar", + "baz": "bot", + } + + fmt.Printf("Result is %t", v(data).Valid) + + // Output: + // Result is true +} + +func ExampleSlice() { + v := MustCompile(Map{ + "foo": Slice{"foo", IsNonEmptyString}, + }) + + data := common.MapStr{"foo": []string{"foo", "something"}} + + fmt.Printf("Result is %t", v(data).Valid) + + // Output: + // Result is true +} diff --git a/libbeat/common/mapval/is_defs.go b/libbeat/common/mapval/is_defs.go index 7421884b5dcd..75cd7787da3c 100644 --- a/libbeat/common/mapval/is_defs.go +++ b/libbeat/common/mapval/is_defs.go @@ -95,14 +95,14 @@ func IsEqual(to interface{}) IsDef { // We know this is an isdef due to the Register check previously checker := isDefFactory.Call([]reflect.Value{toV})[0].Interface().(IsDef).checker - return Is("equals", func(path Path, v interface{}) *Results { + return Is("equals", func(path path, v interface{}) *Results { return checker(path, v) }) } // IsEqualToTime ensures that the actual value is the given time, regardless of zone. func IsEqualToTime(to time.Time) IsDef { - return Is("equal to time", func(path Path, v interface{}) *Results { + return Is("equal to time", func(path path, v interface{}) *Results { actualTime, ok := v.(time.Time) if !ok { return SimpleResult(path, false, "Value %t was not a time.Time", v) @@ -118,7 +118,7 @@ func IsEqualToTime(to time.Time) IsDef { // IsDeepEqual checks equality using reflect.DeepEqual. func IsDeepEqual(to interface{}) IsDef { - return Is("equals", func(path Path, v interface{}) *Results { + return Is("equals", func(path path, v interface{}) *Results { if reflect.DeepEqual(v, to) { return ValidResult(path) } @@ -133,7 +133,7 @@ func IsDeepEqual(to interface{}) IsDef { // IsArrayOf validates that the array at the given key is an array of objects all validatable // via the given Validator. func IsArrayOf(validator Validator) IsDef { - return Is("array of maps", func(path Path, v interface{}) *Results { + return Is("array of maps", func(path path, v interface{}) *Results { vArr, isArr := v.([]common.MapStr) if !isArr { return SimpleResult(path, false, "Expected array at given path") @@ -144,7 +144,7 @@ func IsArrayOf(validator Validator) IsDef { for idx, curMap := range vArr { var validatorRes *Results validatorRes = validator(curMap) - results.mergeUnderPrefix(path.ExtendSlice(idx), validatorRes) + results.mergeUnderPrefix(path.extendSlice(idx), validatorRes) } return results @@ -160,7 +160,7 @@ func IsAny(of ...IsDef) IsDef { } isName := fmt.Sprintf("either %#v", names) - return Is(isName, func(path Path, v interface{}) *Results { + return Is(isName, func(path path, v interface{}) *Results { for _, def := range of { vr := def.check(path, v, true) if vr.Valid { @@ -176,17 +176,41 @@ func IsAny(of ...IsDef) IsDef { }) } +// isStrCheck is a helper for IsDefs that must assert that the value is a string first. +func isStrCheck(path path, v interface{}) (str string, errorResults *Results) { + strV, ok := v.(string) + + if !ok { + return "", SimpleResult( + path, + false, + fmt.Sprintf("Unable to convert '%v' to string", v), + ) + } + + return strV, nil +} + +// IsNonEmptyString checks that the given value is a string and has a length > 1. +var IsNonEmptyString = Is("is a non-empty string", func(path path, v interface{}) *Results { + strV, errorResults := isStrCheck(path, v) + if errorResults != nil { + return errorResults + } + + if len(strV) == 0 { + return SimpleResult(path, false, "String '%s' should not be empty", strV) + } + + return ValidResult(path) +}) + // IsStringContaining validates that the the actual value contains the specified substring. func IsStringContaining(needle string) IsDef { - return Is("is string containing", func(path Path, v interface{}) *Results { - strV, ok := v.(string) - - if !ok { - return SimpleResult( - path, - false, - fmt.Sprintf("Unable to convert '%v' to string", v), - ) + return Is("is string containing", func(path path, v interface{}) *Results { + strV, errorResults := isStrCheck(path, v) + if errorResults != nil { + return errorResults } if !strings.Contains(strV, needle) { @@ -202,7 +226,7 @@ func IsStringContaining(needle string) IsDef { } // IsDuration tests that the given value is a duration. -var IsDuration = Is("is a duration", func(path Path, v interface{}) *Results { +var IsDuration = Is("is a duration", func(path path, v interface{}) *Results { if _, ok := v.(time.Duration); ok { return ValidResult(path) } @@ -214,7 +238,7 @@ var IsDuration = Is("is a duration", func(path Path, v interface{}) *Results { }) // IsNil tests that a value is nil. -var IsNil = Is("is nil", func(path Path, v interface{}) *Results { +var IsNil = Is("is nil", func(path path, v interface{}) *Results { if v == nil { return ValidResult(path) } @@ -226,7 +250,7 @@ var IsNil = Is("is nil", func(path Path, v interface{}) *Results { }) func intGtChecker(than int) ValueValidator { - return func(path Path, v interface{}) *Results { + return func(path path, v interface{}) *Results { n, ok := v.(int) if !ok { msg := fmt.Sprintf("%v is a %T, but was expecting an int!", v, v) diff --git a/libbeat/common/mapval/is_defs_test.go b/libbeat/common/mapval/is_defs_test.go index 7529a5695747..addeef1f8854 100644 --- a/libbeat/common/mapval/is_defs_test.go +++ b/libbeat/common/mapval/is_defs_test.go @@ -27,7 +27,7 @@ import ( ) func assertIsDefValid(t *testing.T, id IsDef, value interface{}) *Results { - res := id.check(MustParsePath("p"), value, true) + res := id.check(mustParsePath("p"), value, true) if !res.Valid { assert.Fail( @@ -40,7 +40,7 @@ func assertIsDefValid(t *testing.T, id IsDef, value interface{}) *Results { } func assertIsDefInvalid(t *testing.T, id IsDef, value interface{}) *Results { - res := id.check(MustParsePath("p"), value, true) + res := id.check(mustParsePath("p"), value, true) if res.Valid { assert.Fail( @@ -102,6 +102,12 @@ func TestRegisteredIsEqual(t *testing.T) { assertIsDefInvalid(t, id, now.Add(100)) } +func TestIsNonEmptyString(t *testing.T) { + assertIsDefValid(t, IsNonEmptyString, "abc") + assertIsDefValid(t, IsNonEmptyString, "a") + assertIsDefInvalid(t, IsNonEmptyString, "") +} + func TestIsStringContaining(t *testing.T) { id := IsStringContaining("foo") diff --git a/libbeat/common/mapval/path.go b/libbeat/common/mapval/path.go index d1fc92c678a7..4ae5fbb2724a 100644 --- a/libbeat/common/mapval/path.go +++ b/libbeat/common/mapval/path.go @@ -27,20 +27,20 @@ import ( "github.com/elastic/beats/libbeat/common" ) -// PathComponentType indicates the type of PathComponent. -type PathComponentType int +// pathComponentType indicates the type of pathComponent. +type pathComponentType int const ( - // PCMapKey is the Type for map keys. - PCMapKey PathComponentType = 1 + iota - // PCSliceIdx is the Type for slice indices. - PCSliceIdx + // pcMapKey is the Type for map keys. + pcMapKey pathComponentType = 1 + iota + // pcSliceIdx is the Type for slice indices. + pcSliceIdx ) -func (pct PathComponentType) String() string { - if pct == PCMapKey { +func (pct pathComponentType) String() string { + if pct == pcMapKey { return "map" - } else if pct == PCSliceIdx { + } else if pct == pcSliceIdx { return "slice" } else { // This should never happen, but we don't want to return an @@ -49,52 +49,52 @@ func (pct PathComponentType) String() string { } } -// PathComponent structs represent one breadcrumb in a Path. -type PathComponent struct { - Type PathComponentType // One of PCMapKey or PCSliceIdx +// pathComponent structs represent one breadcrumb in a path. +type pathComponent struct { + Type pathComponentType // One of pcMapKey or pcSliceIdx Key string // Populated for maps Index int // Populated for slices } -func (pc PathComponent) String() string { - if pc.Type == PCSliceIdx { +func (pc pathComponent) String() string { + if pc.Type == pcSliceIdx { return fmt.Sprintf("[%d]", pc.Index) } return pc.Key } -// Path represents the path within a nested set of maps. -type Path []PathComponent +// path represents the path within a nested set of maps. +type path []pathComponent -// ExtendSlice is used to add a new PathComponent of the PCSliceIdx type. -func (p Path) ExtendSlice(index int) Path { +// extendSlice is used to add a new pathComponent of the pcSliceIdx type. +func (p path) extendSlice(index int) path { return p.extend( - PathComponent{PCSliceIdx, "", index}, + pathComponent{pcSliceIdx, "", index}, ) } -// ExtendMap adds a new PathComponent of the PCMapKey type. -func (p Path) ExtendMap(key string) Path { +// extendMap adds a new pathComponent of the pcMapKey type. +func (p path) extendMap(key string) path { return p.extend( - PathComponent{PCMapKey, key, -1}, + pathComponent{pcMapKey, key, -1}, ) } -func (p Path) extend(pc PathComponent) Path { - out := make(Path, len(p)+1) +func (p path) extend(pc pathComponent) path { + out := make(path, len(p)+1) copy(out, p) out[len(p)] = pc return out } -// Concat combines two paths into a new path without modifying any existing paths. -func (p Path) Concat(other Path) Path { - out := make(Path, 0, len(p)+len(other)) +// concat combines two paths into a new path without modifying any existing paths. +func (p path) concat(other path) path { + out := make(path, 0, len(p)+len(other)) out = append(out, p...) return append(out, other...) } -func (p Path) String() string { +func (p path) String() string { out := make([]string, len(p)) for idx, pc := range p { out[idx] = pc.String() @@ -102,9 +102,9 @@ func (p Path) String() string { return strings.Join(out, ".") } -// Last returns a pointer to the last PathComponent in this path. If the path empty, +// last returns a pointer to the last pathComponent in this path. If the path empty, // a nil pointer is returned. -func (p Path) Last() *PathComponent { +func (p path) last() *pathComponent { idx := len(p) - 1 if idx < 0 { return nil @@ -112,8 +112,8 @@ func (p Path) Last() *PathComponent { return &p[len(p)-1] } -// GetFrom takes a map and fetches the given path from it. -func (p Path) GetFrom(m common.MapStr) (value interface{}, exists bool) { +// getFrom takes a map and fetches the given path from it. +func (p path) getFrom(m common.MapStr) (value interface{}, exists bool) { value = m exists = true for _, pc := range p { @@ -153,26 +153,26 @@ var arrMatcher = regexp.MustCompile("\\[(\\d+)\\]") type InvalidPathString string func (ps InvalidPathString) Error() string { - return fmt.Sprintf("Invalid path Path: %#v", ps) + return fmt.Sprintf("Invalid path path: %#v", ps) } -// ParsePath parses a path of form key.[0].otherKey.[1] into a Path object. -func ParsePath(in string) (p Path, err error) { +// parsePath parses a path of form key.[0].otherKey.[1] into a path object. +func parsePath(in string) (p path, err error) { keyParts := strings.Split(in, ".") - p = make(Path, len(keyParts)) + p = make(path, len(keyParts)) for idx, part := range keyParts { r := arrMatcher.FindStringSubmatch(part) - pc := PathComponent{Index: -1} + pc := pathComponent{Index: -1} if len(r) > 0 { - pc.Type = PCSliceIdx + pc.Type = pcSliceIdx // Cannot fail, validated by regexp already pc.Index, err = strconv.Atoi(r[1]) if err != nil { return p, err } } else if len(part) > 0 { - pc.Type = PCMapKey + pc.Type = pcMapKey pc.Key = part } else { return p, InvalidPathString(in) @@ -184,9 +184,9 @@ func ParsePath(in string) (p Path, err error) { return p, nil } -// MustParsePath is a convenience method for parsing paths that have been previously validated -func MustParsePath(in string) Path { - out, err := ParsePath(in) +// mustParsePath is a convenience method for parsing paths that have been previously validated +func mustParsePath(in string) path { + out, err := parsePath(in) if err != nil { panic(err) } diff --git a/libbeat/common/mapval/path_test.go b/libbeat/common/mapval/path_test.go index 5ee28da10d09..8cb5949a419d 100644 --- a/libbeat/common/mapval/path_test.go +++ b/libbeat/common/mapval/path_test.go @@ -27,17 +27,17 @@ import ( func TestPathComponentType_String(t *testing.T) { tests := []struct { name string - pct PathComponentType + pct pathComponentType want string }{ { "Should return the correct type", - PCMapKey, + pcMapKey, "map", }, { "Should return the correct type", - PCSliceIdx, + pcSliceIdx, "slice", }, } @@ -45,7 +45,7 @@ func TestPathComponentType_String(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if got := tt.pct.String(); got != tt.want { - t.Errorf("PathComponentType.String() = %v, want %v", got, tt.want) + t.Errorf("pathComponentType.String() = %v, want %v", got, tt.want) } }) } @@ -53,7 +53,7 @@ func TestPathComponentType_String(t *testing.T) { func TestPathComponent_String(t *testing.T) { type fields struct { - Type PathComponentType + Type pathComponentType Key string Index int } @@ -64,24 +64,24 @@ func TestPathComponent_String(t *testing.T) { }{ { "Map key should return a literal", - fields{PCMapKey, "foo", 0}, + fields{pcMapKey, "foo", 0}, "foo", }, { "Array index should return a bracketed number", - fields{PCSliceIdx, "", 123}, + fields{pcSliceIdx, "", 123}, "[123]", }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - pc := PathComponent{ + pc := pathComponent{ Type: tt.fields.Type, Key: tt.fields.Key, Index: tt.fields.Index, } if got := pc.String(); got != tt.want { - t.Errorf("PathComponent.String() = %v, want %v", got, tt.want) + t.Errorf("pathComponent.String() = %v, want %v", got, tt.want) } }) } @@ -93,27 +93,27 @@ func TestPath_ExtendSlice(t *testing.T) { } tests := []struct { name string - p Path + p path args args - want Path + want path }{ { "Extending an empty slice", - Path{}, + path{}, args{123}, - Path{PathComponent{PCSliceIdx, "", 123}}, + path{pathComponent{pcSliceIdx, "", 123}}, }, { "Extending a non-empty slice", - Path{PathComponent{PCMapKey, "foo", -1}}, + path{pathComponent{pcMapKey, "foo", -1}}, args{123}, - Path{PathComponent{PCMapKey, "foo", -1}, PathComponent{PCSliceIdx, "", 123}}, + path{pathComponent{pcMapKey, "foo", -1}, pathComponent{pcSliceIdx, "", 123}}, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - if got := tt.p.ExtendSlice(tt.args.index); !reflect.DeepEqual(got, tt.want) { - t.Errorf("Path.ExtendSlice() = %v, want %v", got, tt.want) + if got := tt.p.extendSlice(tt.args.index); !reflect.DeepEqual(got, tt.want) { + t.Errorf("path.extendSlice() = %v, want %v", got, tt.want) } }) } @@ -125,27 +125,27 @@ func TestPath_ExtendMap(t *testing.T) { } tests := []struct { name string - p Path + p path args args - want Path + want path }{ { "Extending an empty slice", - Path{}, + path{}, args{"foo"}, - Path{PathComponent{PCMapKey, "foo", -1}}, + path{pathComponent{pcMapKey, "foo", -1}}, }, { "Extending a non-empty slice", - Path{}.ExtendMap("foo"), + path{}.extendMap("foo"), args{"bar"}, - Path{PathComponent{PCMapKey, "foo", -1}, PathComponent{PCMapKey, "bar", -1}}, + path{pathComponent{pcMapKey, "foo", -1}, pathComponent{pcMapKey, "bar", -1}}, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - if got := tt.p.ExtendMap(tt.args.key); !reflect.DeepEqual(got, tt.want) { - t.Errorf("Path.ExtendMap() = %v, want %v", got, tt.want) + if got := tt.p.extendMap(tt.args.key); !reflect.DeepEqual(got, tt.want) { + t.Errorf("path.extendMap() = %v, want %v", got, tt.want) } }) } @@ -154,21 +154,21 @@ func TestPath_ExtendMap(t *testing.T) { func TestPath_Concat(t *testing.T) { tests := []struct { name string - p Path - arg Path - want Path + p path + arg path + want path }{ { "simple", - Path{}.ExtendMap("foo"), - Path{}.ExtendSlice(123), - Path{}.ExtendMap("foo").ExtendSlice(123), + path{}.extendMap("foo"), + path{}.extendSlice(123), + path{}.extendMap("foo").extendSlice(123), }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - if got := tt.p.Concat(tt.arg); !reflect.DeepEqual(got, tt.want) { - t.Errorf("Path.Concat() = %v, want %v", got, tt.want) + if got := tt.p.concat(tt.arg); !reflect.DeepEqual(got, tt.want) { + t.Errorf("path.concat() = %v, want %v", got, tt.want) } }) } @@ -177,29 +177,29 @@ func TestPath_Concat(t *testing.T) { func TestPath_String(t *testing.T) { tests := []struct { name string - p Path + p path want string }{ { "empty", - Path{}, + path{}, "", }, { "one element", - Path{}.ExtendMap("foo"), + path{}.extendMap("foo"), "foo", }, { "complex", - Path{}.ExtendMap("foo").ExtendSlice(123).ExtendMap("bar"), + path{}.extendMap("foo").extendSlice(123).extendMap("bar"), "foo.[123].bar", }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if got := tt.p.String(); got != tt.want { - t.Errorf("Path.String() = %v, want %v", got, tt.want) + t.Errorf("path.String() = %v, want %v", got, tt.want) } }) } @@ -208,40 +208,40 @@ func TestPath_String(t *testing.T) { func TestPath_Last(t *testing.T) { tests := []struct { name string - p Path - want *PathComponent + p path + want *pathComponent }{ { "empty path", - Path{}, + path{}, nil, }, { "one element", - Path{}.ExtendMap("foo"), - &PathComponent{PCMapKey, "foo", -1}, + path{}.extendMap("foo"), + &pathComponent{pcMapKey, "foo", -1}, }, { "many elements", - Path{}.ExtendMap("foo").ExtendMap("bar").ExtendSlice(123), - &PathComponent{PCSliceIdx, "", 123}, + path{}.extendMap("foo").extendMap("bar").extendSlice(123), + &pathComponent{pcSliceIdx, "", 123}, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - if got := tt.p.Last(); !reflect.DeepEqual(got, tt.want) { - t.Errorf("Path.Last() = %v, want %v", got, tt.want) + if got := tt.p.last(); !reflect.DeepEqual(got, tt.want) { + t.Errorf("path.last() = %v, want %v", got, tt.want) } }) } } func TestPath_GetFrom(t *testing.T) { - fooPath := Path{}.ExtendMap("foo") - complexPath := Path{}.ExtendMap("foo").ExtendSlice(0).ExtendMap("bar").ExtendSlice(1) + fooPath := path{}.extendMap("foo") + complexPath := path{}.extendMap("foo").extendSlice(0).extendMap("bar").extendSlice(1) tests := []struct { name string - p Path + p path arg common.MapStr wantValue interface{} wantExists bool @@ -277,12 +277,12 @@ func TestPath_GetFrom(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - gotValue, gotExists := tt.p.GetFrom(tt.arg) + gotValue, gotExists := tt.p.getFrom(tt.arg) if !reflect.DeepEqual(gotValue, tt.wantValue) { - t.Errorf("Path.GetFrom() gotValue = %v, want %v", gotValue, tt.wantValue) + t.Errorf("path.getFrom() gotValue = %v, want %v", gotValue, tt.wantValue) } if gotExists != tt.wantExists { - t.Errorf("Path.GetFrom() gotExists = %v, want %v", gotExists, tt.wantExists) + t.Errorf("path.getFrom() gotExists = %v, want %v", gotExists, tt.wantExists) } }) } @@ -292,32 +292,32 @@ func TestParsePath(t *testing.T) { tests := []struct { name string arg string - wantP Path + wantP path wantErr bool }{ { "simple", "foo", - Path{}.ExtendMap("foo"), + path{}.extendMap("foo"), false, }, { "complex", "foo.[0].bar.[1].baz", - Path{}.ExtendMap("foo").ExtendSlice(0).ExtendMap("bar").ExtendSlice(1).ExtendMap("baz"), + path{}.extendMap("foo").extendSlice(0).extendMap("bar").extendSlice(1).extendMap("baz"), false, }, // TODO: The validation and testing for this needs to be better } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - gotP, err := ParsePath(tt.arg) + gotP, err := parsePath(tt.arg) if (err != nil) != tt.wantErr { - t.Errorf("ParsePath() error = %v, wantErr %v", err, tt.wantErr) + t.Errorf("parsePath() error = %v, wantErr %v", err, tt.wantErr) return } if !reflect.DeepEqual(gotP, tt.wantP) { - t.Errorf("ParsePath() = %v, want %v", gotP, tt.wantP) + t.Errorf("parsePath() = %v, want %v", gotP, tt.wantP) } }) } diff --git a/libbeat/common/mapval/results.go b/libbeat/common/mapval/results.go index 4b20504cf5da..07c8a37b8afb 100644 --- a/libbeat/common/mapval/results.go +++ b/libbeat/common/mapval/results.go @@ -38,14 +38,14 @@ func NewResults() *Results { // SimpleResult provides a convenient and simple method for creating a *Results object for a single validation. // It's a very common way for validators to return a *Results object, and is generally simpler than // using SingleResult. -func SimpleResult(path Path, valid bool, msg string, args ...interface{}) *Results { +func SimpleResult(path path, valid bool, msg string, args ...interface{}) *Results { vr := ValueResult{valid, fmt.Sprintf(msg, args...)} return SingleResult(path, vr) } // SingleResult returns a *Results object with a single validated value at the given path // using the provided ValueResult as its sole validation. -func SingleResult(path Path, result ValueResult) *Results { +func SingleResult(path path, result ValueResult) *Results { r := NewResults() r.record(path, result) return r @@ -54,12 +54,12 @@ func SingleResult(path Path, result ValueResult) *Results { func (r *Results) merge(other *Results) { for path, valueResults := range other.Fields { for _, valueResult := range valueResults { - r.record(MustParsePath(path), valueResult) + r.record(mustParsePath(path), valueResult) } } } -func (r *Results) mergeUnderPrefix(prefix Path, other *Results) { +func (r *Results) mergeUnderPrefix(prefix path, other *Results) { if len(prefix) == 0 { // If the prefix is empty, just use standard merge // No need to add the dots @@ -69,13 +69,13 @@ func (r *Results) mergeUnderPrefix(prefix Path, other *Results) { for path, valueResults := range other.Fields { for _, valueResult := range valueResults { - parsed := MustParsePath(path) - r.record(prefix.Concat(parsed), valueResult) + parsed := mustParsePath(path) + r.record(prefix.concat(parsed), valueResult) } } } -func (r *Results) record(path Path, result ValueResult) { +func (r *Results) record(path path, result ValueResult) { if r.Fields[path.String()] == nil { r.Fields[path.String()] = []ValueResult{result} } else { @@ -90,10 +90,10 @@ func (r *Results) record(path Path, result ValueResult) { // EachResult executes the given callback once per Value result. // The provided callback can return true to keep iterating, or false // to stop. -func (r Results) EachResult(f func(Path, ValueResult) bool) { +func (r Results) EachResult(f func(path, ValueResult) bool) { for path, pathResults := range r.Fields { for _, result := range pathResults { - if !f(MustParsePath(path), result) { + if !f(mustParsePath(path), result) { return } } @@ -103,7 +103,7 @@ func (r Results) EachResult(f func(Path, ValueResult) bool) { // DetailedErrors returns a new Results object consisting only of error data. func (r *Results) DetailedErrors() *Results { errors := NewResults() - r.EachResult(func(path Path, vr ValueResult) bool { + r.EachResult(func(path path, vr ValueResult) bool { if !vr.Valid { errors.record(path, vr) } @@ -115,7 +115,7 @@ func (r *Results) DetailedErrors() *Results { // ValueResultError is used to represent an error validating an individual value. type ValueResultError struct { - path Path + path path valueResult ValueResult } @@ -128,7 +128,7 @@ func (vre ValueResultError) Error() string { func (r Results) Errors() []error { errors := make([]error, 0) - r.EachResult(func(path Path, vr ValueResult) bool { + r.EachResult(func(path path, vr ValueResult) bool { if !vr.Valid { errors = append(errors, ValueResultError{path, vr}) } diff --git a/libbeat/common/mapval/results_test.go b/libbeat/common/mapval/results_test.go index 50cc1d254a69..8930d8b15da9 100644 --- a/libbeat/common/mapval/results_test.go +++ b/libbeat/common/mapval/results_test.go @@ -32,8 +32,8 @@ func TestEmpty(t *testing.T) { func TestWithError(t *testing.T) { r := NewResults() - r.record(MustParsePath("foo"), KeyMissingVR) - r.record(MustParsePath("bar"), ValidVR) + r.record(mustParsePath("foo"), KeyMissingVR) + r.record(mustParsePath("bar"), ValidVR) assert.False(t, r.Valid) diff --git a/libbeat/common/mapval/value.go b/libbeat/common/mapval/value.go index 2d50f802a3b8..73d066582f7b 100644 --- a/libbeat/common/mapval/value.go +++ b/libbeat/common/mapval/value.go @@ -24,7 +24,7 @@ type ValueResult struct { } // A ValueValidator is used to validate a value in a Map. -type ValueValidator func(path Path, v interface{}) *Results +type ValueValidator func(path path, v interface{}) *Results // An IsDef defines the type of check to do. // Generally only name and checker are set. optional and checkKeyMissing are @@ -36,7 +36,7 @@ type IsDef struct { checkKeyMissing bool } -func (id IsDef) check(path Path, v interface{}, keyExists bool) *Results { +func (id IsDef) check(path path, v interface{}, keyExists bool) *Results { if id.checkKeyMissing { if !keyExists { return ValidResult(path) @@ -57,7 +57,7 @@ func (id IsDef) check(path Path, v interface{}, keyExists bool) *Results { } // ValidResult is a convenience value for Valid results. -func ValidResult(path Path) *Results { +func ValidResult(path path) *Results { return SimpleResult(path, true, "is valid") } @@ -65,7 +65,7 @@ func ValidResult(path Path) *Results { var ValidVR = ValueResult{true, "is valid"} // KeyMissingResult is emitted when a key was expected, but was not present. -func KeyMissingResult(path Path) *Results { +func KeyMissingResult(path path) *Results { return SingleResult(path, KeyMissingVR) } @@ -76,7 +76,7 @@ var KeyMissingVR = ValueResult{ } // StrictFailureResult is emitted when Strict() is used, and an unexpected field is found. -func StrictFailureResult(path Path) *Results { +func StrictFailureResult(path path) *Results { return SingleResult(path, StrictFailureVR) } diff --git a/libbeat/common/mapval/walk.go b/libbeat/common/mapval/walk.go index 118acc8f6450..2ec278b5ff0c 100644 --- a/libbeat/common/mapval/walk.go +++ b/libbeat/common/mapval/walk.go @@ -24,10 +24,10 @@ import ( ) type walkObserverInfo struct { - key PathComponent + key pathComponent value interface{} rootMap common.MapStr - path Path + path path } // walkObserver functions run once per object in the tree. @@ -35,11 +35,11 @@ type walkObserver func(info walkObserverInfo) error // walk is a shorthand way to walk a tree. func walk(m common.MapStr, expandPaths bool, wo walkObserver) error { - return walkFullMap(m, m, Path{}, expandPaths, wo) + return walkFullMap(m, m, path{}, expandPaths, wo) } -func walkFull(o interface{}, root common.MapStr, path Path, expandPaths bool, wo walkObserver) (err error) { - lastPathComponent := path.Last() +func walkFull(o interface{}, root common.MapStr, path path, expandPaths bool, wo walkObserver) (err error) { + lastPathComponent := path.last() if lastPathComponent == nil { panic("Attempted to traverse an empty path in mapval.walkFull, this should never happen.") } @@ -60,7 +60,7 @@ func walkFull(o interface{}, root common.MapStr, path Path, expandPaths bool, wo converted := sliceToSliceOfInterfaces(o) for idx, v := range converted { - newPath := path.ExtendSlice(idx) + newPath := path.extendSlice(idx) err := walkFull(v, root, newPath, expandPaths, wo) if err != nil { return err @@ -72,17 +72,17 @@ func walkFull(o interface{}, root common.MapStr, path Path, expandPaths bool, wo } // walkFullMap walks the given MapStr tree. -func walkFullMap(m common.MapStr, root common.MapStr, path Path, expandPaths bool, wo walkObserver) (err error) { +func walkFullMap(m common.MapStr, root common.MapStr, p path, expandPaths bool, wo walkObserver) (err error) { for k, v := range m { - var newPath Path + var newPath path if !expandPaths { - newPath = path.ExtendMap(k) + newPath = p.extendMap(k) } else { - additionalPath, err := ParsePath(k) + additionalPath, err := parsePath(k) if err != nil { return err } - newPath = path.Concat(additionalPath) + newPath = p.concat(additionalPath) } err = walkFull(v, root, newPath, expandPaths, wo)