From e0680370d72ad2c4956d2675a5b8b3652683cb2b Mon Sep 17 00:00:00 2001 From: Andrew Cholakian Date: Fri, 10 Aug 2018 12:06:08 -0500 Subject: [PATCH] Add mapval docs.go + make mapval.Path private This cleans up the docs for mapval. The various Path related functions were public, and thus cluttering up the docs, though there was no need for them to be so. This adds examples and some high-level docs. --- libbeat/common/mapval/compiled_schema.go | 4 +- libbeat/common/mapval/core.go | 8 +- libbeat/common/mapval/doc.go | 23 +++ libbeat/common/mapval/doc_test.go | 206 +++++++++++++++++++++++ libbeat/common/mapval/is_defs.go | 60 +++++-- libbeat/common/mapval/is_defs_test.go | 10 +- libbeat/common/mapval/path.go | 84 ++++----- libbeat/common/mapval/path_test.go | 118 ++++++------- libbeat/common/mapval/results.go | 24 +-- libbeat/common/mapval/results_test.go | 4 +- libbeat/common/mapval/value.go | 10 +- libbeat/common/mapval/walk.go | 22 +-- 12 files changed, 417 insertions(+), 156 deletions(-) create mode 100644 libbeat/common/mapval/doc.go create mode 100644 libbeat/common/mapval/doc_test.go 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)