diff --git a/vendor/github.com/elastic/go-lookslike/CHANGELOG b/vendor/github.com/elastic/go-lookslike/CHANGELOG new file mode 100644 index 00000000000..61d0aa8d4b5 --- /dev/null +++ b/vendor/github.com/elastic/go-lookslike/CHANGELOG @@ -0,0 +1,7 @@ +## v0.2.0 + +* Move package go-lookslike/lookslike to root (go-lookslike) for simplicity + +## v0.1.0 + +* Initial release! diff --git a/vendor/github.com/elastic/go-lookslike/LICENSE b/vendor/github.com/elastic/go-lookslike/LICENSE new file mode 100644 index 00000000000..8dada3edaf5 --- /dev/null +++ b/vendor/github.com/elastic/go-lookslike/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "{}" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright {yyyy} {name of copyright owner} + + Licensed 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. diff --git a/vendor/github.com/elastic/go-lookslike/README.md b/vendor/github.com/elastic/go-lookslike/README.md new file mode 100644 index 00000000000..4c6486b6c3a --- /dev/null +++ b/vendor/github.com/elastic/go-lookslike/README.md @@ -0,0 +1,33 @@ +# lookslike + +[![Build Status](https://travis-ci.org/elastic/go-lookslike.svg?branch=master)](https://travis-ci.org/elastic/go-lookslike) + +This library is here to help you with all your data validation needs. It's ideally suited to writing tests against JSON-like structures, but can do much much more. We use it in [elastic/beats](https://github.com/elastic/beats) for our own tests. + +## Quick Links + +* [GoDoc](https://godoc.org/github.com/elastic/go-lookslike) for this library. +* [Runnable Examples](https://github.com/elastic/go-lookslike/blob/master/lookslike/doc_test.go). + +## Install + +If using go modules edit `go.mod`, adding the following to your require list, replacing VERSION, with the latest version from our [releases page](https://github.com/elastic/go-lookslike/releases). + +``` +require ( + github.com/elastic/go-lookslike VERSION +) +```` + +If using govendor run: + +`govendor fetch github.com/elastic/go-lookslike` + +## Real World Usage Examples + +lookslike was created to improve the testing of various structures in [elastic/beats](https://github.com/elastic/beats). Searching the tests for `lookslike` will show real world usage. + +## Call for More `isdef`s! + +We currently [define](https://godoc.org/github.com/elastic/go-lookslike/isdef) only the isdefs +we've actually used in the field. If you'd like to add your own, please open a PR (with tests!). diff --git a/vendor/github.com/elastic/go-lookslike/compiled_schema.go b/vendor/github.com/elastic/go-lookslike/compiled_schema.go new file mode 100644 index 00000000000..5139ed38d5d --- /dev/null +++ b/vendor/github.com/elastic/go-lookslike/compiled_schema.go @@ -0,0 +1,48 @@ +// 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 lookslike + +import ( + "github.com/elastic/go-lookslike/isdef" + "github.com/elastic/go-lookslike/llpath" + "github.com/elastic/go-lookslike/llresult" +) + +type flatValidator struct { + path llpath.Path + isDef isdef.IsDef +} + +// CompiledSchema represents a compiled definition for driving a validator.Validator. +type CompiledSchema []flatValidator + +// Check executes the the checks within the CompiledSchema +func (cs CompiledSchema) Check(actual interface{}) *llresult.Results { + res := llresult.NewResults() + for _, pv := range cs { + actualV, actualKeyExists := pv.path.GetFrom(actual) + + if !pv.isDef.Optional || pv.isDef.Optional && actualKeyExists { + var checkRes *llresult.Results + checkRes = pv.isDef.Check(pv.path, actualV, actualKeyExists) + res.Merge(checkRes) + } + } + + return res +} diff --git a/vendor/github.com/elastic/go-lookslike/core.go b/vendor/github.com/elastic/go-lookslike/core.go new file mode 100644 index 00000000000..bf33df31e9f --- /dev/null +++ b/vendor/github.com/elastic/go-lookslike/core.go @@ -0,0 +1,172 @@ +// 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 lookslike + +import ( + "reflect" + "sort" + "strings" + + "github.com/elastic/go-lookslike/isdef" + "github.com/elastic/go-lookslike/llpath" + "github.com/elastic/go-lookslike/llresult" + "github.com/elastic/go-lookslike/validator" +) + +// Compose combines multiple SchemaValidators into a single one. +func Compose(validators ...validator.Validator) validator.Validator { + return func(actual interface{}) *llresult.Results { + res := make([]*llresult.Results, len(validators)) + for idx, validator := range validators { + res[idx] = validator(actual) + } + + combined := llresult.NewResults() + for _, r := range res { + r.EachResult(func(path llpath.Path, vr llresult.ValueResult) bool { + combined.Record(path, vr) + return true + }) + } + return combined + } +} + +// Strict is used when you want any unspecified keys that are encountered to be considered errors. +func Strict(laxValidator validator.Validator) validator.Validator { + return func(actual interface{}) *llresult.Results { + res := laxValidator(actual) + + // When validating nil objects the lax validator is by definition sufficient + if actual == nil { + return res + } + + // The inner workings of this are a little weird + // We use a hash of dotted paths to track the res + // We can Check if a key had a test associated with it by looking up the laxValidator + // result data + // What's trickier is intermediate maps, maps don't usually have explicit tests, they usually just have + // their properties tested. + // This method counts an intermediate map as tested if a subkey is tested. + // Since the datastructure we have to search is a flattened hashmap of the original map we take that hashmap + // and turn it into a sorted string array, then do a binary prefix search to determine if a subkey was tested. + // It's a little weird, but is fairly efficient. We could stop using the flattened map as a datastructure, but + // that would add complexity elsewhere. Probably a good refactor at some point, but not worth it now. + validatedPaths := []string{} + for k := range res.Fields { + validatedPaths = append(validatedPaths, k) + } + sort.Strings(validatedPaths) + + walk(actual, false, func(woi walkObserverInfo) error { + _, validatedExactly := res.Fields[woi.path.String()] + if validatedExactly { + return nil // This key was tested, passes strict test + } + + // Search returns the point just before an actual match (since we ruled out an exact match with the cheaper + // hash Check above. We have to validate the actual match with a prefix Check as well + matchIdx := sort.SearchStrings(validatedPaths, woi.path.String()) + if matchIdx < len(validatedPaths) && strings.HasPrefix(validatedPaths[matchIdx], woi.path.String()) { + return nil + } + + res.Merge(llresult.StrictFailureResult(woi.path)) + + return nil + }) + + return res + } +} + +func compile(in interface{}) (validator.Validator, error) { + switch in.(type) { + case map[string]interface{}: + return compileMap(in.(map[string]interface{})) + case []interface{}: + return compileSlice(in.([]interface{})) + case isdef.IsDef: + return compileIsDef(in.(isdef.IsDef)) + case nil: + // nil can't be handled by the default case of IsEqual + return compileIsDef(isdef.IsNil) + default: + // By default we just check reflection equality + return compileIsDef(isdef.IsEqual(in)) + } +} + +func compileMap(in map[string]interface{}) (validator validator.Validator, err error) { + wo, compiled := setupWalkObserver() + err = walkMap(in, true, wo) + + return func(actual interface{}) *llresult.Results { + return compiled.Check(actual) + }, err +} + +func compileSlice(in []interface{}) (validator validator.Validator, err error) { + wo, compiled := setupWalkObserver() + err = walkSlice(in, true, wo) + + // Slices are always strict in validation because + // it would be surprising to only validate the first specified values + return Strict(func(actual interface{}) *llresult.Results { + return compiled.Check(actual) + }), err +} + +func compileIsDef(def isdef.IsDef) (validator validator.Validator, err error) { + return func(actual interface{}) *llresult.Results { + return def.Check(llpath.Path{}, actual, true) + }, nil +} + +func setupWalkObserver() (walkObserver, *CompiledSchema) { + compiled := make(CompiledSchema, 0) + return func(current walkObserverInfo) error { + // Determine whether we should test this value + // We want to test all values except collections that contain a value + // If a collection contains a value, we Check those 'leaf' values instead + rv := reflect.ValueOf(current.value) + kind := rv.Kind() + isCollection := kind == reflect.Map || kind == reflect.Slice + isNonEmptyCollection := isCollection && rv.Len() > 0 + + if !isNonEmptyCollection { + isDef, isIsDef := current.value.(isdef.IsDef) + if !isIsDef { + isDef = isdef.IsEqual(current.value) + } + + compiled = append(compiled, flatValidator{current.path, isDef}) + } + return nil + }, &compiled +} + +// MustCompile compiles the given validation, panic-ing if that map is invalid. +func MustCompile(in interface{}) validator.Validator { + compiled, err := compile(in) + if err != nil { + panic(err) + } + return compiled +} diff --git a/vendor/github.com/elastic/go-lookslike/doc.go b/vendor/github.com/elastic/go-lookslike/doc.go new file mode 100644 index 00000000000..4f05ed626ca --- /dev/null +++ b/vendor/github.com/elastic/go-lookslike/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 lookslike 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 lookslike diff --git a/vendor/github.com/elastic/go-lookslike/go.mod b/vendor/github.com/elastic/go-lookslike/go.mod new file mode 100644 index 00000000000..fa7dc8be11e --- /dev/null +++ b/vendor/github.com/elastic/go-lookslike/go.mod @@ -0,0 +1,6 @@ +module github.com/elastic/go-lookslike + +require ( + github.com/davecgh/go-spew v1.1.1 + github.com/stretchr/testify v1.3.0 +) diff --git a/vendor/github.com/elastic/go-lookslike/go.sum b/vendor/github.com/elastic/go-lookslike/go.sum new file mode 100644 index 00000000000..4f89841505b --- /dev/null +++ b/vendor/github.com/elastic/go-lookslike/go.sum @@ -0,0 +1,8 @@ +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= diff --git a/vendor/github.com/elastic/go-lookslike/internal/llreflect/llreflect.go b/vendor/github.com/elastic/go-lookslike/internal/llreflect/llreflect.go new file mode 100644 index 00000000000..ba959edbd6c --- /dev/null +++ b/vendor/github.com/elastic/go-lookslike/internal/llreflect/llreflect.go @@ -0,0 +1,68 @@ +// 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 llreflect + +import ( + "reflect" +) + +// InterfaceToMap converts the given interface to a map[string]interface{}. +func InterfaceToMap(o interface{}) map[string]interface{} { + newMap := map[string]interface{}{} + rv := reflect.ValueOf(o) + + for _, key := range rv.MapKeys() { + mapV := rv.MapIndex(key) + keyStr := key.Interface().(string) + var value interface{} + + if !mapV.IsNil() { + value = mapV.Interface().(interface{}) + } + + newMap[keyStr] = value + } + return newMap +} + +// InterfaceToSliceOfInterfaces coerces the given interface into a slice of []interface{}. +// If the given interface is already []interface{} lets it pass through. If it's another +// slice type it converts all values to interface{}. +func InterfaceToSliceOfInterfaces(o interface{}) []interface{} { + if reflect.TypeOf(o).Kind() != reflect.Slice { + o = []interface{}{o} + } + + rv := reflect.ValueOf(o) + converted := make([]interface{}, rv.Len()) + for i := 0; i < rv.Len(); i++ { + var indexV = rv.Index(i) + var convertedValue interface{} + if indexV.Type().Kind() == reflect.Interface { + if !indexV.IsNil() { + convertedValue = indexV.Interface().(interface{}) + } else { + convertedValue = nil + } + } else { + convertedValue = indexV.Interface().(interface{}) + } + converted[i] = convertedValue + } + return converted +} diff --git a/vendor/github.com/elastic/go-lookslike/isdef/core.go b/vendor/github.com/elastic/go-lookslike/isdef/core.go new file mode 100644 index 00000000000..5d36037e007 --- /dev/null +++ b/vendor/github.com/elastic/go-lookslike/isdef/core.go @@ -0,0 +1,109 @@ +package isdef + +import ( + "fmt" + "reflect" + + "github.com/elastic/go-lookslike/llpath" + "github.com/elastic/go-lookslike/llresult" +) + +// IsEqual tests that the given object is equal to the actual object. +func IsEqual(to interface{}) IsDef { + toV := reflect.ValueOf(to) + isDefFactory, ok := equalChecks[toV.Type()] + + // If there are no handlers declared explicitly for this type we perform a deep equality check + if !ok { + return IsDeepEqual(to) + } + + // 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 llpath.Path, v interface{}) *llresult.Results { + return checker(path, v) + }) +} + +// KeyPresent checks that the given key is in the map, even if it has a nil value. +var KeyPresent = IsDef{Name: "check key present"} + +// KeyMissing checks that the given key is not present defined. +var KeyMissing = IsDef{Name: "check key not present", CheckKeyMissing: true} + +func init() { + MustRegisterEqual(IsEqualToTime) +} + +// InvalidEqualFnError is the error type returned by RegisterEqual when +// there is an issue with the given function. +type InvalidEqualFnError struct{ msg string } + +func (e InvalidEqualFnError) Error() string { + return fmt.Sprintf("Function is not a valid equal function: %s", e.msg) +} + +// MustRegisterEqual is the panic-ing equivalent of RegisterEqual. +func MustRegisterEqual(fn interface{}) { + if err := RegisterEqual(fn); err != nil { + panic(fmt.Sprintf("Could not register fn as equal! %v", err)) + } +} + +var equalChecks = map[reflect.Type]reflect.Value{} + +// RegisterEqual takes a function of the form fn(v someType) IsDef +// and registers it to check equality for that type. +func RegisterEqual(fn interface{}) error { + fnV := reflect.ValueOf(fn) + fnT := fnV.Type() + + if fnT.Kind() != reflect.Func { + return InvalidEqualFnError{"Provided value is not a function"} + } + if fnT.NumIn() != 1 { + return InvalidEqualFnError{"Equal FN should take one argument"} + } + if fnT.NumOut() != 1 { + return InvalidEqualFnError{"Equal FN should return one value"} + } + if fnT.Out(0) != reflect.TypeOf(IsDef{}) { + return InvalidEqualFnError{"Equal FN should return an IsDef"} + } + + inT := fnT.In(0) + if _, ok := equalChecks[inT]; ok { + return InvalidEqualFnError{fmt.Sprintf("Duplicate Equal FN for type %v encountered!", inT)} + } + + equalChecks[inT] = fnV + + return nil +} + +// IsDeepEqual checks equality using reflect.DeepEqual. +func IsDeepEqual(to interface{}) IsDef { + return Is("equals", func(path llpath.Path, v interface{}) *llresult.Results { + if reflect.DeepEqual(v, to) { + return llresult.ValidResult(path) + } + return llresult.SimpleResult( + path, + false, + fmt.Sprintf("objects not equal: actual(%T(%v)) != expected(%T(%v))", v, v, to, to), + ) + }) +} + +// IsNil tests that a value is nil. +var IsNil = Is("is nil", func(path llpath.Path, v interface{}) *llresult.Results { + if v == nil { + return llresult.ValidResult(path) + } + return llresult.SimpleResult( + path, + false, + fmt.Sprintf("Value %#v is not nil", v), + ) +}) diff --git a/vendor/github.com/elastic/go-lookslike/isdef/dsl.go b/vendor/github.com/elastic/go-lookslike/isdef/dsl.go new file mode 100644 index 00000000000..4db4bc1ca0a --- /dev/null +++ b/vendor/github.com/elastic/go-lookslike/isdef/dsl.go @@ -0,0 +1,132 @@ +package isdef + +import ( + "fmt" + "reflect" + + "github.com/elastic/go-lookslike/internal/llreflect" + "github.com/elastic/go-lookslike/llpath" + "github.com/elastic/go-lookslike/llresult" + "github.com/elastic/go-lookslike/validator" +) + +// Is creates a named IsDef with the given Checker. +func Is(name string, checker ValueValidator) IsDef { + return IsDef{Name: name, Checker: checker} +} + +// A ValueValidator is used to validate a value in an interface{}. +type ValueValidator func(path llpath.Path, v interface{}) *llresult.Results + +// An IsDef defines the type of Check to do. +// Generally only Name and Checker are set. Optional and CheckKeyMissing are +// needed for weird checks like key presence. +type IsDef struct { + Name string + Checker ValueValidator + Optional bool + CheckKeyMissing bool +} + +// Check runs the IsDef at the given value at the given path +func (id IsDef) Check(path llpath.Path, v interface{}, keyExists bool) *llresult.Results { + if id.CheckKeyMissing { + if !keyExists { + return llresult.ValidResult(path) + } + + return llresult.SimpleResult(path, false, "this key should not exist") + } + + if !id.Optional && !keyExists { + return llresult.KeyMissingResult(path) + } + + if id.Checker != nil { + return id.Checker(path, v) + } + + return llresult.ValidResult(path) +} + +// Optional wraps an IsDef to mark the field's presence as Optional. +func Optional(id IsDef) IsDef { + id.Name = "Optional " + id.Name + id.Optional = true + return id +} + +// IsSliceOf validates that the array at the given key is an array of objects all validatable +// via the given validator.Validator. +func IsSliceOf(validator validator.Validator) IsDef { + return Is("slice", func(path llpath.Path, v interface{}) *llresult.Results { + if reflect.TypeOf(v).Kind() != reflect.Slice { + return llresult.SimpleResult(path, false, "Expected slice at given path") + } + vSlice := llreflect.InterfaceToSliceOfInterfaces(v) + + res := llresult.NewResults() + + for idx, curV := range vSlice { + var validatorRes *llresult.Results + validatorRes = validator(curV) + res.MergeUnderPrefix(path.ExtendSlice(idx), validatorRes) + } + + return res + }) +} + +// IsAny takes a variable number of IsDef's and combines them with a logical OR. If any single definition +// matches the key will be marked as valid. +func IsAny(of ...IsDef) IsDef { + names := make([]string, len(of)) + for i, def := range of { + names[i] = def.Name + } + isName := fmt.Sprintf("either %#v", names) + + return Is(isName, func(path llpath.Path, v interface{}) *llresult.Results { + for _, def := range of { + vr := def.Check(path, v, true) + if vr.Valid { + return vr + } + } + + return llresult.SimpleResult( + path, + false, + fmt.Sprintf("Value was none of %#v, actual value was %#v", names, v), + ) + }) +} + +// IsUnique instances are used in multiple spots, flagging a value as being in error if it's seen across invocations. +// To use it, assign IsUnique to a variable, then use that variable multiple times in a map[string]interface{}. +func IsUnique() IsDef { + return ScopedIsUnique().IsUniqueTo("") +} + +// UniqScopeTracker is represents the tracking data for invoking IsUniqueTo. +type UniqScopeTracker map[interface{}]string + +// IsUniqueTo validates that the given value is only ever seen within a single namespace. +func (ust UniqScopeTracker) IsUniqueTo(namespace string) IsDef { + return Is("unique", func(path llpath.Path, v interface{}) *llresult.Results { + for trackerK, trackerNs := range ust { + hasNamespace := len(namespace) > 0 + if reflect.DeepEqual(trackerK, v) && (!hasNamespace || namespace != trackerNs) { + return llresult.SimpleResult(path, false, "Value '%v' is repeated", v) + } + } + + ust[v] = namespace + return llresult.ValidResult(path) + }) +} + +// ScopedIsUnique returns a new scope for uniqueness checks. +func ScopedIsUnique() UniqScopeTracker { + return UniqScopeTracker{} +} diff --git a/vendor/github.com/elastic/go-lookslike/isdef/duration.go b/vendor/github.com/elastic/go-lookslike/isdef/duration.go new file mode 100644 index 00000000000..90cdf85ca08 --- /dev/null +++ b/vendor/github.com/elastic/go-lookslike/isdef/duration.go @@ -0,0 +1,21 @@ +package isdef + +import ( + "fmt" + "time" + + "github.com/elastic/go-lookslike/llpath" + "github.com/elastic/go-lookslike/llresult" +) + +// IsDuration tests that the given value is a duration. +var IsDuration = Is("is a duration", func(path llpath.Path, v interface{}) *llresult.Results { + if _, ok := v.(time.Duration); ok { + return llresult.ValidResult(path) + } + return llresult.SimpleResult( + path, + false, + fmt.Sprintf("Expected a time.duration, got '%v' which is a %T", v, v), + ) +}) diff --git a/vendor/github.com/elastic/go-lookslike/isdef/int.go b/vendor/github.com/elastic/go-lookslike/isdef/int.go new file mode 100644 index 00000000000..984f182eabd --- /dev/null +++ b/vendor/github.com/elastic/go-lookslike/isdef/int.go @@ -0,0 +1,33 @@ +package isdef + +import ( + "fmt" + + "github.com/elastic/go-lookslike/llpath" + "github.com/elastic/go-lookslike/llresult" +) + +func intGtChecker(than int) ValueValidator { + return func(path llpath.Path, v interface{}) *llresult.Results { + n, ok := v.(int) + if !ok { + msg := fmt.Sprintf("%v is a %T, but was expecting an int!", v, v) + return llresult.SimpleResult(path, false, msg) + } + + if n > than { + return llresult.ValidResult(path) + } + + return llresult.SimpleResult( + path, + false, + fmt.Sprintf("%v is not greater than %v", n, than), + ) + } +} + +// IsIntGt tests that a value is an int greater than. +func IsIntGt(than int) IsDef { + return Is("greater than", intGtChecker(than)) +} diff --git a/vendor/github.com/elastic/go-lookslike/isdef/string.go b/vendor/github.com/elastic/go-lookslike/isdef/string.go new file mode 100644 index 00000000000..9182231fa98 --- /dev/null +++ b/vendor/github.com/elastic/go-lookslike/isdef/string.go @@ -0,0 +1,89 @@ +package isdef + +import ( + "fmt" + "regexp" + "strings" + + "github.com/elastic/go-lookslike/llpath" + "github.com/elastic/go-lookslike/llresult" +) + +// isStrCheck is a helper for IsDefs that must assert that the value is a string first. +func isStrCheck(path llpath.Path, v interface{}) (str string, errorResults *llresult.Results) { + strV, ok := v.(string) + + if !ok { + return "", llresult.SimpleResult( + path, + false, + fmt.Sprintf("Unable to convert '%v' to string", v), + ) + } + + return strV, nil +} + +// IsString checks that the given value is a string. +var IsString = Is("is a string", func(path llpath.Path, v interface{}) *llresult.Results { + _, errorResults := isStrCheck(path, v) + if errorResults != nil { + return errorResults + } + + return llresult.ValidResult(path) +}) + +// IsNonEmptyString checks that the given value is a string and has a length > 1. +var IsNonEmptyString = Is("is a non-empty string", func(path llpath.Path, v interface{}) *llresult.Results { + strV, errorResults := isStrCheck(path, v) + if errorResults != nil { + return errorResults + } + + if len(strV) == 0 { + return llresult.SimpleResult(path, false, "String '%s' should not be empty", strV) + } + + return llresult.ValidResult(path) +}) + +// IsStringMatching checks whether a value matches the given regexp. +func IsStringMatching(regexp *regexp.Regexp) IsDef { + return Is("is string matching regexp", func(path llpath.Path, v interface{}) *llresult.Results { + strV, errorResults := isStrCheck(path, v) + if errorResults != nil { + return errorResults + } + + if !regexp.MatchString(strV) { + return llresult.SimpleResult( + path, + false, + fmt.Sprintf("String '%s' did not match regexp %s", strV, regexp.String()), + ) + } + + return llresult.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 llpath.Path, v interface{}) *llresult.Results { + strV, errorResults := isStrCheck(path, v) + if errorResults != nil { + return errorResults + } + + if !strings.Contains(strV, needle) { + return llresult.SimpleResult( + path, + false, + fmt.Sprintf("String '%s' did not contain substring '%s'", strV, needle), + ) + } + + return llresult.ValidResult(path) + }) +} diff --git a/vendor/github.com/elastic/go-lookslike/isdef/time.go b/vendor/github.com/elastic/go-lookslike/isdef/time.go new file mode 100644 index 00000000000..e1a13afa8d8 --- /dev/null +++ b/vendor/github.com/elastic/go-lookslike/isdef/time.go @@ -0,0 +1,24 @@ +package isdef + +import ( + "time" + + "github.com/elastic/go-lookslike/llpath" + "github.com/elastic/go-lookslike/llresult" +) + +// 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 llpath.Path, v interface{}) *llresult.Results { + actualTime, ok := v.(time.Time) + if !ok { + return llresult.SimpleResult(path, false, "Value %t was not a time.Time", v) + } + + if actualTime.Equal(to) { + return llresult.ValidResult(path) + } + + return llresult.SimpleResult(path, false, "actual(%v) != expected(%v)", actualTime, to) + }) +} diff --git a/vendor/github.com/elastic/go-lookslike/llpath/path.go b/vendor/github.com/elastic/go-lookslike/llpath/path.go new file mode 100644 index 00000000000..0ac5f910d73 --- /dev/null +++ b/vendor/github.com/elastic/go-lookslike/llpath/path.go @@ -0,0 +1,212 @@ +// 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 llpath + +import ( + "fmt" + "github.com/elastic/go-lookslike/internal/llreflect" + "reflect" + "regexp" + "strconv" + "strings" +) + +// 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 + // pcInterface is the type for all other values + pcInterface +) + +func (pct PathComponentType) String() string { + if pct == pcMapKey { + return "map" + } else if pct == pcSliceIdx { + return "slice" + } else if pct == pcInterface { + return "scalar" + } else { + // This should never happen, but we don't want to return an + // error since that would unnecessarily complicate the fluid API + return "" + } +} + +// 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 { + return fmt.Sprintf("[%d]", pc.Index) + } + return pc.Key +} + +// 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 { + return p.Extend( + PathComponent{pcSliceIdx, "", index}, + ) +} + +// ExtendMap adds a new PathComponent of the pcMapKey type. +func (p Path) ExtendMap(key string) Path { + return p.Extend( + PathComponent{pcMapKey, key, -1}, + ) +} + +// Extend lengthens the given path with the given component. +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)) + out = append(out, p...) + return append(out, other...) +} + +func (p Path) String() string { + out := make([]string, len(p)) + for idx, pc := range p { + out[idx] = pc.String() + } + return strings.Join(out, ".") +} + +// 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 { + idx := len(p) - 1 + if idx < 0 { + return nil + } + return &p[len(p)-1] +} + +// GetFrom takes a map and fetches the given Path from it. +func (p Path) GetFrom(m interface{}) (value interface{}, exists bool) { + // nil values are handled specially. If we're fetching from a nil + // there's one case where it exists, when comparing it to another nil. + if m == nil { + // since another nil would be scalar, we just check that the + // path length is 0. + return nil, len(p) == 0 + } + + value = m + exists = true + for _, pc := range p { + rt := reflect.TypeOf(value) + switch rt.Kind() { + case reflect.Map: + converted := llreflect.InterfaceToMap(value) + value, exists = converted[pc.Key] + case reflect.Slice: + converted := llreflect.InterfaceToSliceOfInterfaces(value) + if pc.Index < len(converted) { + exists = true + value = converted[pc.Index] + } else { + exists = false + value = nil + } + default: + // If this case has been reached this means the expected type, say a map, + // is actually something else, like a string or an array. In this case we + // simply say the value doesn't exist. From a practical perspective this is + // the right behavior since it will cause validation to fail. + return nil, false + } + + if exists == false { + return nil, exists + } + } + + return value, exists +} + +var arrMatcher = regexp.MustCompile("\\[(\\d+)\\]") + +// InvalidPathString is the error type returned from unparseable paths. +type InvalidPathString string + +func (ps InvalidPathString) Error() string { + return fmt.Sprintf("Invalid 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) { + keyParts := strings.Split(in, ".") + + // We return empty paths for empty strings + // Empty paths are valid when working with scalar values + if in == "" { + return Path{}, nil + } + + p = make(Path, len(keyParts)) + for idx, part := range keyParts { + r := arrMatcher.FindStringSubmatch(part) + pc := PathComponent{Index: -1} + if len(r) > 0 { + 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.Key = part + } else { + return nil, InvalidPathString(in) + } + + p[idx] = pc + } + + 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) + if err != nil { + panic(err) + } + return out +} diff --git a/vendor/github.com/elastic/go-lookslike/llresult/results.go b/vendor/github.com/elastic/go-lookslike/llresult/results.go new file mode 100644 index 00000000000..2b9e56e761c --- /dev/null +++ b/vendor/github.com/elastic/go-lookslike/llresult/results.go @@ -0,0 +1,156 @@ +// 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 llresult + +import ( + "fmt" + + "github.com/elastic/go-lookslike/llpath" +) + +// Results the results of executing a schema. +// They are a flattened map (using dotted paths) of all the values ValueResult representing the results +// of the IsDefs. +type Results struct { + Fields map[string][]ValueResult + Valid bool +} + +//ValueResult represents the result of checking a leaf value. +type ValueResult struct { + Valid bool + Message string // Reason this is invalid +} + +// NewResults creates a new Results object. +func NewResults() *Results { + return &Results{ + Fields: make(map[string][]ValueResult), + Valid: true, + } +} + +// 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 llpath.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 providedValueResult as its sole validation. +func SingleResult(path llpath.Path, result ValueResult) *Results { + r := NewResults() + r.Record(path, result) + return r +} + +// Merge combines multiple *Results sets together. +func (r *Results) Merge(other *Results) { + for otherPath, valueResults := range other.Fields { + for _, valueResult := range valueResults { + r.Record(llpath.MustParsePath(otherPath), valueResult) + } + } +} + +// MergeUnderPrefix merges the given results at the path specified by the given prefix. +func (r *Results) MergeUnderPrefix(prefix llpath.Path, other *Results) { + if len(prefix) == 0 { + // If the prefix is empty, just use standard Merge + // No need to add the dots + r.Merge(other) + return + } + + for otherPath, valueResults := range other.Fields { + for _, valueResult := range valueResults { + parsed := llpath.MustParsePath(otherPath) + r.Record(prefix.Concat(parsed), valueResult) + } + } +} + +// Record records a single path result to this instance. +func (r *Results) Record(p llpath.Path, result ValueResult) { + if r.Fields[p.String()] == nil { + r.Fields[p.String()] = []ValueResult{result} + } else { + r.Fields[p.String()] = append(r.Fields[p.String()], result) + } + + if !result.Valid { + r.Valid = false + } +} + +// 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(llpath.Path, ValueResult) bool) { + for p, pathResults := range r.Fields { + for _, result := range pathResults { + // We can ignore path parse errors here, those are from scalars and other + // types that have an invalid string path + // TODO: Find a cleaner way to do this + parsed, _ := llpath.ParsePath(p) + if !f(parsed, result) { + return + } + } + } +} + +// DetailedErrors returns a new Results object consisting only of error data. +func (r *Results) DetailedErrors() *Results { + errors := NewResults() + r.EachResult(func(p llpath.Path, vr ValueResult) bool { + if !vr.Valid { + errors.Record(p, vr) + } + + return true + }) + return errors +} + +//ValueResultError is used to represent an error validating an individual value. +type ValueResultError struct { + path llpath.Path + valueResult ValueResult +} + +// Error returns the error that occurred during validation with its context included. +func (vre ValueResultError) Error() string { + return fmt.Sprintf("@Path '%s': %s", vre.path, vre.valueResult.Message) +} + +// Errors returns a list of error objects, one per failed value validation. +func (r Results) Errors() []error { + errors := make([]error, 0) + + r.EachResult(func(path llpath.Path, vr ValueResult) bool { + if !vr.Valid { + errors = append(errors, ValueResultError{path, vr}) + } + return true + }) + + return errors +} diff --git a/vendor/github.com/elastic/go-lookslike/llresult/value.go b/vendor/github.com/elastic/go-lookslike/llresult/value.go new file mode 100644 index 00000000000..e0630510043 --- /dev/null +++ b/vendor/github.com/elastic/go-lookslike/llresult/value.go @@ -0,0 +1,50 @@ +// 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 llresult + +import "github.com/elastic/go-lookslike/llpath" + +// ValidResult is a convenience value for Valid results. +func ValidResult(p llpath.Path) *Results { + return SimpleResult(p, true, "is valid") +} + +// ValidVR is a convenience value for Valid results. +var ValidVR = ValueResult{true, "is valid"} + +// KeyMissingResult is emitted when a key was expected, but was not present. +func KeyMissingResult(path llpath.Path) *Results { + return SingleResult(path, KeyMissingVR) +} + +// KeyMissingVR is emitted when a key was expected, but was not present. +var KeyMissingVR = ValueResult{ + false, + "expected this key to be present", +} + +// StrictFailureResult is emitted when Strict() is used, and an unexpected field is found. +func StrictFailureResult(path llpath.Path) *Results { + return SingleResult(path, StrictFailureVR) +} + +// StrictFailureVR is emitted when Strict() is used, and an unexpected field is found. +var StrictFailureVR = ValueResult{ + false, + "unexpected field encountered during strict validation", +} diff --git a/vendor/github.com/elastic/go-lookslike/testslike/testing.go b/vendor/github.com/elastic/go-lookslike/testslike/testing.go new file mode 100644 index 00000000000..7e7a4e62506 --- /dev/null +++ b/vendor/github.com/elastic/go-lookslike/testslike/testing.go @@ -0,0 +1,47 @@ +// 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 testslike + +import ( + "testing" + + "github.com/elastic/go-lookslike/llresult" + "github.com/elastic/go-lookslike/validator" + + "github.com/davecgh/go-spew/spew" + "github.com/stretchr/testify/assert" +) + +// Test takes the output from a validator.Validator invocation and runs test assertions on the result. +// If you are using this library for testing you will probably want to run Test(t, Compile(map[string]interface{}{...}), actual) as a pattern. +func Test(t *testing.T, validator validator.Validator, value interface{}) *llresult.Results { + r := validator(value) + + if !r.Valid { + assert.Fail( + t, + "lookslike could not validate map", + "%d errors validating source: \n%s", len(r.Errors()), spew.Sdump(value), + ) + } + + for _, err := range r.Errors() { + assert.NoError(t, err) + } + return r +} diff --git a/vendor/github.com/elastic/go-lookslike/validator/validator.go b/vendor/github.com/elastic/go-lookslike/validator/validator.go new file mode 100644 index 00000000000..f02bd34d99a --- /dev/null +++ b/vendor/github.com/elastic/go-lookslike/validator/validator.go @@ -0,0 +1,6 @@ +package validator + +import "github.com/elastic/go-lookslike/llresult" + +// Validator is the result of Compile and is run against the map you'd like to test. +type Validator func(interface{}) *llresult.Results diff --git a/vendor/github.com/elastic/go-lookslike/walk.go b/vendor/github.com/elastic/go-lookslike/walk.go new file mode 100644 index 00000000000..2c0189a685e --- /dev/null +++ b/vendor/github.com/elastic/go-lookslike/walk.go @@ -0,0 +1,142 @@ +// 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 lookslike + +import ( + "reflect" + + "github.com/elastic/go-lookslike/internal/llreflect" + "github.com/elastic/go-lookslike/llpath" +) + +type walkObserverInfo struct { + key llpath.PathComponent + value interface{} + root map[string]interface{} + path llpath.Path +} + +// walkObserver functions run once per object in the tree. +type walkObserver func(info walkObserverInfo) error + +// walk determine if in is a `map[string]interface{}` or a `Slice` and traverse it if so, otherwise will +// treat it as a scalar and invoke the walk observer on the input value directly. +func walk(in interface{}, expandPaths bool, wo walkObserver) error { + switch in.(type) { + case map[string]interface{}: + return walkMap(in.(map[string]interface{}), expandPaths, wo) + case []interface{}: + return walkSlice(in.([]interface{}), expandPaths, wo) + default: + return walkInterface(in, expandPaths, wo) + } +} + +// walkmap[string]interface{} is a shorthand way to walk a tree with a map as the root. +func walkMap(m map[string]interface{}, expandPaths bool, wo walkObserver) error { + return walkFullMap(m, m, llpath.Path{}, expandPaths, wo) +} + +// walkSlice walks the provided root slice. +func walkSlice(s []interface{}, expandPaths bool, wo walkObserver) error { + return walkFullSlice(s, map[string]interface{}{}, llpath.Path{}, expandPaths, wo) +} + +func walkInterface(s interface{}, expandPaths bool, wo walkObserver) error { + return wo(walkObserverInfo{ + value: s, + key: llpath.PathComponent{}, + root: map[string]interface{}{}, + path: llpath.Path{}, + }) +} + +func walkFull(o interface{}, root map[string]interface{}, path llpath.Path, expandPaths bool, wo walkObserver) (err error) { + lastPathComponent := path.Last() + if lastPathComponent == nil { + // In the case of a slice we can have an empty path + if _, ok := o.([]interface{}); ok { + lastPathComponent = &llpath.PathComponent{} + } else { + panic("Attempted to traverse an empty Path on a map[string]interface{} in lookslike.walkFull, this should never happen.") + } + } + + err = wo(walkObserverInfo{*lastPathComponent, o, root, path}) + if err != nil { + return err + } + + switch reflect.TypeOf(o).Kind() { + case reflect.Map: + converted := llreflect.InterfaceToMap(o) + err := walkFullMap(converted, root, path, expandPaths, wo) + if err != nil { + return err + } + case reflect.Slice: + converted := llreflect.InterfaceToSliceOfInterfaces(o) + + for idx, v := range converted { + newPath := path.ExtendSlice(idx) + err := walkFull(v, root, newPath, expandPaths, wo) + if err != nil { + return err + } + } + } + + return nil +} + +// walkFull walks the given map[string]interface{} tree. +func walkFullMap(m map[string]interface{}, root map[string]interface{}, p llpath.Path, expandPaths bool, wo walkObserver) (err error) { + for k, v := range m { + var newPath llpath.Path + if !expandPaths { + newPath = p.ExtendMap(k) + } else { + additionalPath, err := llpath.ParsePath(k) + if err != nil { + return err + } + newPath = p.Concat(additionalPath) + } + + err = walkFull(v, root, newPath, expandPaths, wo) + if err != nil { + return err + } + } + + return nil +} + +func walkFullSlice(s []interface{}, root map[string]interface{}, p llpath.Path, expandPaths bool, wo walkObserver) (err error) { + for idx, v := range s { + var newPath llpath.Path + newPath = p.ExtendSlice(idx) + + err = walkFull(v, root, newPath, expandPaths, wo) + if err != nil { + return err + } + } + + return nil +} diff --git a/vendor/vendor.json b/vendor/vendor.json index f8e44cd32c7..4c56e809488 100644 --- a/vendor/vendor.json +++ b/vendor/vendor.json @@ -898,6 +898,15 @@ "version": "v0.4.0", "versionExact": "v0.4.0" }, + { + "checksumSHA1": "c54eehjtxPjBUbM4aQuDPgc7G9Q=", + "path": "github.com/elastic/go-lookslike", + "revision": "807124eb9fc6684949aa99744577175fd6bac4fd", + "revisionTime": "2019-06-17T15:05:19Z", + "tree": true, + "version": "v0.2.0", + "versionExact": "v0.2.0" + }, { "checksumSHA1": "3jizmlZPCyo6FAZY8Trk9jA8NH4=", "path": "github.com/elastic/go-lumber/client/v2",