From 67884fc9e3b1a61a85db024c0cf33710e57cb37f Mon Sep 17 00:00:00 2001 From: Kir Kolyshkin Date: Mon, 18 Oct 2021 20:11:20 -0700 Subject: [PATCH 1/2] validate: prepare for new xeipuuv/gojsonschema New(er) xeipuuv/gojsonschema package is trying to fetch id fields, which in the spec were looking like this: "id": "https://opencontainers.org/schema/bundle/linux" Obviously, this results in HTTP 404s, and multiple test failures. This was fixed by https://github.com/opencontainers/runtime-spec/pull/945 which ended up in runtime-spec v1.0.2. This essentially means with newer xeipuuv/gojsonschema we are no longer able to validate against runtime-spec < 1.0.2. To adopt for a new xeipuuv/gojsonschema, do the following: 1. Add the version check, add a test case for it. 2. Remove some test cases: - "process is required" as it needed v1.0.0-rc5 version of spec. - "args is required" as args are no longer required since commit https://github.com/opencontainers/runtime-spec/commit/deb4d954eafc4fc. 3. Bump the spec version in all test cases. 4. Fix "invalid seccomp action" error as it now also has SCMP_ACT_LOG. Signed-off-by: Kir Kolyshkin --- validate/validate.go | 5 ++--- validate/validate_test.go | 45 +++++++++++++++++---------------------- 2 files changed, 21 insertions(+), 29 deletions(-) diff --git a/validate/validate.go b/validate/validate.go index a827f11f..2d3d42bc 100644 --- a/validate/validate.go +++ b/validate/validate.go @@ -131,9 +131,8 @@ func JSONSchemaURL(version string) (url string, err error) { if err != nil { return "", specerror.NewError(specerror.SpecVersionInSemVer, err, rspec.Version) } - configRenamedToConfigSchemaVersion, _ := semver.Parse("1.0.0-rc2") // config.json became config-schema.json in 1.0.0-rc2 - if ver.Compare(configRenamedToConfigSchemaVersion) == -1 { - return "", fmt.Errorf("unsupported configuration version (older than %s)", configRenamedToConfigSchemaVersion) + if ver.LT(semver.Version{Major: 1, Minor: 0, Patch: 2}) { + return "", errors.New("unsupported configuration version (older than 1.0.2)") } return fmt.Sprintf(configSchemaTemplate, version), nil } diff --git a/validate/validate_test.go b/validate/validate_test.go index 852f20ce..abc4205f 100644 --- a/validate/validate_test.go +++ b/validate/validate_test.go @@ -47,33 +47,32 @@ func TestJSONSchema(t *testing.T) { }, { config: &rspec.Spec{ - Version: "1.0.1-rc1", + Version: "1.0.99-rc1", // non-existent }, error: "Could not read schema from HTTP, response status is 404 Not Found", }, { config: &rspec.Spec{ - Version: "1.0.0", + Version: "1.0.0", // too old }, - error: "", + error: "1 error occurred:\n\t* unsupported configuration version (older than 1.0.2)\n\n", }, { config: &rspec.Spec{ - Version: "1.0.0", - Process: &rspec.Process{}, + Version: "1.0.2", }, - error: "1 error occurred:\n\t* args: args is required\n\n", + error: "", }, { config: &rspec.Spec{ - Version: "1.0.0", + Version: "1.0.2", Linux: &rspec.Linux{}, }, error: "", }, { config: &rspec.Spec{ - Version: "1.0.0", + Version: "1.0.2", Linux: &rspec.Linux{ RootfsPropagation: "", }, @@ -82,7 +81,7 @@ func TestJSONSchema(t *testing.T) { }, { config: &rspec.Spec{ - Version: "1.0.0", + Version: "1.0.2", Linux: &rspec.Linux{ RootfsPropagation: "shared", }, @@ -91,7 +90,7 @@ func TestJSONSchema(t *testing.T) { }, { config: &rspec.Spec{ - Version: "1.0.0", + Version: "1.0.2", Linux: &rspec.Linux{ RootfsPropagation: "rshared", }, @@ -100,13 +99,7 @@ func TestJSONSchema(t *testing.T) { }, { config: &rspec.Spec{ - Version: "1.0.0-rc5", - }, - error: "process: process is required", - }, - { - config: &rspec.Spec{ - Version: "1.0.0", + Version: "1.0.2", Linux: &rspec.Linux{ Namespaces: []rspec.LinuxNamespace{ { @@ -119,7 +112,7 @@ func TestJSONSchema(t *testing.T) { }, { config: &rspec.Spec{ - Version: "1.0.0", + Version: "1.0.2", Linux: &rspec.Linux{ Namespaces: []rspec.LinuxNamespace{ { @@ -132,7 +125,7 @@ func TestJSONSchema(t *testing.T) { }, { config: &rspec.Spec{ - Version: "1.0.0", + Version: "1.0.2", Linux: &rspec.Linux{ Seccomp: &rspec.LinuxSeccomp{ DefaultAction: "SCMP_ACT_ALLOW", @@ -147,7 +140,7 @@ func TestJSONSchema(t *testing.T) { }, { config: &rspec.Spec{ - Version: "1.0.0", + Version: "1.0.2", Linux: &rspec.Linux{ Seccomp: &rspec.LinuxSeccomp{ DefaultAction: "SCMP_ACT_ALLOW", @@ -162,7 +155,7 @@ func TestJSONSchema(t *testing.T) { }, { config: &rspec.Spec{ - Version: "1.0.0", + Version: "1.0.2", Linux: &rspec.Linux{ Seccomp: &rspec.LinuxSeccomp{ DefaultAction: "SCMP_ACT_ALLOW", @@ -179,7 +172,7 @@ func TestJSONSchema(t *testing.T) { }, { config: &rspec.Spec{ - Version: "1.0.0", + Version: "1.0.2", Linux: &rspec.Linux{ Seccomp: &rspec.LinuxSeccomp{ DefaultAction: "SCMP_ACT_ALLOW", @@ -192,11 +185,11 @@ func TestJSONSchema(t *testing.T) { }, }, }, - error: "linux.seccomp.syscalls.0.action: linux.seccomp.syscalls.0.action must be one of the following: \"SCMP_ACT_KILL\", \"SCMP_ACT_TRAP\", \"SCMP_ACT_ERRNO\", \"SCMP_ACT_TRACE\", \"SCMP_ACT_ALLOW\"", + error: "linux.seccomp.syscalls.0.action: linux.seccomp.syscalls.0.action must be one of the following: \"SCMP_ACT_KILL\", \"SCMP_ACT_TRAP\", \"SCMP_ACT_ERRNO\", \"SCMP_ACT_TRACE\", \"SCMP_ACT_ALLOW\", \"SCMP_ACT_LOG\"", }, { config: &rspec.Spec{ - Version: "1.0.0", + Version: "1.0.2", Linux: &rspec.Linux{ Seccomp: &rspec.LinuxSeccomp{ DefaultAction: "SCMP_ACT_ALLOW", @@ -220,7 +213,7 @@ func TestJSONSchema(t *testing.T) { }, { config: &rspec.Spec{ - Version: "1.0.0", + Version: "1.0.2", Linux: &rspec.Linux{ Seccomp: &rspec.LinuxSeccomp{ DefaultAction: "SCMP_ACT_ALLOW", @@ -322,7 +315,7 @@ func TestCheckSemVer(t *testing.T) { expected specerror.Code }{ {rspec.Version, specerror.NonError}, - //FIXME: validate currently only handles rpsec.Version + // FIXME: validate currently only handles rpsec.Version {"0.0.1", specerror.NonRFCError}, {"invalid", specerror.SpecVersionInSemVer}, } From 221e5eac762e8a48279a1ad7fd14979b18e74e14 Mon Sep 17 00:00:00 2001 From: Kir Kolyshkin Date: Mon, 18 Oct 2021 20:19:45 -0700 Subject: [PATCH 2/2] deps: bump github.com/xeipuuv/gojsonschema to v1.2.0 Brought to you by go get github.com/xeipuuv/gojsonschema@latest go mod vendor go mod tidy Signed-off-by: Kir Kolyshkin --- go.mod | 7 +- go.sum | 18 +- vendor/github.com/stretchr/testify/LICENSE | 35 +- .../testify/assert/assertion_format.go | 243 ++++++--- .../testify/assert/assertion_format.go.tmpl | 1 + .../testify/assert/assertion_forward.go | 486 ++++++++++++----- .../testify/assert/assertion_forward.go.tmpl | 1 + .../stretchr/testify/assert/assertions.go | 506 ++++++++++++------ .../testify/assert/http_assertions.go | 32 +- .../xeipuuv/gojsonpointer/README.md | 33 ++ .../xeipuuv/gojsonpointer/pointer.go | 21 + .../xeipuuv/gojsonreference/reference.go | 18 +- .../xeipuuv/gojsonschema/.gitignore | 2 + .../xeipuuv/gojsonschema/.travis.yml | 8 +- .../github.com/xeipuuv/gojsonschema/README.md | 225 +++++++- .../github.com/xeipuuv/gojsonschema/draft.go | 125 +++++ .../github.com/xeipuuv/gojsonschema/errors.go | 139 ++++- .../xeipuuv/gojsonschema/format_checkers.go | 269 ++++++++-- .../xeipuuv/gojsonschema/glide.yaml | 7 +- vendor/github.com/xeipuuv/gojsonschema/go.mod | 7 + vendor/github.com/xeipuuv/gojsonschema/go.sum | 11 + .../xeipuuv/gojsonschema/jsonContext.go | 17 +- .../xeipuuv/gojsonschema/jsonLoader.go | 97 +++- .../xeipuuv/gojsonschema/locales.go | 196 ++++++- .../github.com/xeipuuv/gojsonschema/result.go | 84 ++- .../github.com/xeipuuv/gojsonschema/schema.go | 502 +++++++++++------ .../xeipuuv/gojsonschema/schemaLoader.go | 206 +++++++ .../xeipuuv/gojsonschema/schemaPool.go | 170 ++++-- .../gojsonschema/schemaReferencePool.go | 5 +- .../xeipuuv/gojsonschema/subSchema.go | 136 +---- .../github.com/xeipuuv/gojsonschema/types.go | 4 + .../github.com/xeipuuv/gojsonschema/utils.go | 125 ++--- .../xeipuuv/gojsonschema/validation.go | 456 ++++++++-------- vendor/modules.txt | 11 +- 34 files changed, 3045 insertions(+), 1158 deletions(-) create mode 100644 vendor/github.com/xeipuuv/gojsonschema/draft.go create mode 100644 vendor/github.com/xeipuuv/gojsonschema/go.mod create mode 100644 vendor/github.com/xeipuuv/gojsonschema/go.sum create mode 100644 vendor/github.com/xeipuuv/gojsonschema/schemaLoader.go diff --git a/go.mod b/go.mod index b0389e87..de40a694 100644 --- a/go.mod +++ b/go.mod @@ -11,17 +11,14 @@ require ( github.com/opencontainers/selinux v1.0.1-0.20190118194646-2d93b96e1a01 github.com/satori/go.uuid v1.1.0 github.com/sirupsen/logrus v1.0.2-0.20170713114250-a3f95b5c4235 - github.com/stretchr/testify v1.1.5-0.20170809224252-890a5c3458b4 + github.com/stretchr/testify v1.3.0 github.com/syndtr/gocapability v0.0.0-20170704070218-db04d3cc01c8 github.com/urfave/cli v1.19.1 - github.com/xeipuuv/gojsonschema v0.0.0-20170528113821-0c8571ac0ce1 + github.com/xeipuuv/gojsonschema v1.2.0 golang.org/x/sys v0.0.0-20170407050850-f3918c30c5c2 ) require ( github.com/davecgh/go-spew v1.1.1-0.20170829195320-a47672248388 // indirect - github.com/pmezard/go-difflib v1.0.0 // indirect - github.com/xeipuuv/gojsonpointer v0.0.0-20170225233418-6fe8760cad35 // indirect - github.com/xeipuuv/gojsonreference v0.0.0-20150808065054-e02fc20de94c // indirect gopkg.in/yaml.v2 v2.4.0 // indirect ) diff --git a/go.sum b/go.sum index d9d30400..2796a486 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,6 @@ github.com/blang/semver v3.5.0+incompatible h1:CGxCgetQ64DKk7rdZ++Vfnb1+ogGNnB17OJKJXD2Cfs= github.com/blang/semver v3.5.0+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1-0.20170829195320-a47672248388 h1:c9n+0y02ovmvU9O/gS/fqw6HRlUwG645A2AJckmQVy0= github.com/davecgh/go-spew v1.1.1-0.20170829195320-a47672248388/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA= @@ -20,18 +21,19 @@ github.com/satori/go.uuid v1.1.0 h1:B9KXyj+GzIpJbV7gmr873NsY6zpbxNy24CBtGrk7jHo= github.com/satori/go.uuid v1.1.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= github.com/sirupsen/logrus v1.0.2-0.20170713114250-a3f95b5c4235 h1:aPIH7fk87dLHot2nJ8bbakmAgwM4RZJtGEkwQ52pQCg= github.com/sirupsen/logrus v1.0.2-0.20170713114250-a3f95b5c4235/go.mod h1:pMByvHTf9Beacp5x1UXfOR9xyW/9antXMhjMPG0dEzc= -github.com/stretchr/testify v1.1.5-0.20170809224252-890a5c3458b4 h1:c5DdG2to+wHgjlxcmknq5BnzaaJ0N0W842kLlOSurXc= -github.com/stretchr/testify v1.1.5-0.20170809224252-890a5c3458b4/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +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= github.com/syndtr/gocapability v0.0.0-20170704070218-db04d3cc01c8 h1:zLV6q4e8Jv9EHjNg/iHfzwDkCve6Ua5jCygptrtXHvI= github.com/syndtr/gocapability v0.0.0-20170704070218-db04d3cc01c8/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww= github.com/urfave/cli v1.19.1 h1:0mKm4ZoB74PxYmZVua162y1dGt1qc10MyymYRBf3lb8= github.com/urfave/cli v1.19.1/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= -github.com/xeipuuv/gojsonpointer v0.0.0-20170225233418-6fe8760cad35 h1:0TnXeVP6mx+A4CBf8cQVkQfkhyGBQCmJcT4g6zKzm7M= -github.com/xeipuuv/gojsonpointer v0.0.0-20170225233418-6fe8760cad35/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= -github.com/xeipuuv/gojsonreference v0.0.0-20150808065054-e02fc20de94c h1:XZWnr3bsDQWAZg4Ne+cPoXRPILrNlPNQfxBuwLl43is= -github.com/xeipuuv/gojsonreference v0.0.0-20150808065054-e02fc20de94c/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ= -github.com/xeipuuv/gojsonschema v0.0.0-20170528113821-0c8571ac0ce1 h1:p7SJSlzQ0w/wo+yjj+lnaOsAFlC0AL+t6QWkR2P7X18= -github.com/xeipuuv/gojsonschema v0.0.0-20170528113821-0c8571ac0ce1/go.mod h1:5yf86TLmAcydyeJq5YvxkGPE2fm/u4myDekKRoLuqhs= +github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f h1:J9EGpcZtP0E/raorCMxlFGSTBrsSlaDGf3jU/qvAE2c= +github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= +github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHovont7NscjpAxXsDA8S8BMYve8Y5+7cuRE7R0= +github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ= +github.com/xeipuuv/gojsonschema v1.2.0 h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17UxZ74= +github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y= golang.org/x/sys v0.0.0-20170407050850-f3918c30c5c2 h1:niKkabq6kYToDafvvFw9MeTkT4ifSvpOCRP6pFxOCZE= golang.org/x/sys v0.0.0-20170407050850-f3918c30c5c2/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= diff --git a/vendor/github.com/stretchr/testify/LICENSE b/vendor/github.com/stretchr/testify/LICENSE index 473b670a..f38ec595 100644 --- a/vendor/github.com/stretchr/testify/LICENSE +++ b/vendor/github.com/stretchr/testify/LICENSE @@ -1,22 +1,21 @@ -Copyright (c) 2012 - 2013 Mat Ryer and Tyler Bunnell +MIT License -Please consider promoting this project if you find it useful. +Copyright (c) 2012-2018 Mat Ryer and Tyler Bunnell -Permission is hereby granted, free of charge, to any person -obtaining a copy of this software and associated documentation -files (the "Software"), to deal in the Software without restriction, -including without limitation the rights to use, copy, modify, merge, -publish, distribute, sublicense, and/or sell copies of the Software, -and to permit persons to whom the Software is furnished to do so, -subject to the following conditions: +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: -The above copyright notice and this permission notice shall be included -in all copies or substantial portions of the Software. +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES -OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, -DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT -OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE -OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/vendor/github.com/stretchr/testify/assert/assertion_format.go b/vendor/github.com/stretchr/testify/assert/assertion_format.go index 23838c4c..aa1c2b95 100644 --- a/vendor/github.com/stretchr/testify/assert/assertion_format.go +++ b/vendor/github.com/stretchr/testify/assert/assertion_format.go @@ -13,6 +13,9 @@ import ( // Conditionf uses a Comparison to assert a complex condition. func Conditionf(t TestingT, comp Comparison, msg string, args ...interface{}) bool { + if h, ok := t.(tHelper); ok { + h.Helper() + } return Condition(t, comp, append([]interface{}{msg}, args...)...) } @@ -22,19 +25,41 @@ func Conditionf(t TestingT, comp Comparison, msg string, args ...interface{}) bo // assert.Containsf(t, "Hello World", "World", "error message %s", "formatted") // assert.Containsf(t, ["Hello", "World"], "World", "error message %s", "formatted") // assert.Containsf(t, {"Hello": "World"}, "Hello", "error message %s", "formatted") -// -// Returns whether the assertion was successful (true) or not (false). func Containsf(t TestingT, s interface{}, contains interface{}, msg string, args ...interface{}) bool { + if h, ok := t.(tHelper); ok { + h.Helper() + } return Contains(t, s, contains, append([]interface{}{msg}, args...)...) } +// DirExistsf checks whether a directory exists in the given path. It also fails if the path is a file rather a directory or there is an error checking whether it exists. +func DirExistsf(t TestingT, path string, msg string, args ...interface{}) bool { + if h, ok := t.(tHelper); ok { + h.Helper() + } + return DirExists(t, path, append([]interface{}{msg}, args...)...) +} + +// ElementsMatchf asserts that the specified listA(array, slice...) is equal to specified +// listB(array, slice...) ignoring the order of the elements. If there are duplicate elements, +// the number of appearances of each of them in both lists should match. +// +// assert.ElementsMatchf(t, [1, 3, 2, 3], [1, 3, 3, 2], "error message %s", "formatted") +func ElementsMatchf(t TestingT, listA interface{}, listB interface{}, msg string, args ...interface{}) bool { + if h, ok := t.(tHelper); ok { + h.Helper() + } + return ElementsMatch(t, listA, listB, append([]interface{}{msg}, args...)...) +} + // Emptyf asserts that the specified object is empty. I.e. nil, "", false, 0 or either // a slice or a channel with len == 0. // // assert.Emptyf(t, obj, "error message %s", "formatted") -// -// Returns whether the assertion was successful (true) or not (false). func Emptyf(t TestingT, object interface{}, msg string, args ...interface{}) bool { + if h, ok := t.(tHelper); ok { + h.Helper() + } return Empty(t, object, append([]interface{}{msg}, args...)...) } @@ -42,12 +67,13 @@ func Emptyf(t TestingT, object interface{}, msg string, args ...interface{}) boo // // assert.Equalf(t, 123, 123, "error message %s", "formatted") // -// Returns whether the assertion was successful (true) or not (false). -// // Pointer variable equality is determined based on the equality of the // referenced values (as opposed to the memory addresses). Function equality // cannot be determined and will always fail. func Equalf(t TestingT, expected interface{}, actual interface{}, msg string, args ...interface{}) bool { + if h, ok := t.(tHelper); ok { + h.Helper() + } return Equal(t, expected, actual, append([]interface{}{msg}, args...)...) } @@ -56,9 +82,10 @@ func Equalf(t TestingT, expected interface{}, actual interface{}, msg string, ar // // actualObj, err := SomeFunction() // assert.EqualErrorf(t, err, expectedErrorString, "error message %s", "formatted") -// -// Returns whether the assertion was successful (true) or not (false). func EqualErrorf(t TestingT, theError error, errString string, msg string, args ...interface{}) bool { + if h, ok := t.(tHelper); ok { + h.Helper() + } return EqualError(t, theError, errString, append([]interface{}{msg}, args...)...) } @@ -66,9 +93,10 @@ func EqualErrorf(t TestingT, theError error, errString string, msg string, args // and equal. // // assert.EqualValuesf(t, uint32(123, "error message %s", "formatted"), int32(123)) -// -// Returns whether the assertion was successful (true) or not (false). func EqualValuesf(t TestingT, expected interface{}, actual interface{}, msg string, args ...interface{}) bool { + if h, ok := t.(tHelper); ok { + h.Helper() + } return EqualValues(t, expected, actual, append([]interface{}{msg}, args...)...) } @@ -78,58 +106,81 @@ func EqualValuesf(t TestingT, expected interface{}, actual interface{}, msg stri // if assert.Errorf(t, err, "error message %s", "formatted") { // assert.Equal(t, expectedErrorf, err) // } -// -// Returns whether the assertion was successful (true) or not (false). func Errorf(t TestingT, err error, msg string, args ...interface{}) bool { + if h, ok := t.(tHelper); ok { + h.Helper() + } return Error(t, err, append([]interface{}{msg}, args...)...) } -// Exactlyf asserts that two objects are equal is value and type. +// Exactlyf asserts that two objects are equal in value and type. // // assert.Exactlyf(t, int32(123, "error message %s", "formatted"), int64(123)) -// -// Returns whether the assertion was successful (true) or not (false). func Exactlyf(t TestingT, expected interface{}, actual interface{}, msg string, args ...interface{}) bool { + if h, ok := t.(tHelper); ok { + h.Helper() + } return Exactly(t, expected, actual, append([]interface{}{msg}, args...)...) } // Failf reports a failure through func Failf(t TestingT, failureMessage string, msg string, args ...interface{}) bool { + if h, ok := t.(tHelper); ok { + h.Helper() + } return Fail(t, failureMessage, append([]interface{}{msg}, args...)...) } // FailNowf fails test func FailNowf(t TestingT, failureMessage string, msg string, args ...interface{}) bool { + if h, ok := t.(tHelper); ok { + h.Helper() + } return FailNow(t, failureMessage, append([]interface{}{msg}, args...)...) } // Falsef asserts that the specified value is false. // // assert.Falsef(t, myBool, "error message %s", "formatted") -// -// Returns whether the assertion was successful (true) or not (false). func Falsef(t TestingT, value bool, msg string, args ...interface{}) bool { + if h, ok := t.(tHelper); ok { + h.Helper() + } return False(t, value, append([]interface{}{msg}, args...)...) } +// FileExistsf checks whether a file exists in the given path. It also fails if the path points to a directory or there is an error when trying to check the file. +func FileExistsf(t TestingT, path string, msg string, args ...interface{}) bool { + if h, ok := t.(tHelper); ok { + h.Helper() + } + return FileExists(t, path, append([]interface{}{msg}, args...)...) +} + // HTTPBodyContainsf asserts that a specified handler returns a // body that contains a string. // -// assert.HTTPBodyContainsf(t, myHandler, "www.google.com", nil, "I'm Feeling Lucky", "error message %s", "formatted") +// assert.HTTPBodyContainsf(t, myHandler, "GET", "www.google.com", nil, "I'm Feeling Lucky", "error message %s", "formatted") // // Returns whether the assertion was successful (true) or not (false). -func HTTPBodyContainsf(t TestingT, handler http.HandlerFunc, method string, url string, values url.Values, str interface{}) bool { - return HTTPBodyContains(t, handler, method, url, values, str) +func HTTPBodyContainsf(t TestingT, handler http.HandlerFunc, method string, url string, values url.Values, str interface{}, msg string, args ...interface{}) bool { + if h, ok := t.(tHelper); ok { + h.Helper() + } + return HTTPBodyContains(t, handler, method, url, values, str, append([]interface{}{msg}, args...)...) } // HTTPBodyNotContainsf asserts that a specified handler returns a // body that does not contain a string. // -// assert.HTTPBodyNotContainsf(t, myHandler, "www.google.com", nil, "I'm Feeling Lucky", "error message %s", "formatted") +// assert.HTTPBodyNotContainsf(t, myHandler, "GET", "www.google.com", nil, "I'm Feeling Lucky", "error message %s", "formatted") // // Returns whether the assertion was successful (true) or not (false). -func HTTPBodyNotContainsf(t TestingT, handler http.HandlerFunc, method string, url string, values url.Values, str interface{}) bool { - return HTTPBodyNotContains(t, handler, method, url, values, str) +func HTTPBodyNotContainsf(t TestingT, handler http.HandlerFunc, method string, url string, values url.Values, str interface{}, msg string, args ...interface{}) bool { + if h, ok := t.(tHelper); ok { + h.Helper() + } + return HTTPBodyNotContains(t, handler, method, url, values, str, append([]interface{}{msg}, args...)...) } // HTTPErrorf asserts that a specified handler returns an error status code. @@ -137,8 +188,11 @@ func HTTPBodyNotContainsf(t TestingT, handler http.HandlerFunc, method string, u // assert.HTTPErrorf(t, myHandler, "POST", "/a/b/c", url.Values{"a": []string{"b", "c"}} // // Returns whether the assertion was successful (true, "error message %s", "formatted") or not (false). -func HTTPErrorf(t TestingT, handler http.HandlerFunc, method string, url string, values url.Values) bool { - return HTTPError(t, handler, method, url, values) +func HTTPErrorf(t TestingT, handler http.HandlerFunc, method string, url string, values url.Values, msg string, args ...interface{}) bool { + if h, ok := t.(tHelper); ok { + h.Helper() + } + return HTTPError(t, handler, method, url, values, append([]interface{}{msg}, args...)...) } // HTTPRedirectf asserts that a specified handler returns a redirect status code. @@ -146,8 +200,11 @@ func HTTPErrorf(t TestingT, handler http.HandlerFunc, method string, url string, // assert.HTTPRedirectf(t, myHandler, "GET", "/a/b/c", url.Values{"a": []string{"b", "c"}} // // Returns whether the assertion was successful (true, "error message %s", "formatted") or not (false). -func HTTPRedirectf(t TestingT, handler http.HandlerFunc, method string, url string, values url.Values) bool { - return HTTPRedirect(t, handler, method, url, values) +func HTTPRedirectf(t TestingT, handler http.HandlerFunc, method string, url string, values url.Values, msg string, args ...interface{}) bool { + if h, ok := t.(tHelper); ok { + h.Helper() + } + return HTTPRedirect(t, handler, method, url, values, append([]interface{}{msg}, args...)...) } // HTTPSuccessf asserts that a specified handler returns a success status code. @@ -155,54 +212,80 @@ func HTTPRedirectf(t TestingT, handler http.HandlerFunc, method string, url stri // assert.HTTPSuccessf(t, myHandler, "POST", "http://www.google.com", nil, "error message %s", "formatted") // // Returns whether the assertion was successful (true) or not (false). -func HTTPSuccessf(t TestingT, handler http.HandlerFunc, method string, url string, values url.Values) bool { - return HTTPSuccess(t, handler, method, url, values) +func HTTPSuccessf(t TestingT, handler http.HandlerFunc, method string, url string, values url.Values, msg string, args ...interface{}) bool { + if h, ok := t.(tHelper); ok { + h.Helper() + } + return HTTPSuccess(t, handler, method, url, values, append([]interface{}{msg}, args...)...) } // Implementsf asserts that an object is implemented by the specified interface. // // assert.Implementsf(t, (*MyInterface, "error message %s", "formatted")(nil), new(MyObject)) func Implementsf(t TestingT, interfaceObject interface{}, object interface{}, msg string, args ...interface{}) bool { + if h, ok := t.(tHelper); ok { + h.Helper() + } return Implements(t, interfaceObject, object, append([]interface{}{msg}, args...)...) } // InDeltaf asserts that the two numerals are within delta of each other. // // assert.InDeltaf(t, math.Pi, (22 / 7.0, "error message %s", "formatted"), 0.01) -// -// Returns whether the assertion was successful (true) or not (false). func InDeltaf(t TestingT, expected interface{}, actual interface{}, delta float64, msg string, args ...interface{}) bool { + if h, ok := t.(tHelper); ok { + h.Helper() + } return InDelta(t, expected, actual, delta, append([]interface{}{msg}, args...)...) } +// InDeltaMapValuesf is the same as InDelta, but it compares all values between two maps. Both maps must have exactly the same keys. +func InDeltaMapValuesf(t TestingT, expected interface{}, actual interface{}, delta float64, msg string, args ...interface{}) bool { + if h, ok := t.(tHelper); ok { + h.Helper() + } + return InDeltaMapValues(t, expected, actual, delta, append([]interface{}{msg}, args...)...) +} + // InDeltaSlicef is the same as InDelta, except it compares two slices. func InDeltaSlicef(t TestingT, expected interface{}, actual interface{}, delta float64, msg string, args ...interface{}) bool { + if h, ok := t.(tHelper); ok { + h.Helper() + } return InDeltaSlice(t, expected, actual, delta, append([]interface{}{msg}, args...)...) } // InEpsilonf asserts that expected and actual have a relative error less than epsilon -// -// Returns whether the assertion was successful (true) or not (false). func InEpsilonf(t TestingT, expected interface{}, actual interface{}, epsilon float64, msg string, args ...interface{}) bool { + if h, ok := t.(tHelper); ok { + h.Helper() + } return InEpsilon(t, expected, actual, epsilon, append([]interface{}{msg}, args...)...) } // InEpsilonSlicef is the same as InEpsilon, except it compares each value from two slices. func InEpsilonSlicef(t TestingT, expected interface{}, actual interface{}, epsilon float64, msg string, args ...interface{}) bool { + if h, ok := t.(tHelper); ok { + h.Helper() + } return InEpsilonSlice(t, expected, actual, epsilon, append([]interface{}{msg}, args...)...) } // IsTypef asserts that the specified objects are of the same type. func IsTypef(t TestingT, expectedType interface{}, object interface{}, msg string, args ...interface{}) bool { + if h, ok := t.(tHelper); ok { + h.Helper() + } return IsType(t, expectedType, object, append([]interface{}{msg}, args...)...) } // JSONEqf asserts that two JSON strings are equivalent. // // assert.JSONEqf(t, `{"hello": "world", "foo": "bar"}`, `{"foo": "bar", "hello": "world"}`, "error message %s", "formatted") -// -// Returns whether the assertion was successful (true) or not (false). func JSONEqf(t TestingT, expected string, actual string, msg string, args ...interface{}) bool { + if h, ok := t.(tHelper); ok { + h.Helper() + } return JSONEq(t, expected, actual, append([]interface{}{msg}, args...)...) } @@ -210,18 +293,20 @@ func JSONEqf(t TestingT, expected string, actual string, msg string, args ...int // Lenf also fails if the object has a type that len() not accept. // // assert.Lenf(t, mySlice, 3, "error message %s", "formatted") -// -// Returns whether the assertion was successful (true) or not (false). func Lenf(t TestingT, object interface{}, length int, msg string, args ...interface{}) bool { + if h, ok := t.(tHelper); ok { + h.Helper() + } return Len(t, object, length, append([]interface{}{msg}, args...)...) } // Nilf asserts that the specified object is nil. // // assert.Nilf(t, err, "error message %s", "formatted") -// -// Returns whether the assertion was successful (true) or not (false). func Nilf(t TestingT, object interface{}, msg string, args ...interface{}) bool { + if h, ok := t.(tHelper); ok { + h.Helper() + } return Nil(t, object, append([]interface{}{msg}, args...)...) } @@ -231,9 +316,10 @@ func Nilf(t TestingT, object interface{}, msg string, args ...interface{}) bool // if assert.NoErrorf(t, err, "error message %s", "formatted") { // assert.Equal(t, expectedObj, actualObj) // } -// -// Returns whether the assertion was successful (true) or not (false). func NoErrorf(t TestingT, err error, msg string, args ...interface{}) bool { + if h, ok := t.(tHelper); ok { + h.Helper() + } return NoError(t, err, append([]interface{}{msg}, args...)...) } @@ -243,9 +329,10 @@ func NoErrorf(t TestingT, err error, msg string, args ...interface{}) bool { // assert.NotContainsf(t, "Hello World", "Earth", "error message %s", "formatted") // assert.NotContainsf(t, ["Hello", "World"], "Earth", "error message %s", "formatted") // assert.NotContainsf(t, {"Hello": "World"}, "Earth", "error message %s", "formatted") -// -// Returns whether the assertion was successful (true) or not (false). func NotContainsf(t TestingT, s interface{}, contains interface{}, msg string, args ...interface{}) bool { + if h, ok := t.(tHelper); ok { + h.Helper() + } return NotContains(t, s, contains, append([]interface{}{msg}, args...)...) } @@ -255,9 +342,10 @@ func NotContainsf(t TestingT, s interface{}, contains interface{}, msg string, a // if assert.NotEmptyf(t, obj, "error message %s", "formatted") { // assert.Equal(t, "two", obj[1]) // } -// -// Returns whether the assertion was successful (true) or not (false). func NotEmptyf(t TestingT, object interface{}, msg string, args ...interface{}) bool { + if h, ok := t.(tHelper); ok { + h.Helper() + } return NotEmpty(t, object, append([]interface{}{msg}, args...)...) } @@ -265,29 +353,32 @@ func NotEmptyf(t TestingT, object interface{}, msg string, args ...interface{}) // // assert.NotEqualf(t, obj1, obj2, "error message %s", "formatted") // -// Returns whether the assertion was successful (true) or not (false). -// // Pointer variable equality is determined based on the equality of the // referenced values (as opposed to the memory addresses). func NotEqualf(t TestingT, expected interface{}, actual interface{}, msg string, args ...interface{}) bool { + if h, ok := t.(tHelper); ok { + h.Helper() + } return NotEqual(t, expected, actual, append([]interface{}{msg}, args...)...) } // NotNilf asserts that the specified object is not nil. // // assert.NotNilf(t, err, "error message %s", "formatted") -// -// Returns whether the assertion was successful (true) or not (false). func NotNilf(t TestingT, object interface{}, msg string, args ...interface{}) bool { + if h, ok := t.(tHelper); ok { + h.Helper() + } return NotNil(t, object, append([]interface{}{msg}, args...)...) } // NotPanicsf asserts that the code inside the specified PanicTestFunc does NOT panic. // // assert.NotPanicsf(t, func(){ RemainCalm() }, "error message %s", "formatted") -// -// Returns whether the assertion was successful (true) or not (false). func NotPanicsf(t TestingT, f PanicTestFunc, msg string, args ...interface{}) bool { + if h, ok := t.(tHelper); ok { + h.Helper() + } return NotPanics(t, f, append([]interface{}{msg}, args...)...) } @@ -295,9 +386,10 @@ func NotPanicsf(t TestingT, f PanicTestFunc, msg string, args ...interface{}) bo // // assert.NotRegexpf(t, regexp.MustCompile("starts", "error message %s", "formatted"), "it's starting") // assert.NotRegexpf(t, "^start", "it's not starting", "error message %s", "formatted") -// -// Returns whether the assertion was successful (true) or not (false). func NotRegexpf(t TestingT, rx interface{}, str interface{}, msg string, args ...interface{}) bool { + if h, ok := t.(tHelper); ok { + h.Helper() + } return NotRegexp(t, rx, str, append([]interface{}{msg}, args...)...) } @@ -305,23 +397,28 @@ func NotRegexpf(t TestingT, rx interface{}, str interface{}, msg string, args .. // elements given in the specified subset(array, slice...). // // assert.NotSubsetf(t, [1, 3, 4], [1, 2], "But [1, 3, 4] does not contain [1, 2]", "error message %s", "formatted") -// -// Returns whether the assertion was successful (true) or not (false). func NotSubsetf(t TestingT, list interface{}, subset interface{}, msg string, args ...interface{}) bool { + if h, ok := t.(tHelper); ok { + h.Helper() + } return NotSubset(t, list, subset, append([]interface{}{msg}, args...)...) } -// NotZerof asserts that i is not the zero value for its type and returns the truth. +// NotZerof asserts that i is not the zero value for its type. func NotZerof(t TestingT, i interface{}, msg string, args ...interface{}) bool { + if h, ok := t.(tHelper); ok { + h.Helper() + } return NotZero(t, i, append([]interface{}{msg}, args...)...) } // Panicsf asserts that the code inside the specified PanicTestFunc panics. // // assert.Panicsf(t, func(){ GoCrazy() }, "error message %s", "formatted") -// -// Returns whether the assertion was successful (true) or not (false). func Panicsf(t TestingT, f PanicTestFunc, msg string, args ...interface{}) bool { + if h, ok := t.(tHelper); ok { + h.Helper() + } return Panics(t, f, append([]interface{}{msg}, args...)...) } @@ -329,9 +426,10 @@ func Panicsf(t TestingT, f PanicTestFunc, msg string, args ...interface{}) bool // the recovered panic value equals the expected panic value. // // assert.PanicsWithValuef(t, "crazy error", func(){ GoCrazy() }, "error message %s", "formatted") -// -// Returns whether the assertion was successful (true) or not (false). func PanicsWithValuef(t TestingT, expected interface{}, f PanicTestFunc, msg string, args ...interface{}) bool { + if h, ok := t.(tHelper); ok { + h.Helper() + } return PanicsWithValue(t, expected, f, append([]interface{}{msg}, args...)...) } @@ -339,9 +437,10 @@ func PanicsWithValuef(t TestingT, expected interface{}, f PanicTestFunc, msg str // // assert.Regexpf(t, regexp.MustCompile("start", "error message %s", "formatted"), "it's starting") // assert.Regexpf(t, "start...$", "it's not starting", "error message %s", "formatted") -// -// Returns whether the assertion was successful (true) or not (false). func Regexpf(t TestingT, rx interface{}, str interface{}, msg string, args ...interface{}) bool { + if h, ok := t.(tHelper); ok { + h.Helper() + } return Regexp(t, rx, str, append([]interface{}{msg}, args...)...) } @@ -349,31 +448,37 @@ func Regexpf(t TestingT, rx interface{}, str interface{}, msg string, args ...in // elements given in the specified subset(array, slice...). // // assert.Subsetf(t, [1, 2, 3], [1, 2], "But [1, 2, 3] does contain [1, 2]", "error message %s", "formatted") -// -// Returns whether the assertion was successful (true) or not (false). func Subsetf(t TestingT, list interface{}, subset interface{}, msg string, args ...interface{}) bool { + if h, ok := t.(tHelper); ok { + h.Helper() + } return Subset(t, list, subset, append([]interface{}{msg}, args...)...) } // Truef asserts that the specified value is true. // // assert.Truef(t, myBool, "error message %s", "formatted") -// -// Returns whether the assertion was successful (true) or not (false). func Truef(t TestingT, value bool, msg string, args ...interface{}) bool { + if h, ok := t.(tHelper); ok { + h.Helper() + } return True(t, value, append([]interface{}{msg}, args...)...) } // WithinDurationf asserts that the two times are within duration delta of each other. // // assert.WithinDurationf(t, time.Now(), time.Now(), 10*time.Second, "error message %s", "formatted") -// -// Returns whether the assertion was successful (true) or not (false). func WithinDurationf(t TestingT, expected time.Time, actual time.Time, delta time.Duration, msg string, args ...interface{}) bool { + if h, ok := t.(tHelper); ok { + h.Helper() + } return WithinDuration(t, expected, actual, delta, append([]interface{}{msg}, args...)...) } -// Zerof asserts that i is the zero value for its type and returns the truth. +// Zerof asserts that i is the zero value for its type. func Zerof(t TestingT, i interface{}, msg string, args ...interface{}) bool { + if h, ok := t.(tHelper); ok { + h.Helper() + } return Zero(t, i, append([]interface{}{msg}, args...)...) } diff --git a/vendor/github.com/stretchr/testify/assert/assertion_format.go.tmpl b/vendor/github.com/stretchr/testify/assert/assertion_format.go.tmpl index c5cc66f4..d2bb0b81 100644 --- a/vendor/github.com/stretchr/testify/assert/assertion_format.go.tmpl +++ b/vendor/github.com/stretchr/testify/assert/assertion_format.go.tmpl @@ -1,4 +1,5 @@ {{.CommentFormat}} func {{.DocInfo.Name}}f(t TestingT, {{.ParamsFormat}}) bool { + if h, ok := t.(tHelper); ok { h.Helper() } return {{.DocInfo.Name}}(t, {{.ForwardedParamsFormat}}) } diff --git a/vendor/github.com/stretchr/testify/assert/assertion_forward.go b/vendor/github.com/stretchr/testify/assert/assertion_forward.go index fcccbd01..de39f794 100644 --- a/vendor/github.com/stretchr/testify/assert/assertion_forward.go +++ b/vendor/github.com/stretchr/testify/assert/assertion_forward.go @@ -13,11 +13,17 @@ import ( // Condition uses a Comparison to assert a complex condition. func (a *Assertions) Condition(comp Comparison, msgAndArgs ...interface{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } return Condition(a.t, comp, msgAndArgs...) } // Conditionf uses a Comparison to assert a complex condition. func (a *Assertions) Conditionf(comp Comparison, msg string, args ...interface{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } return Conditionf(a.t, comp, msg, args...) } @@ -27,9 +33,10 @@ func (a *Assertions) Conditionf(comp Comparison, msg string, args ...interface{} // a.Contains("Hello World", "World") // a.Contains(["Hello", "World"], "World") // a.Contains({"Hello": "World"}, "Hello") -// -// Returns whether the assertion was successful (true) or not (false). func (a *Assertions) Contains(s interface{}, contains interface{}, msgAndArgs ...interface{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } return Contains(a.t, s, contains, msgAndArgs...) } @@ -39,19 +46,61 @@ func (a *Assertions) Contains(s interface{}, contains interface{}, msgAndArgs .. // a.Containsf("Hello World", "World", "error message %s", "formatted") // a.Containsf(["Hello", "World"], "World", "error message %s", "formatted") // a.Containsf({"Hello": "World"}, "Hello", "error message %s", "formatted") -// -// Returns whether the assertion was successful (true) or not (false). func (a *Assertions) Containsf(s interface{}, contains interface{}, msg string, args ...interface{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } return Containsf(a.t, s, contains, msg, args...) } +// DirExists checks whether a directory exists in the given path. It also fails if the path is a file rather a directory or there is an error checking whether it exists. +func (a *Assertions) DirExists(path string, msgAndArgs ...interface{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + return DirExists(a.t, path, msgAndArgs...) +} + +// DirExistsf checks whether a directory exists in the given path. It also fails if the path is a file rather a directory or there is an error checking whether it exists. +func (a *Assertions) DirExistsf(path string, msg string, args ...interface{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + return DirExistsf(a.t, path, msg, args...) +} + +// ElementsMatch asserts that the specified listA(array, slice...) is equal to specified +// listB(array, slice...) ignoring the order of the elements. If there are duplicate elements, +// the number of appearances of each of them in both lists should match. +// +// a.ElementsMatch([1, 3, 2, 3], [1, 3, 3, 2]) +func (a *Assertions) ElementsMatch(listA interface{}, listB interface{}, msgAndArgs ...interface{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + return ElementsMatch(a.t, listA, listB, msgAndArgs...) +} + +// ElementsMatchf asserts that the specified listA(array, slice...) is equal to specified +// listB(array, slice...) ignoring the order of the elements. If there are duplicate elements, +// the number of appearances of each of them in both lists should match. +// +// a.ElementsMatchf([1, 3, 2, 3], [1, 3, 3, 2], "error message %s", "formatted") +func (a *Assertions) ElementsMatchf(listA interface{}, listB interface{}, msg string, args ...interface{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + return ElementsMatchf(a.t, listA, listB, msg, args...) +} + // Empty asserts that the specified object is empty. I.e. nil, "", false, 0 or either // a slice or a channel with len == 0. // // a.Empty(obj) -// -// Returns whether the assertion was successful (true) or not (false). func (a *Assertions) Empty(object interface{}, msgAndArgs ...interface{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } return Empty(a.t, object, msgAndArgs...) } @@ -59,9 +108,10 @@ func (a *Assertions) Empty(object interface{}, msgAndArgs ...interface{}) bool { // a slice or a channel with len == 0. // // a.Emptyf(obj, "error message %s", "formatted") -// -// Returns whether the assertion was successful (true) or not (false). func (a *Assertions) Emptyf(object interface{}, msg string, args ...interface{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } return Emptyf(a.t, object, msg, args...) } @@ -69,12 +119,13 @@ func (a *Assertions) Emptyf(object interface{}, msg string, args ...interface{}) // // a.Equal(123, 123) // -// Returns whether the assertion was successful (true) or not (false). -// // Pointer variable equality is determined based on the equality of the // referenced values (as opposed to the memory addresses). Function equality // cannot be determined and will always fail. func (a *Assertions) Equal(expected interface{}, actual interface{}, msgAndArgs ...interface{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } return Equal(a.t, expected, actual, msgAndArgs...) } @@ -83,9 +134,10 @@ func (a *Assertions) Equal(expected interface{}, actual interface{}, msgAndArgs // // actualObj, err := SomeFunction() // a.EqualError(err, expectedErrorString) -// -// Returns whether the assertion was successful (true) or not (false). func (a *Assertions) EqualError(theError error, errString string, msgAndArgs ...interface{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } return EqualError(a.t, theError, errString, msgAndArgs...) } @@ -94,9 +146,10 @@ func (a *Assertions) EqualError(theError error, errString string, msgAndArgs ... // // actualObj, err := SomeFunction() // a.EqualErrorf(err, expectedErrorString, "error message %s", "formatted") -// -// Returns whether the assertion was successful (true) or not (false). func (a *Assertions) EqualErrorf(theError error, errString string, msg string, args ...interface{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } return EqualErrorf(a.t, theError, errString, msg, args...) } @@ -104,9 +157,10 @@ func (a *Assertions) EqualErrorf(theError error, errString string, msg string, a // and equal. // // a.EqualValues(uint32(123), int32(123)) -// -// Returns whether the assertion was successful (true) or not (false). func (a *Assertions) EqualValues(expected interface{}, actual interface{}, msgAndArgs ...interface{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } return EqualValues(a.t, expected, actual, msgAndArgs...) } @@ -114,9 +168,10 @@ func (a *Assertions) EqualValues(expected interface{}, actual interface{}, msgAn // and equal. // // a.EqualValuesf(uint32(123, "error message %s", "formatted"), int32(123)) -// -// Returns whether the assertion was successful (true) or not (false). func (a *Assertions) EqualValuesf(expected interface{}, actual interface{}, msg string, args ...interface{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } return EqualValuesf(a.t, expected, actual, msg, args...) } @@ -124,12 +179,13 @@ func (a *Assertions) EqualValuesf(expected interface{}, actual interface{}, msg // // a.Equalf(123, 123, "error message %s", "formatted") // -// Returns whether the assertion was successful (true) or not (false). -// // Pointer variable equality is determined based on the equality of the // referenced values (as opposed to the memory addresses). Function equality // cannot be determined and will always fail. func (a *Assertions) Equalf(expected interface{}, actual interface{}, msg string, args ...interface{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } return Equalf(a.t, expected, actual, msg, args...) } @@ -139,9 +195,10 @@ func (a *Assertions) Equalf(expected interface{}, actual interface{}, msg string // if a.Error(err) { // assert.Equal(t, expectedError, err) // } -// -// Returns whether the assertion was successful (true) or not (false). func (a *Assertions) Error(err error, msgAndArgs ...interface{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } return Error(a.t, err, msgAndArgs...) } @@ -151,106 +208,151 @@ func (a *Assertions) Error(err error, msgAndArgs ...interface{}) bool { // if a.Errorf(err, "error message %s", "formatted") { // assert.Equal(t, expectedErrorf, err) // } -// -// Returns whether the assertion was successful (true) or not (false). func (a *Assertions) Errorf(err error, msg string, args ...interface{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } return Errorf(a.t, err, msg, args...) } -// Exactly asserts that two objects are equal is value and type. +// Exactly asserts that two objects are equal in value and type. // // a.Exactly(int32(123), int64(123)) -// -// Returns whether the assertion was successful (true) or not (false). func (a *Assertions) Exactly(expected interface{}, actual interface{}, msgAndArgs ...interface{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } return Exactly(a.t, expected, actual, msgAndArgs...) } -// Exactlyf asserts that two objects are equal is value and type. +// Exactlyf asserts that two objects are equal in value and type. // // a.Exactlyf(int32(123, "error message %s", "formatted"), int64(123)) -// -// Returns whether the assertion was successful (true) or not (false). func (a *Assertions) Exactlyf(expected interface{}, actual interface{}, msg string, args ...interface{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } return Exactlyf(a.t, expected, actual, msg, args...) } // Fail reports a failure through func (a *Assertions) Fail(failureMessage string, msgAndArgs ...interface{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } return Fail(a.t, failureMessage, msgAndArgs...) } // FailNow fails test func (a *Assertions) FailNow(failureMessage string, msgAndArgs ...interface{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } return FailNow(a.t, failureMessage, msgAndArgs...) } // FailNowf fails test func (a *Assertions) FailNowf(failureMessage string, msg string, args ...interface{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } return FailNowf(a.t, failureMessage, msg, args...) } // Failf reports a failure through func (a *Assertions) Failf(failureMessage string, msg string, args ...interface{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } return Failf(a.t, failureMessage, msg, args...) } // False asserts that the specified value is false. // // a.False(myBool) -// -// Returns whether the assertion was successful (true) or not (false). func (a *Assertions) False(value bool, msgAndArgs ...interface{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } return False(a.t, value, msgAndArgs...) } // Falsef asserts that the specified value is false. // // a.Falsef(myBool, "error message %s", "formatted") -// -// Returns whether the assertion was successful (true) or not (false). func (a *Assertions) Falsef(value bool, msg string, args ...interface{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } return Falsef(a.t, value, msg, args...) } +// FileExists checks whether a file exists in the given path. It also fails if the path points to a directory or there is an error when trying to check the file. +func (a *Assertions) FileExists(path string, msgAndArgs ...interface{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + return FileExists(a.t, path, msgAndArgs...) +} + +// FileExistsf checks whether a file exists in the given path. It also fails if the path points to a directory or there is an error when trying to check the file. +func (a *Assertions) FileExistsf(path string, msg string, args ...interface{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + return FileExistsf(a.t, path, msg, args...) +} + // HTTPBodyContains asserts that a specified handler returns a // body that contains a string. // -// a.HTTPBodyContains(myHandler, "www.google.com", nil, "I'm Feeling Lucky") +// a.HTTPBodyContains(myHandler, "GET", "www.google.com", nil, "I'm Feeling Lucky") // // Returns whether the assertion was successful (true) or not (false). -func (a *Assertions) HTTPBodyContains(handler http.HandlerFunc, method string, url string, values url.Values, str interface{}) bool { - return HTTPBodyContains(a.t, handler, method, url, values, str) +func (a *Assertions) HTTPBodyContains(handler http.HandlerFunc, method string, url string, values url.Values, str interface{}, msgAndArgs ...interface{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + return HTTPBodyContains(a.t, handler, method, url, values, str, msgAndArgs...) } // HTTPBodyContainsf asserts that a specified handler returns a // body that contains a string. // -// a.HTTPBodyContainsf(myHandler, "www.google.com", nil, "I'm Feeling Lucky", "error message %s", "formatted") +// a.HTTPBodyContainsf(myHandler, "GET", "www.google.com", nil, "I'm Feeling Lucky", "error message %s", "formatted") // // Returns whether the assertion was successful (true) or not (false). -func (a *Assertions) HTTPBodyContainsf(handler http.HandlerFunc, method string, url string, values url.Values, str interface{}) bool { - return HTTPBodyContainsf(a.t, handler, method, url, values, str) +func (a *Assertions) HTTPBodyContainsf(handler http.HandlerFunc, method string, url string, values url.Values, str interface{}, msg string, args ...interface{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + return HTTPBodyContainsf(a.t, handler, method, url, values, str, msg, args...) } // HTTPBodyNotContains asserts that a specified handler returns a // body that does not contain a string. // -// a.HTTPBodyNotContains(myHandler, "www.google.com", nil, "I'm Feeling Lucky") +// a.HTTPBodyNotContains(myHandler, "GET", "www.google.com", nil, "I'm Feeling Lucky") // // Returns whether the assertion was successful (true) or not (false). -func (a *Assertions) HTTPBodyNotContains(handler http.HandlerFunc, method string, url string, values url.Values, str interface{}) bool { - return HTTPBodyNotContains(a.t, handler, method, url, values, str) +func (a *Assertions) HTTPBodyNotContains(handler http.HandlerFunc, method string, url string, values url.Values, str interface{}, msgAndArgs ...interface{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + return HTTPBodyNotContains(a.t, handler, method, url, values, str, msgAndArgs...) } // HTTPBodyNotContainsf asserts that a specified handler returns a // body that does not contain a string. // -// a.HTTPBodyNotContainsf(myHandler, "www.google.com", nil, "I'm Feeling Lucky", "error message %s", "formatted") +// a.HTTPBodyNotContainsf(myHandler, "GET", "www.google.com", nil, "I'm Feeling Lucky", "error message %s", "formatted") // // Returns whether the assertion was successful (true) or not (false). -func (a *Assertions) HTTPBodyNotContainsf(handler http.HandlerFunc, method string, url string, values url.Values, str interface{}) bool { - return HTTPBodyNotContainsf(a.t, handler, method, url, values, str) +func (a *Assertions) HTTPBodyNotContainsf(handler http.HandlerFunc, method string, url string, values url.Values, str interface{}, msg string, args ...interface{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + return HTTPBodyNotContainsf(a.t, handler, method, url, values, str, msg, args...) } // HTTPError asserts that a specified handler returns an error status code. @@ -258,8 +360,11 @@ func (a *Assertions) HTTPBodyNotContainsf(handler http.HandlerFunc, method strin // a.HTTPError(myHandler, "POST", "/a/b/c", url.Values{"a": []string{"b", "c"}} // // Returns whether the assertion was successful (true) or not (false). -func (a *Assertions) HTTPError(handler http.HandlerFunc, method string, url string, values url.Values) bool { - return HTTPError(a.t, handler, method, url, values) +func (a *Assertions) HTTPError(handler http.HandlerFunc, method string, url string, values url.Values, msgAndArgs ...interface{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + return HTTPError(a.t, handler, method, url, values, msgAndArgs...) } // HTTPErrorf asserts that a specified handler returns an error status code. @@ -267,8 +372,11 @@ func (a *Assertions) HTTPError(handler http.HandlerFunc, method string, url stri // a.HTTPErrorf(myHandler, "POST", "/a/b/c", url.Values{"a": []string{"b", "c"}} // // Returns whether the assertion was successful (true, "error message %s", "formatted") or not (false). -func (a *Assertions) HTTPErrorf(handler http.HandlerFunc, method string, url string, values url.Values) bool { - return HTTPErrorf(a.t, handler, method, url, values) +func (a *Assertions) HTTPErrorf(handler http.HandlerFunc, method string, url string, values url.Values, msg string, args ...interface{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + return HTTPErrorf(a.t, handler, method, url, values, msg, args...) } // HTTPRedirect asserts that a specified handler returns a redirect status code. @@ -276,8 +384,11 @@ func (a *Assertions) HTTPErrorf(handler http.HandlerFunc, method string, url str // a.HTTPRedirect(myHandler, "GET", "/a/b/c", url.Values{"a": []string{"b", "c"}} // // Returns whether the assertion was successful (true) or not (false). -func (a *Assertions) HTTPRedirect(handler http.HandlerFunc, method string, url string, values url.Values) bool { - return HTTPRedirect(a.t, handler, method, url, values) +func (a *Assertions) HTTPRedirect(handler http.HandlerFunc, method string, url string, values url.Values, msgAndArgs ...interface{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + return HTTPRedirect(a.t, handler, method, url, values, msgAndArgs...) } // HTTPRedirectf asserts that a specified handler returns a redirect status code. @@ -285,8 +396,11 @@ func (a *Assertions) HTTPRedirect(handler http.HandlerFunc, method string, url s // a.HTTPRedirectf(myHandler, "GET", "/a/b/c", url.Values{"a": []string{"b", "c"}} // // Returns whether the assertion was successful (true, "error message %s", "formatted") or not (false). -func (a *Assertions) HTTPRedirectf(handler http.HandlerFunc, method string, url string, values url.Values) bool { - return HTTPRedirectf(a.t, handler, method, url, values) +func (a *Assertions) HTTPRedirectf(handler http.HandlerFunc, method string, url string, values url.Values, msg string, args ...interface{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + return HTTPRedirectf(a.t, handler, method, url, values, msg, args...) } // HTTPSuccess asserts that a specified handler returns a success status code. @@ -294,8 +408,11 @@ func (a *Assertions) HTTPRedirectf(handler http.HandlerFunc, method string, url // a.HTTPSuccess(myHandler, "POST", "http://www.google.com", nil) // // Returns whether the assertion was successful (true) or not (false). -func (a *Assertions) HTTPSuccess(handler http.HandlerFunc, method string, url string, values url.Values) bool { - return HTTPSuccess(a.t, handler, method, url, values) +func (a *Assertions) HTTPSuccess(handler http.HandlerFunc, method string, url string, values url.Values, msgAndArgs ...interface{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + return HTTPSuccess(a.t, handler, method, url, values, msgAndArgs...) } // HTTPSuccessf asserts that a specified handler returns a success status code. @@ -303,14 +420,20 @@ func (a *Assertions) HTTPSuccess(handler http.HandlerFunc, method string, url st // a.HTTPSuccessf(myHandler, "POST", "http://www.google.com", nil, "error message %s", "formatted") // // Returns whether the assertion was successful (true) or not (false). -func (a *Assertions) HTTPSuccessf(handler http.HandlerFunc, method string, url string, values url.Values) bool { - return HTTPSuccessf(a.t, handler, method, url, values) +func (a *Assertions) HTTPSuccessf(handler http.HandlerFunc, method string, url string, values url.Values, msg string, args ...interface{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + return HTTPSuccessf(a.t, handler, method, url, values, msg, args...) } // Implements asserts that an object is implemented by the specified interface. // // a.Implements((*MyInterface)(nil), new(MyObject)) func (a *Assertions) Implements(interfaceObject interface{}, object interface{}, msgAndArgs ...interface{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } return Implements(a.t, interfaceObject, object, msgAndArgs...) } @@ -318,86 +441,129 @@ func (a *Assertions) Implements(interfaceObject interface{}, object interface{}, // // a.Implementsf((*MyInterface, "error message %s", "formatted")(nil), new(MyObject)) func (a *Assertions) Implementsf(interfaceObject interface{}, object interface{}, msg string, args ...interface{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } return Implementsf(a.t, interfaceObject, object, msg, args...) } // InDelta asserts that the two numerals are within delta of each other. // // a.InDelta(math.Pi, (22 / 7.0), 0.01) -// -// Returns whether the assertion was successful (true) or not (false). func (a *Assertions) InDelta(expected interface{}, actual interface{}, delta float64, msgAndArgs ...interface{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } return InDelta(a.t, expected, actual, delta, msgAndArgs...) } +// InDeltaMapValues is the same as InDelta, but it compares all values between two maps. Both maps must have exactly the same keys. +func (a *Assertions) InDeltaMapValues(expected interface{}, actual interface{}, delta float64, msgAndArgs ...interface{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + return InDeltaMapValues(a.t, expected, actual, delta, msgAndArgs...) +} + +// InDeltaMapValuesf is the same as InDelta, but it compares all values between two maps. Both maps must have exactly the same keys. +func (a *Assertions) InDeltaMapValuesf(expected interface{}, actual interface{}, delta float64, msg string, args ...interface{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + return InDeltaMapValuesf(a.t, expected, actual, delta, msg, args...) +} + // InDeltaSlice is the same as InDelta, except it compares two slices. func (a *Assertions) InDeltaSlice(expected interface{}, actual interface{}, delta float64, msgAndArgs ...interface{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } return InDeltaSlice(a.t, expected, actual, delta, msgAndArgs...) } // InDeltaSlicef is the same as InDelta, except it compares two slices. func (a *Assertions) InDeltaSlicef(expected interface{}, actual interface{}, delta float64, msg string, args ...interface{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } return InDeltaSlicef(a.t, expected, actual, delta, msg, args...) } // InDeltaf asserts that the two numerals are within delta of each other. // // a.InDeltaf(math.Pi, (22 / 7.0, "error message %s", "formatted"), 0.01) -// -// Returns whether the assertion was successful (true) or not (false). func (a *Assertions) InDeltaf(expected interface{}, actual interface{}, delta float64, msg string, args ...interface{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } return InDeltaf(a.t, expected, actual, delta, msg, args...) } // InEpsilon asserts that expected and actual have a relative error less than epsilon -// -// Returns whether the assertion was successful (true) or not (false). func (a *Assertions) InEpsilon(expected interface{}, actual interface{}, epsilon float64, msgAndArgs ...interface{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } return InEpsilon(a.t, expected, actual, epsilon, msgAndArgs...) } // InEpsilonSlice is the same as InEpsilon, except it compares each value from two slices. func (a *Assertions) InEpsilonSlice(expected interface{}, actual interface{}, epsilon float64, msgAndArgs ...interface{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } return InEpsilonSlice(a.t, expected, actual, epsilon, msgAndArgs...) } // InEpsilonSlicef is the same as InEpsilon, except it compares each value from two slices. func (a *Assertions) InEpsilonSlicef(expected interface{}, actual interface{}, epsilon float64, msg string, args ...interface{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } return InEpsilonSlicef(a.t, expected, actual, epsilon, msg, args...) } // InEpsilonf asserts that expected and actual have a relative error less than epsilon -// -// Returns whether the assertion was successful (true) or not (false). func (a *Assertions) InEpsilonf(expected interface{}, actual interface{}, epsilon float64, msg string, args ...interface{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } return InEpsilonf(a.t, expected, actual, epsilon, msg, args...) } // IsType asserts that the specified objects are of the same type. func (a *Assertions) IsType(expectedType interface{}, object interface{}, msgAndArgs ...interface{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } return IsType(a.t, expectedType, object, msgAndArgs...) } // IsTypef asserts that the specified objects are of the same type. func (a *Assertions) IsTypef(expectedType interface{}, object interface{}, msg string, args ...interface{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } return IsTypef(a.t, expectedType, object, msg, args...) } // JSONEq asserts that two JSON strings are equivalent. // // a.JSONEq(`{"hello": "world", "foo": "bar"}`, `{"foo": "bar", "hello": "world"}`) -// -// Returns whether the assertion was successful (true) or not (false). func (a *Assertions) JSONEq(expected string, actual string, msgAndArgs ...interface{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } return JSONEq(a.t, expected, actual, msgAndArgs...) } // JSONEqf asserts that two JSON strings are equivalent. // // a.JSONEqf(`{"hello": "world", "foo": "bar"}`, `{"foo": "bar", "hello": "world"}`, "error message %s", "formatted") -// -// Returns whether the assertion was successful (true) or not (false). func (a *Assertions) JSONEqf(expected string, actual string, msg string, args ...interface{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } return JSONEqf(a.t, expected, actual, msg, args...) } @@ -405,9 +571,10 @@ func (a *Assertions) JSONEqf(expected string, actual string, msg string, args .. // Len also fails if the object has a type that len() not accept. // // a.Len(mySlice, 3) -// -// Returns whether the assertion was successful (true) or not (false). func (a *Assertions) Len(object interface{}, length int, msgAndArgs ...interface{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } return Len(a.t, object, length, msgAndArgs...) } @@ -415,27 +582,30 @@ func (a *Assertions) Len(object interface{}, length int, msgAndArgs ...interface // Lenf also fails if the object has a type that len() not accept. // // a.Lenf(mySlice, 3, "error message %s", "formatted") -// -// Returns whether the assertion was successful (true) or not (false). func (a *Assertions) Lenf(object interface{}, length int, msg string, args ...interface{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } return Lenf(a.t, object, length, msg, args...) } // Nil asserts that the specified object is nil. // // a.Nil(err) -// -// Returns whether the assertion was successful (true) or not (false). func (a *Assertions) Nil(object interface{}, msgAndArgs ...interface{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } return Nil(a.t, object, msgAndArgs...) } // Nilf asserts that the specified object is nil. // // a.Nilf(err, "error message %s", "formatted") -// -// Returns whether the assertion was successful (true) or not (false). func (a *Assertions) Nilf(object interface{}, msg string, args ...interface{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } return Nilf(a.t, object, msg, args...) } @@ -445,9 +615,10 @@ func (a *Assertions) Nilf(object interface{}, msg string, args ...interface{}) b // if a.NoError(err) { // assert.Equal(t, expectedObj, actualObj) // } -// -// Returns whether the assertion was successful (true) or not (false). func (a *Assertions) NoError(err error, msgAndArgs ...interface{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } return NoError(a.t, err, msgAndArgs...) } @@ -457,9 +628,10 @@ func (a *Assertions) NoError(err error, msgAndArgs ...interface{}) bool { // if a.NoErrorf(err, "error message %s", "formatted") { // assert.Equal(t, expectedObj, actualObj) // } -// -// Returns whether the assertion was successful (true) or not (false). func (a *Assertions) NoErrorf(err error, msg string, args ...interface{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } return NoErrorf(a.t, err, msg, args...) } @@ -469,9 +641,10 @@ func (a *Assertions) NoErrorf(err error, msg string, args ...interface{}) bool { // a.NotContains("Hello World", "Earth") // a.NotContains(["Hello", "World"], "Earth") // a.NotContains({"Hello": "World"}, "Earth") -// -// Returns whether the assertion was successful (true) or not (false). func (a *Assertions) NotContains(s interface{}, contains interface{}, msgAndArgs ...interface{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } return NotContains(a.t, s, contains, msgAndArgs...) } @@ -481,9 +654,10 @@ func (a *Assertions) NotContains(s interface{}, contains interface{}, msgAndArgs // a.NotContainsf("Hello World", "Earth", "error message %s", "formatted") // a.NotContainsf(["Hello", "World"], "Earth", "error message %s", "formatted") // a.NotContainsf({"Hello": "World"}, "Earth", "error message %s", "formatted") -// -// Returns whether the assertion was successful (true) or not (false). func (a *Assertions) NotContainsf(s interface{}, contains interface{}, msg string, args ...interface{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } return NotContainsf(a.t, s, contains, msg, args...) } @@ -493,9 +667,10 @@ func (a *Assertions) NotContainsf(s interface{}, contains interface{}, msg strin // if a.NotEmpty(obj) { // assert.Equal(t, "two", obj[1]) // } -// -// Returns whether the assertion was successful (true) or not (false). func (a *Assertions) NotEmpty(object interface{}, msgAndArgs ...interface{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } return NotEmpty(a.t, object, msgAndArgs...) } @@ -505,9 +680,10 @@ func (a *Assertions) NotEmpty(object interface{}, msgAndArgs ...interface{}) boo // if a.NotEmptyf(obj, "error message %s", "formatted") { // assert.Equal(t, "two", obj[1]) // } -// -// Returns whether the assertion was successful (true) or not (false). func (a *Assertions) NotEmptyf(object interface{}, msg string, args ...interface{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } return NotEmptyf(a.t, object, msg, args...) } @@ -515,11 +691,12 @@ func (a *Assertions) NotEmptyf(object interface{}, msg string, args ...interface // // a.NotEqual(obj1, obj2) // -// Returns whether the assertion was successful (true) or not (false). -// // Pointer variable equality is determined based on the equality of the // referenced values (as opposed to the memory addresses). func (a *Assertions) NotEqual(expected interface{}, actual interface{}, msgAndArgs ...interface{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } return NotEqual(a.t, expected, actual, msgAndArgs...) } @@ -527,47 +704,52 @@ func (a *Assertions) NotEqual(expected interface{}, actual interface{}, msgAndAr // // a.NotEqualf(obj1, obj2, "error message %s", "formatted") // -// Returns whether the assertion was successful (true) or not (false). -// // Pointer variable equality is determined based on the equality of the // referenced values (as opposed to the memory addresses). func (a *Assertions) NotEqualf(expected interface{}, actual interface{}, msg string, args ...interface{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } return NotEqualf(a.t, expected, actual, msg, args...) } // NotNil asserts that the specified object is not nil. // // a.NotNil(err) -// -// Returns whether the assertion was successful (true) or not (false). func (a *Assertions) NotNil(object interface{}, msgAndArgs ...interface{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } return NotNil(a.t, object, msgAndArgs...) } // NotNilf asserts that the specified object is not nil. // // a.NotNilf(err, "error message %s", "formatted") -// -// Returns whether the assertion was successful (true) or not (false). func (a *Assertions) NotNilf(object interface{}, msg string, args ...interface{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } return NotNilf(a.t, object, msg, args...) } // NotPanics asserts that the code inside the specified PanicTestFunc does NOT panic. // // a.NotPanics(func(){ RemainCalm() }) -// -// Returns whether the assertion was successful (true) or not (false). func (a *Assertions) NotPanics(f PanicTestFunc, msgAndArgs ...interface{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } return NotPanics(a.t, f, msgAndArgs...) } // NotPanicsf asserts that the code inside the specified PanicTestFunc does NOT panic. // // a.NotPanicsf(func(){ RemainCalm() }, "error message %s", "formatted") -// -// Returns whether the assertion was successful (true) or not (false). func (a *Assertions) NotPanicsf(f PanicTestFunc, msg string, args ...interface{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } return NotPanicsf(a.t, f, msg, args...) } @@ -575,9 +757,10 @@ func (a *Assertions) NotPanicsf(f PanicTestFunc, msg string, args ...interface{} // // a.NotRegexp(regexp.MustCompile("starts"), "it's starting") // a.NotRegexp("^start", "it's not starting") -// -// Returns whether the assertion was successful (true) or not (false). func (a *Assertions) NotRegexp(rx interface{}, str interface{}, msgAndArgs ...interface{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } return NotRegexp(a.t, rx, str, msgAndArgs...) } @@ -585,9 +768,10 @@ func (a *Assertions) NotRegexp(rx interface{}, str interface{}, msgAndArgs ...in // // a.NotRegexpf(regexp.MustCompile("starts", "error message %s", "formatted"), "it's starting") // a.NotRegexpf("^start", "it's not starting", "error message %s", "formatted") -// -// Returns whether the assertion was successful (true) or not (false). func (a *Assertions) NotRegexpf(rx interface{}, str interface{}, msg string, args ...interface{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } return NotRegexpf(a.t, rx, str, msg, args...) } @@ -595,9 +779,10 @@ func (a *Assertions) NotRegexpf(rx interface{}, str interface{}, msg string, arg // elements given in the specified subset(array, slice...). // // a.NotSubset([1, 3, 4], [1, 2], "But [1, 3, 4] does not contain [1, 2]") -// -// Returns whether the assertion was successful (true) or not (false). func (a *Assertions) NotSubset(list interface{}, subset interface{}, msgAndArgs ...interface{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } return NotSubset(a.t, list, subset, msgAndArgs...) } @@ -605,28 +790,36 @@ func (a *Assertions) NotSubset(list interface{}, subset interface{}, msgAndArgs // elements given in the specified subset(array, slice...). // // a.NotSubsetf([1, 3, 4], [1, 2], "But [1, 3, 4] does not contain [1, 2]", "error message %s", "formatted") -// -// Returns whether the assertion was successful (true) or not (false). func (a *Assertions) NotSubsetf(list interface{}, subset interface{}, msg string, args ...interface{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } return NotSubsetf(a.t, list, subset, msg, args...) } -// NotZero asserts that i is not the zero value for its type and returns the truth. +// NotZero asserts that i is not the zero value for its type. func (a *Assertions) NotZero(i interface{}, msgAndArgs ...interface{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } return NotZero(a.t, i, msgAndArgs...) } -// NotZerof asserts that i is not the zero value for its type and returns the truth. +// NotZerof asserts that i is not the zero value for its type. func (a *Assertions) NotZerof(i interface{}, msg string, args ...interface{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } return NotZerof(a.t, i, msg, args...) } // Panics asserts that the code inside the specified PanicTestFunc panics. // // a.Panics(func(){ GoCrazy() }) -// -// Returns whether the assertion was successful (true) or not (false). func (a *Assertions) Panics(f PanicTestFunc, msgAndArgs ...interface{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } return Panics(a.t, f, msgAndArgs...) } @@ -634,9 +827,10 @@ func (a *Assertions) Panics(f PanicTestFunc, msgAndArgs ...interface{}) bool { // the recovered panic value equals the expected panic value. // // a.PanicsWithValue("crazy error", func(){ GoCrazy() }) -// -// Returns whether the assertion was successful (true) or not (false). func (a *Assertions) PanicsWithValue(expected interface{}, f PanicTestFunc, msgAndArgs ...interface{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } return PanicsWithValue(a.t, expected, f, msgAndArgs...) } @@ -644,18 +838,20 @@ func (a *Assertions) PanicsWithValue(expected interface{}, f PanicTestFunc, msgA // the recovered panic value equals the expected panic value. // // a.PanicsWithValuef("crazy error", func(){ GoCrazy() }, "error message %s", "formatted") -// -// Returns whether the assertion was successful (true) or not (false). func (a *Assertions) PanicsWithValuef(expected interface{}, f PanicTestFunc, msg string, args ...interface{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } return PanicsWithValuef(a.t, expected, f, msg, args...) } // Panicsf asserts that the code inside the specified PanicTestFunc panics. // // a.Panicsf(func(){ GoCrazy() }, "error message %s", "formatted") -// -// Returns whether the assertion was successful (true) or not (false). func (a *Assertions) Panicsf(f PanicTestFunc, msg string, args ...interface{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } return Panicsf(a.t, f, msg, args...) } @@ -663,9 +859,10 @@ func (a *Assertions) Panicsf(f PanicTestFunc, msg string, args ...interface{}) b // // a.Regexp(regexp.MustCompile("start"), "it's starting") // a.Regexp("start...$", "it's not starting") -// -// Returns whether the assertion was successful (true) or not (false). func (a *Assertions) Regexp(rx interface{}, str interface{}, msgAndArgs ...interface{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } return Regexp(a.t, rx, str, msgAndArgs...) } @@ -673,9 +870,10 @@ func (a *Assertions) Regexp(rx interface{}, str interface{}, msgAndArgs ...inter // // a.Regexpf(regexp.MustCompile("start", "error message %s", "formatted"), "it's starting") // a.Regexpf("start...$", "it's not starting", "error message %s", "formatted") -// -// Returns whether the assertion was successful (true) or not (false). func (a *Assertions) Regexpf(rx interface{}, str interface{}, msg string, args ...interface{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } return Regexpf(a.t, rx, str, msg, args...) } @@ -683,9 +881,10 @@ func (a *Assertions) Regexpf(rx interface{}, str interface{}, msg string, args . // elements given in the specified subset(array, slice...). // // a.Subset([1, 2, 3], [1, 2], "But [1, 2, 3] does contain [1, 2]") -// -// Returns whether the assertion was successful (true) or not (false). func (a *Assertions) Subset(list interface{}, subset interface{}, msgAndArgs ...interface{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } return Subset(a.t, list, subset, msgAndArgs...) } @@ -693,54 +892,65 @@ func (a *Assertions) Subset(list interface{}, subset interface{}, msgAndArgs ... // elements given in the specified subset(array, slice...). // // a.Subsetf([1, 2, 3], [1, 2], "But [1, 2, 3] does contain [1, 2]", "error message %s", "formatted") -// -// Returns whether the assertion was successful (true) or not (false). func (a *Assertions) Subsetf(list interface{}, subset interface{}, msg string, args ...interface{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } return Subsetf(a.t, list, subset, msg, args...) } // True asserts that the specified value is true. // // a.True(myBool) -// -// Returns whether the assertion was successful (true) or not (false). func (a *Assertions) True(value bool, msgAndArgs ...interface{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } return True(a.t, value, msgAndArgs...) } // Truef asserts that the specified value is true. // // a.Truef(myBool, "error message %s", "formatted") -// -// Returns whether the assertion was successful (true) or not (false). func (a *Assertions) Truef(value bool, msg string, args ...interface{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } return Truef(a.t, value, msg, args...) } // WithinDuration asserts that the two times are within duration delta of each other. // // a.WithinDuration(time.Now(), time.Now(), 10*time.Second) -// -// Returns whether the assertion was successful (true) or not (false). func (a *Assertions) WithinDuration(expected time.Time, actual time.Time, delta time.Duration, msgAndArgs ...interface{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } return WithinDuration(a.t, expected, actual, delta, msgAndArgs...) } // WithinDurationf asserts that the two times are within duration delta of each other. // // a.WithinDurationf(time.Now(), time.Now(), 10*time.Second, "error message %s", "formatted") -// -// Returns whether the assertion was successful (true) or not (false). func (a *Assertions) WithinDurationf(expected time.Time, actual time.Time, delta time.Duration, msg string, args ...interface{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } return WithinDurationf(a.t, expected, actual, delta, msg, args...) } -// Zero asserts that i is the zero value for its type and returns the truth. +// Zero asserts that i is the zero value for its type. func (a *Assertions) Zero(i interface{}, msgAndArgs ...interface{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } return Zero(a.t, i, msgAndArgs...) } -// Zerof asserts that i is the zero value for its type and returns the truth. +// Zerof asserts that i is the zero value for its type. func (a *Assertions) Zerof(i interface{}, msg string, args ...interface{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } return Zerof(a.t, i, msg, args...) } diff --git a/vendor/github.com/stretchr/testify/assert/assertion_forward.go.tmpl b/vendor/github.com/stretchr/testify/assert/assertion_forward.go.tmpl index 99f9acfb..188bb9e1 100644 --- a/vendor/github.com/stretchr/testify/assert/assertion_forward.go.tmpl +++ b/vendor/github.com/stretchr/testify/assert/assertion_forward.go.tmpl @@ -1,4 +1,5 @@ {{.CommentWithoutT "a"}} func (a *Assertions) {{.DocInfo.Name}}({{.Params}}) bool { + if h, ok := a.t.(tHelper); ok { h.Helper() } return {{.DocInfo.Name}}(a.t, {{.ForwardedParams}}) } diff --git a/vendor/github.com/stretchr/testify/assert/assertions.go b/vendor/github.com/stretchr/testify/assert/assertions.go index 82590507..9bd4a80e 100644 --- a/vendor/github.com/stretchr/testify/assert/assertions.go +++ b/vendor/github.com/stretchr/testify/assert/assertions.go @@ -7,6 +7,7 @@ import ( "errors" "fmt" "math" + "os" "reflect" "regexp" "runtime" @@ -26,6 +27,22 @@ type TestingT interface { Errorf(format string, args ...interface{}) } +// ComparisonAssertionFunc is a common function prototype when comparing two values. Can be useful +// for table driven tests. +type ComparisonAssertionFunc func(TestingT, interface{}, interface{}, ...interface{}) bool + +// ValueAssertionFunc is a common function prototype when validating a single value. Can be useful +// for table driven tests. +type ValueAssertionFunc func(TestingT, interface{}, ...interface{}) bool + +// BoolAssertionFunc is a common function prototype when validating a bool value. Can be useful +// for table driven tests. +type BoolAssertionFunc func(TestingT, bool, ...interface{}) bool + +// ErrorAssertionFunc is a common function prototype when validating an error value. Can be useful +// for table driven tests. +type ErrorAssertionFunc func(TestingT, error, ...interface{}) bool + // Comparison a custom function that returns true on success and false on failure type Comparison func() (success bool) @@ -37,21 +54,23 @@ type Comparison func() (success bool) // // This function does no assertion of any kind. func ObjectsAreEqual(expected, actual interface{}) bool { - if expected == nil || actual == nil { return expected == actual } - if exp, ok := expected.([]byte); ok { - act, ok := actual.([]byte) - if !ok { - return false - } else if exp == nil || act == nil { - return exp == nil && act == nil - } - return bytes.Equal(exp, act) + + exp, ok := expected.([]byte) + if !ok { + return reflect.DeepEqual(expected, actual) } - return reflect.DeepEqual(expected, actual) + act, ok := actual.([]byte) + if !ok { + return false + } + if exp == nil || act == nil { + return exp == nil && act == nil + } + return bytes.Equal(exp, act) } // ObjectsAreEqualValues gets whether two objects are equal, or if their @@ -155,27 +174,16 @@ func isTest(name, prefix string) bool { return !unicode.IsLower(rune) } -// getWhitespaceString returns a string that is long enough to overwrite the default -// output from the go testing framework. -func getWhitespaceString() string { - - _, file, line, ok := runtime.Caller(1) - if !ok { - return "" - } - parts := strings.Split(file, "/") - file = parts[len(parts)-1] - - return strings.Repeat(" ", len(fmt.Sprintf("%s:%d: ", file, line))) - -} - func messageFromMsgAndArgs(msgAndArgs ...interface{}) string { if len(msgAndArgs) == 0 || msgAndArgs == nil { return "" } if len(msgAndArgs) == 1 { - return msgAndArgs[0].(string) + msg := msgAndArgs[0] + if msgAsStr, ok := msg.(string); ok { + return msgAsStr + } + return fmt.Sprintf("%+v", msg) } if len(msgAndArgs) > 1 { return fmt.Sprintf(msgAndArgs[0].(string), msgAndArgs[1:]...) @@ -194,7 +202,7 @@ func indentMessageLines(message string, longestLabelLen int) string { // no need to align first line because it starts at the correct location (after the label) if i != 0 { // append alignLen+1 spaces to align with "{{longestLabel}}:" before adding tab - outBuf.WriteString("\n\r\t" + strings.Repeat(" ", longestLabelLen+1) + "\t") + outBuf.WriteString("\n\t" + strings.Repeat(" ", longestLabelLen+1) + "\t") } outBuf.WriteString(scanner.Text()) } @@ -208,6 +216,9 @@ type failNower interface { // FailNow fails test func FailNow(t TestingT, failureMessage string, msgAndArgs ...interface{}) bool { + if h, ok := t.(tHelper); ok { + h.Helper() + } Fail(t, failureMessage, msgAndArgs...) // We cannot extend TestingT with FailNow() and @@ -226,17 +237,27 @@ func FailNow(t TestingT, failureMessage string, msgAndArgs ...interface{}) bool // Fail reports a failure through func Fail(t TestingT, failureMessage string, msgAndArgs ...interface{}) bool { + if h, ok := t.(tHelper); ok { + h.Helper() + } content := []labeledContent{ - {"Error Trace", strings.Join(CallerInfo(), "\n\r\t\t\t")}, + {"Error Trace", strings.Join(CallerInfo(), "\n\t\t\t")}, {"Error", failureMessage}, } + // Add test name if the Go version supports it + if n, ok := t.(interface { + Name() string + }); ok { + content = append(content, labeledContent{"Test", n.Name()}) + } + message := messageFromMsgAndArgs(msgAndArgs...) if len(message) > 0 { content = append(content, labeledContent{"Messages", message}) } - t.Errorf("%s", "\r"+getWhitespaceString()+labeledOutput(content...)) + t.Errorf("\n%s", ""+labeledOutput(content...)) return false } @@ -248,7 +269,7 @@ type labeledContent struct { // labeledOutput returns a string consisting of the provided labeledContent. Each labeled output is appended in the following manner: // -// \r\t{{label}}:{{align_spaces}}\t{{content}}\n +// \t{{label}}:{{align_spaces}}\t{{content}}\n // // The initial carriage return is required to undo/erase any padding added by testing.T.Errorf. The "\t{{label}}:" is for the label. // If a label is shorter than the longest label provided, padding spaces are added to make all the labels match in length. Once this @@ -264,7 +285,7 @@ func labeledOutput(content ...labeledContent) string { } var output string for _, v := range content { - output += "\r\t" + v.label + ":" + strings.Repeat(" ", longestLabel-len(v.label)) + "\t" + indentMessageLines(v.content, longestLabel) + "\n" + output += "\t" + v.label + ":" + strings.Repeat(" ", longestLabel-len(v.label)) + "\t" + indentMessageLines(v.content, longestLabel) + "\n" } return output } @@ -273,19 +294,26 @@ func labeledOutput(content ...labeledContent) string { // // assert.Implements(t, (*MyInterface)(nil), new(MyObject)) func Implements(t TestingT, interfaceObject interface{}, object interface{}, msgAndArgs ...interface{}) bool { - + if h, ok := t.(tHelper); ok { + h.Helper() + } interfaceType := reflect.TypeOf(interfaceObject).Elem() + if object == nil { + return Fail(t, fmt.Sprintf("Cannot check if nil implements %v", interfaceType), msgAndArgs...) + } if !reflect.TypeOf(object).Implements(interfaceType) { return Fail(t, fmt.Sprintf("%T must implement %v", object, interfaceType), msgAndArgs...) } return true - } // IsType asserts that the specified objects are of the same type. func IsType(t TestingT, expectedType interface{}, object interface{}, msgAndArgs ...interface{}) bool { + if h, ok := t.(tHelper); ok { + h.Helper() + } if !ObjectsAreEqual(reflect.TypeOf(object), reflect.TypeOf(expectedType)) { return Fail(t, fmt.Sprintf("Object expected to be of type %v, but was %v", reflect.TypeOf(expectedType), reflect.TypeOf(object)), msgAndArgs...) @@ -298,12 +326,13 @@ func IsType(t TestingT, expectedType interface{}, object interface{}, msgAndArgs // // assert.Equal(t, 123, 123) // -// Returns whether the assertion was successful (true) or not (false). -// // Pointer variable equality is determined based on the equality of the // referenced values (as opposed to the memory addresses). Function equality // cannot be determined and will always fail. func Equal(t TestingT, expected, actual interface{}, msgAndArgs ...interface{}) bool { + if h, ok := t.(tHelper); ok { + h.Helper() + } if err := validateEqualArgs(expected, actual); err != nil { return Fail(t, fmt.Sprintf("Invalid operation: %#v == %#v (%s)", expected, actual, err), msgAndArgs...) @@ -314,7 +343,7 @@ func Equal(t TestingT, expected, actual interface{}, msgAndArgs ...interface{}) expected, actual = formatUnequalValues(expected, actual) return Fail(t, fmt.Sprintf("Not equal: \n"+ "expected: %s\n"+ - "actual: %s%s", expected, actual, diff), msgAndArgs...) + "actual : %s%s", expected, actual, diff), msgAndArgs...) } return true @@ -341,34 +370,36 @@ func formatUnequalValues(expected, actual interface{}) (e string, a string) { // and equal. // // assert.EqualValues(t, uint32(123), int32(123)) -// -// Returns whether the assertion was successful (true) or not (false). func EqualValues(t TestingT, expected, actual interface{}, msgAndArgs ...interface{}) bool { + if h, ok := t.(tHelper); ok { + h.Helper() + } if !ObjectsAreEqualValues(expected, actual) { diff := diff(expected, actual) expected, actual = formatUnequalValues(expected, actual) return Fail(t, fmt.Sprintf("Not equal: \n"+ "expected: %s\n"+ - "actual: %s%s", expected, actual, diff), msgAndArgs...) + "actual : %s%s", expected, actual, diff), msgAndArgs...) } return true } -// Exactly asserts that two objects are equal is value and type. +// Exactly asserts that two objects are equal in value and type. // // assert.Exactly(t, int32(123), int64(123)) -// -// Returns whether the assertion was successful (true) or not (false). func Exactly(t TestingT, expected, actual interface{}, msgAndArgs ...interface{}) bool { + if h, ok := t.(tHelper); ok { + h.Helper() + } aType := reflect.TypeOf(expected) bType := reflect.TypeOf(actual) if aType != bType { - return Fail(t, fmt.Sprintf("Types expected to match exactly\n\r\t%v != %v", aType, bType), msgAndArgs...) + return Fail(t, fmt.Sprintf("Types expected to match exactly\n\t%v != %v", aType, bType), msgAndArgs...) } return Equal(t, expected, actual, msgAndArgs...) @@ -378,15 +409,27 @@ func Exactly(t TestingT, expected, actual interface{}, msgAndArgs ...interface{} // NotNil asserts that the specified object is not nil. // // assert.NotNil(t, err) -// -// Returns whether the assertion was successful (true) or not (false). func NotNil(t TestingT, object interface{}, msgAndArgs ...interface{}) bool { + if h, ok := t.(tHelper); ok { + h.Helper() + } if !isNil(object) { return true } return Fail(t, "Expected value not to be nil.", msgAndArgs...) } +// containsKind checks if a specified kind in the slice of kinds. +func containsKind(kinds []reflect.Kind, kind reflect.Kind) bool { + for i := 0; i < len(kinds); i++ { + if kind == kinds[i] { + return true + } + } + + return false +} + // isNil checks if a specified object is nil or not, without Failing. func isNil(object interface{}) bool { if object == nil { @@ -395,7 +438,14 @@ func isNil(object interface{}) bool { value := reflect.ValueOf(object) kind := value.Kind() - if kind >= reflect.Chan && kind <= reflect.Slice && value.IsNil() { + isNilableKind := containsKind( + []reflect.Kind{ + reflect.Chan, reflect.Func, + reflect.Interface, reflect.Map, + reflect.Ptr, reflect.Slice}, + kind) + + if isNilableKind && value.IsNil() { return true } @@ -405,82 +455,52 @@ func isNil(object interface{}) bool { // Nil asserts that the specified object is nil. // // assert.Nil(t, err) -// -// Returns whether the assertion was successful (true) or not (false). func Nil(t TestingT, object interface{}, msgAndArgs ...interface{}) bool { + if h, ok := t.(tHelper); ok { + h.Helper() + } if isNil(object) { return true } return Fail(t, fmt.Sprintf("Expected nil, but got: %#v", object), msgAndArgs...) } -var numericZeros = []interface{}{ - int(0), - int8(0), - int16(0), - int32(0), - int64(0), - uint(0), - uint8(0), - uint16(0), - uint32(0), - uint64(0), - float32(0), - float64(0), -} - // isEmpty gets whether the specified object is considered empty or not. func isEmpty(object interface{}) bool { + // get nil case out of the way if object == nil { return true - } else if object == "" { - return true - } else if object == false { - return true - } - - for _, v := range numericZeros { - if object == v { - return true - } } objValue := reflect.ValueOf(object) switch objValue.Kind() { - case reflect.Array, reflect.Chan, reflect.Map, reflect.Slice, reflect.String: - { - return (objValue.Len() == 0) - } - case reflect.Struct: - switch object.(type) { - case time.Time: - return object.(time.Time).IsZero() - } + // collection types are empty when they have no element + case reflect.Array, reflect.Chan, reflect.Map, reflect.Slice: + return objValue.Len() == 0 + // pointers are empty if nil or if the value they point to is empty case reflect.Ptr: - { - if objValue.IsNil() { - return true - } - switch object.(type) { - case *time.Time: - return object.(*time.Time).IsZero() - default: - return false - } + if objValue.IsNil() { + return true } + deref := objValue.Elem().Interface() + return isEmpty(deref) + // for all other types, compare against the zero value + default: + zero := reflect.Zero(objValue.Type()) + return reflect.DeepEqual(object, zero.Interface()) } - return false } // Empty asserts that the specified object is empty. I.e. nil, "", false, 0 or either // a slice or a channel with len == 0. // // assert.Empty(t, obj) -// -// Returns whether the assertion was successful (true) or not (false). func Empty(t TestingT, object interface{}, msgAndArgs ...interface{}) bool { + if h, ok := t.(tHelper); ok { + h.Helper() + } pass := isEmpty(object) if !pass { @@ -497,9 +517,10 @@ func Empty(t TestingT, object interface{}, msgAndArgs ...interface{}) bool { // if assert.NotEmpty(t, obj) { // assert.Equal(t, "two", obj[1]) // } -// -// Returns whether the assertion was successful (true) or not (false). func NotEmpty(t TestingT, object interface{}, msgAndArgs ...interface{}) bool { + if h, ok := t.(tHelper); ok { + h.Helper() + } pass := !isEmpty(object) if !pass { @@ -526,9 +547,10 @@ func getLen(x interface{}) (ok bool, length int) { // Len also fails if the object has a type that len() not accept. // // assert.Len(t, mySlice, 3) -// -// Returns whether the assertion was successful (true) or not (false). func Len(t TestingT, object interface{}, length int, msgAndArgs ...interface{}) bool { + if h, ok := t.(tHelper); ok { + h.Helper() + } ok, l := getLen(object) if !ok { return Fail(t, fmt.Sprintf("\"%s\" could not be applied builtin len()", object), msgAndArgs...) @@ -543,9 +565,15 @@ func Len(t TestingT, object interface{}, length int, msgAndArgs ...interface{}) // True asserts that the specified value is true. // // assert.True(t, myBool) -// -// Returns whether the assertion was successful (true) or not (false). func True(t TestingT, value bool, msgAndArgs ...interface{}) bool { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if h, ok := t.(interface { + Helper() + }); ok { + h.Helper() + } if value != true { return Fail(t, "Should be true", msgAndArgs...) @@ -558,9 +586,10 @@ func True(t TestingT, value bool, msgAndArgs ...interface{}) bool { // False asserts that the specified value is false. // // assert.False(t, myBool) -// -// Returns whether the assertion was successful (true) or not (false). func False(t TestingT, value bool, msgAndArgs ...interface{}) bool { + if h, ok := t.(tHelper); ok { + h.Helper() + } if value != false { return Fail(t, "Should be false", msgAndArgs...) @@ -574,11 +603,12 @@ func False(t TestingT, value bool, msgAndArgs ...interface{}) bool { // // assert.NotEqual(t, obj1, obj2) // -// Returns whether the assertion was successful (true) or not (false). -// // Pointer variable equality is determined based on the equality of the // referenced values (as opposed to the memory addresses). func NotEqual(t TestingT, expected, actual interface{}, msgAndArgs ...interface{}) bool { + if h, ok := t.(tHelper); ok { + h.Helper() + } if err := validateEqualArgs(expected, actual); err != nil { return Fail(t, fmt.Sprintf("Invalid operation: %#v != %#v (%s)", expected, actual, err), msgAndArgs...) @@ -636,9 +666,10 @@ func includeElement(list interface{}, element interface{}) (ok, found bool) { // assert.Contains(t, "Hello World", "World") // assert.Contains(t, ["Hello", "World"], "World") // assert.Contains(t, {"Hello": "World"}, "Hello") -// -// Returns whether the assertion was successful (true) or not (false). func Contains(t TestingT, s, contains interface{}, msgAndArgs ...interface{}) bool { + if h, ok := t.(tHelper); ok { + h.Helper() + } ok, found := includeElement(s, contains) if !ok { @@ -658,9 +689,10 @@ func Contains(t TestingT, s, contains interface{}, msgAndArgs ...interface{}) bo // assert.NotContains(t, "Hello World", "Earth") // assert.NotContains(t, ["Hello", "World"], "Earth") // assert.NotContains(t, {"Hello": "World"}, "Earth") -// -// Returns whether the assertion was successful (true) or not (false). func NotContains(t TestingT, s, contains interface{}, msgAndArgs ...interface{}) bool { + if h, ok := t.(tHelper); ok { + h.Helper() + } ok, found := includeElement(s, contains) if !ok { @@ -678,9 +710,10 @@ func NotContains(t TestingT, s, contains interface{}, msgAndArgs ...interface{}) // elements given in the specified subset(array, slice...). // // assert.Subset(t, [1, 2, 3], [1, 2], "But [1, 2, 3] does contain [1, 2]") -// -// Returns whether the assertion was successful (true) or not (false). func Subset(t TestingT, list, subset interface{}, msgAndArgs ...interface{}) (ok bool) { + if h, ok := t.(tHelper); ok { + h.Helper() + } if subset == nil { return true // we consider nil to be equal to the nil set } @@ -721,11 +754,12 @@ func Subset(t TestingT, list, subset interface{}, msgAndArgs ...interface{}) (ok // elements given in the specified subset(array, slice...). // // assert.NotSubset(t, [1, 3, 4], [1, 2], "But [1, 3, 4] does not contain [1, 2]") -// -// Returns whether the assertion was successful (true) or not (false). func NotSubset(t TestingT, list, subset interface{}, msgAndArgs ...interface{}) (ok bool) { + if h, ok := t.(tHelper); ok { + h.Helper() + } if subset == nil { - return false // we consider nil to be equal to the nil set + return Fail(t, fmt.Sprintf("nil is the empty set which is a subset of every set"), msgAndArgs...) } subsetValue := reflect.ValueOf(subset) @@ -760,8 +794,68 @@ func NotSubset(t TestingT, list, subset interface{}, msgAndArgs ...interface{}) return Fail(t, fmt.Sprintf("%q is a subset of %q", subset, list), msgAndArgs...) } +// ElementsMatch asserts that the specified listA(array, slice...) is equal to specified +// listB(array, slice...) ignoring the order of the elements. If there are duplicate elements, +// the number of appearances of each of them in both lists should match. +// +// assert.ElementsMatch(t, [1, 3, 2, 3], [1, 3, 3, 2]) +func ElementsMatch(t TestingT, listA, listB interface{}, msgAndArgs ...interface{}) (ok bool) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if isEmpty(listA) && isEmpty(listB) { + return true + } + + aKind := reflect.TypeOf(listA).Kind() + bKind := reflect.TypeOf(listB).Kind() + + if aKind != reflect.Array && aKind != reflect.Slice { + return Fail(t, fmt.Sprintf("%q has an unsupported type %s", listA, aKind), msgAndArgs...) + } + + if bKind != reflect.Array && bKind != reflect.Slice { + return Fail(t, fmt.Sprintf("%q has an unsupported type %s", listB, bKind), msgAndArgs...) + } + + aValue := reflect.ValueOf(listA) + bValue := reflect.ValueOf(listB) + + aLen := aValue.Len() + bLen := bValue.Len() + + if aLen != bLen { + return Fail(t, fmt.Sprintf("lengths don't match: %d != %d", aLen, bLen), msgAndArgs...) + } + + // Mark indexes in bValue that we already used + visited := make([]bool, bLen) + for i := 0; i < aLen; i++ { + element := aValue.Index(i).Interface() + found := false + for j := 0; j < bLen; j++ { + if visited[j] { + continue + } + if ObjectsAreEqual(bValue.Index(j).Interface(), element) { + visited[j] = true + found = true + break + } + } + if !found { + return Fail(t, fmt.Sprintf("element %s appears more times in %s than in %s", element, aValue, bValue), msgAndArgs...) + } + } + + return true +} + // Condition uses a Comparison to assert a complex condition. func Condition(t TestingT, comp Comparison, msgAndArgs ...interface{}) bool { + if h, ok := t.(tHelper); ok { + h.Helper() + } result := comp() if !result { Fail(t, "Condition failed!", msgAndArgs...) @@ -798,12 +892,13 @@ func didPanic(f PanicTestFunc) (bool, interface{}) { // Panics asserts that the code inside the specified PanicTestFunc panics. // // assert.Panics(t, func(){ GoCrazy() }) -// -// Returns whether the assertion was successful (true) or not (false). func Panics(t TestingT, f PanicTestFunc, msgAndArgs ...interface{}) bool { + if h, ok := t.(tHelper); ok { + h.Helper() + } if funcDidPanic, panicValue := didPanic(f); !funcDidPanic { - return Fail(t, fmt.Sprintf("func %#v should panic\n\r\tPanic value:\t%v", f, panicValue), msgAndArgs...) + return Fail(t, fmt.Sprintf("func %#v should panic\n\tPanic value:\t%#v", f, panicValue), msgAndArgs...) } return true @@ -813,16 +908,17 @@ func Panics(t TestingT, f PanicTestFunc, msgAndArgs ...interface{}) bool { // the recovered panic value equals the expected panic value. // // assert.PanicsWithValue(t, "crazy error", func(){ GoCrazy() }) -// -// Returns whether the assertion was successful (true) or not (false). func PanicsWithValue(t TestingT, expected interface{}, f PanicTestFunc, msgAndArgs ...interface{}) bool { + if h, ok := t.(tHelper); ok { + h.Helper() + } funcDidPanic, panicValue := didPanic(f) if !funcDidPanic { - return Fail(t, fmt.Sprintf("func %#v should panic\n\r\tPanic value:\t%v", f, panicValue), msgAndArgs...) + return Fail(t, fmt.Sprintf("func %#v should panic\n\tPanic value:\t%#v", f, panicValue), msgAndArgs...) } if panicValue != expected { - return Fail(t, fmt.Sprintf("func %#v should panic with value:\t%v\n\r\tPanic value:\t%v", f, expected, panicValue), msgAndArgs...) + return Fail(t, fmt.Sprintf("func %#v should panic with value:\t%#v\n\tPanic value:\t%#v", f, expected, panicValue), msgAndArgs...) } return true @@ -831,12 +927,13 @@ func PanicsWithValue(t TestingT, expected interface{}, f PanicTestFunc, msgAndAr // NotPanics asserts that the code inside the specified PanicTestFunc does NOT panic. // // assert.NotPanics(t, func(){ RemainCalm() }) -// -// Returns whether the assertion was successful (true) or not (false). func NotPanics(t TestingT, f PanicTestFunc, msgAndArgs ...interface{}) bool { + if h, ok := t.(tHelper); ok { + h.Helper() + } if funcDidPanic, panicValue := didPanic(f); funcDidPanic { - return Fail(t, fmt.Sprintf("func %#v should not panic\n\r\tPanic value:\t%v", f, panicValue), msgAndArgs...) + return Fail(t, fmt.Sprintf("func %#v should not panic\n\tPanic value:\t%v", f, panicValue), msgAndArgs...) } return true @@ -845,9 +942,10 @@ func NotPanics(t TestingT, f PanicTestFunc, msgAndArgs ...interface{}) bool { // WithinDuration asserts that the two times are within duration delta of each other. // // assert.WithinDuration(t, time.Now(), time.Now(), 10*time.Second) -// -// Returns whether the assertion was successful (true) or not (false). func WithinDuration(t TestingT, expected, actual time.Time, delta time.Duration, msgAndArgs ...interface{}) bool { + if h, ok := t.(tHelper); ok { + h.Helper() + } dt := expected.Sub(actual) if dt < -delta || dt > delta { @@ -896,9 +994,10 @@ func toFloat(x interface{}) (float64, bool) { // InDelta asserts that the two numerals are within delta of each other. // // assert.InDelta(t, math.Pi, (22 / 7.0), 0.01) -// -// Returns whether the assertion was successful (true) or not (false). func InDelta(t TestingT, expected, actual interface{}, delta float64, msgAndArgs ...interface{}) bool { + if h, ok := t.(tHelper); ok { + h.Helper() + } af, aok := toFloat(expected) bf, bok := toFloat(actual) @@ -925,6 +1024,9 @@ func InDelta(t TestingT, expected, actual interface{}, delta float64, msgAndArgs // InDeltaSlice is the same as InDelta, except it compares two slices. func InDeltaSlice(t TestingT, expected, actual interface{}, delta float64, msgAndArgs ...interface{}) bool { + if h, ok := t.(tHelper); ok { + h.Helper() + } if expected == nil || actual == nil || reflect.TypeOf(actual).Kind() != reflect.Slice || reflect.TypeOf(expected).Kind() != reflect.Slice { @@ -944,6 +1046,50 @@ func InDeltaSlice(t TestingT, expected, actual interface{}, delta float64, msgAn return true } +// InDeltaMapValues is the same as InDelta, but it compares all values between two maps. Both maps must have exactly the same keys. +func InDeltaMapValues(t TestingT, expected, actual interface{}, delta float64, msgAndArgs ...interface{}) bool { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if expected == nil || actual == nil || + reflect.TypeOf(actual).Kind() != reflect.Map || + reflect.TypeOf(expected).Kind() != reflect.Map { + return Fail(t, "Arguments must be maps", msgAndArgs...) + } + + expectedMap := reflect.ValueOf(expected) + actualMap := reflect.ValueOf(actual) + + if expectedMap.Len() != actualMap.Len() { + return Fail(t, "Arguments must have the same number of keys", msgAndArgs...) + } + + for _, k := range expectedMap.MapKeys() { + ev := expectedMap.MapIndex(k) + av := actualMap.MapIndex(k) + + if !ev.IsValid() { + return Fail(t, fmt.Sprintf("missing key %q in expected map", k), msgAndArgs...) + } + + if !av.IsValid() { + return Fail(t, fmt.Sprintf("missing key %q in actual map", k), msgAndArgs...) + } + + if !InDelta( + t, + ev.Interface(), + av.Interface(), + delta, + msgAndArgs..., + ) { + return false + } + } + + return true +} + func calcRelativeError(expected, actual interface{}) (float64, error) { af, aok := toFloat(expected) if !aok { @@ -961,9 +1107,10 @@ func calcRelativeError(expected, actual interface{}) (float64, error) { } // InEpsilon asserts that expected and actual have a relative error less than epsilon -// -// Returns whether the assertion was successful (true) or not (false). func InEpsilon(t TestingT, expected, actual interface{}, epsilon float64, msgAndArgs ...interface{}) bool { + if h, ok := t.(tHelper); ok { + h.Helper() + } actualEpsilon, err := calcRelativeError(expected, actual) if err != nil { return Fail(t, err.Error(), msgAndArgs...) @@ -978,6 +1125,9 @@ func InEpsilon(t TestingT, expected, actual interface{}, epsilon float64, msgAnd // InEpsilonSlice is the same as InEpsilon, except it compares each value from two slices. func InEpsilonSlice(t TestingT, expected, actual interface{}, epsilon float64, msgAndArgs ...interface{}) bool { + if h, ok := t.(tHelper); ok { + h.Helper() + } if expected == nil || actual == nil || reflect.TypeOf(actual).Kind() != reflect.Slice || reflect.TypeOf(expected).Kind() != reflect.Slice { @@ -1007,9 +1157,10 @@ func InEpsilonSlice(t TestingT, expected, actual interface{}, epsilon float64, m // if assert.NoError(t, err) { // assert.Equal(t, expectedObj, actualObj) // } -// -// Returns whether the assertion was successful (true) or not (false). func NoError(t TestingT, err error, msgAndArgs ...interface{}) bool { + if h, ok := t.(tHelper); ok { + h.Helper() + } if err != nil { return Fail(t, fmt.Sprintf("Received unexpected error:\n%+v", err), msgAndArgs...) } @@ -1023,9 +1174,10 @@ func NoError(t TestingT, err error, msgAndArgs ...interface{}) bool { // if assert.Error(t, err) { // assert.Equal(t, expectedError, err) // } -// -// Returns whether the assertion was successful (true) or not (false). func Error(t TestingT, err error, msgAndArgs ...interface{}) bool { + if h, ok := t.(tHelper); ok { + h.Helper() + } if err == nil { return Fail(t, "An error is expected but got nil.", msgAndArgs...) @@ -1039,9 +1191,10 @@ func Error(t TestingT, err error, msgAndArgs ...interface{}) bool { // // actualObj, err := SomeFunction() // assert.EqualError(t, err, expectedErrorString) -// -// Returns whether the assertion was successful (true) or not (false). func EqualError(t TestingT, theError error, errString string, msgAndArgs ...interface{}) bool { + if h, ok := t.(tHelper); ok { + h.Helper() + } if !Error(t, theError, msgAndArgs...) { return false } @@ -1051,7 +1204,7 @@ func EqualError(t TestingT, theError error, errString string, msgAndArgs ...inte if expected != actual { return Fail(t, fmt.Sprintf("Error message not equal:\n"+ "expected: %q\n"+ - "actual: %q", expected, actual), msgAndArgs...) + "actual : %q", expected, actual), msgAndArgs...) } return true } @@ -1074,9 +1227,10 @@ func matchRegexp(rx interface{}, str interface{}) bool { // // assert.Regexp(t, regexp.MustCompile("start"), "it's starting") // assert.Regexp(t, "start...$", "it's not starting") -// -// Returns whether the assertion was successful (true) or not (false). func Regexp(t TestingT, rx interface{}, str interface{}, msgAndArgs ...interface{}) bool { + if h, ok := t.(tHelper); ok { + h.Helper() + } match := matchRegexp(rx, str) @@ -1091,9 +1245,10 @@ func Regexp(t TestingT, rx interface{}, str interface{}, msgAndArgs ...interface // // assert.NotRegexp(t, regexp.MustCompile("starts"), "it's starting") // assert.NotRegexp(t, "^start", "it's not starting") -// -// Returns whether the assertion was successful (true) or not (false). func NotRegexp(t TestingT, rx interface{}, str interface{}, msgAndArgs ...interface{}) bool { + if h, ok := t.(tHelper); ok { + h.Helper() + } match := matchRegexp(rx, str) if match { @@ -1104,28 +1259,71 @@ func NotRegexp(t TestingT, rx interface{}, str interface{}, msgAndArgs ...interf } -// Zero asserts that i is the zero value for its type and returns the truth. +// Zero asserts that i is the zero value for its type. func Zero(t TestingT, i interface{}, msgAndArgs ...interface{}) bool { + if h, ok := t.(tHelper); ok { + h.Helper() + } if i != nil && !reflect.DeepEqual(i, reflect.Zero(reflect.TypeOf(i)).Interface()) { return Fail(t, fmt.Sprintf("Should be zero, but was %v", i), msgAndArgs...) } return true } -// NotZero asserts that i is not the zero value for its type and returns the truth. +// NotZero asserts that i is not the zero value for its type. func NotZero(t TestingT, i interface{}, msgAndArgs ...interface{}) bool { + if h, ok := t.(tHelper); ok { + h.Helper() + } if i == nil || reflect.DeepEqual(i, reflect.Zero(reflect.TypeOf(i)).Interface()) { return Fail(t, fmt.Sprintf("Should not be zero, but was %v", i), msgAndArgs...) } return true } +// FileExists checks whether a file exists in the given path. It also fails if the path points to a directory or there is an error when trying to check the file. +func FileExists(t TestingT, path string, msgAndArgs ...interface{}) bool { + if h, ok := t.(tHelper); ok { + h.Helper() + } + info, err := os.Lstat(path) + if err != nil { + if os.IsNotExist(err) { + return Fail(t, fmt.Sprintf("unable to find file %q", path), msgAndArgs...) + } + return Fail(t, fmt.Sprintf("error when running os.Lstat(%q): %s", path, err), msgAndArgs...) + } + if info.IsDir() { + return Fail(t, fmt.Sprintf("%q is a directory", path), msgAndArgs...) + } + return true +} + +// DirExists checks whether a directory exists in the given path. It also fails if the path is a file rather a directory or there is an error checking whether it exists. +func DirExists(t TestingT, path string, msgAndArgs ...interface{}) bool { + if h, ok := t.(tHelper); ok { + h.Helper() + } + info, err := os.Lstat(path) + if err != nil { + if os.IsNotExist(err) { + return Fail(t, fmt.Sprintf("unable to find file %q", path), msgAndArgs...) + } + return Fail(t, fmt.Sprintf("error when running os.Lstat(%q): %s", path, err), msgAndArgs...) + } + if !info.IsDir() { + return Fail(t, fmt.Sprintf("%q is a file", path), msgAndArgs...) + } + return true +} + // JSONEq asserts that two JSON strings are equivalent. // // assert.JSONEq(t, `{"hello": "world", "foo": "bar"}`, `{"foo": "bar", "hello": "world"}`) -// -// Returns whether the assertion was successful (true) or not (false). func JSONEq(t TestingT, expected string, actual string, msgAndArgs ...interface{}) bool { + if h, ok := t.(tHelper); ok { + h.Helper() + } var expectedJSONAsInterface, actualJSONAsInterface interface{} if err := json.Unmarshal([]byte(expected), &expectedJSONAsInterface); err != nil { @@ -1151,7 +1349,7 @@ func typeAndKind(v interface{}) (reflect.Type, reflect.Kind) { } // diff returns a diff of both values as long as both are of the same type and -// are a struct, map, slice or array. Otherwise it returns an empty string. +// are a struct, map, slice, array or string. Otherwise it returns an empty string. func diff(expected interface{}, actual interface{}) string { if expected == nil || actual == nil { return "" @@ -1164,12 +1362,18 @@ func diff(expected interface{}, actual interface{}) string { return "" } - if ek != reflect.Struct && ek != reflect.Map && ek != reflect.Slice && ek != reflect.Array { + if ek != reflect.Struct && ek != reflect.Map && ek != reflect.Slice && ek != reflect.Array && ek != reflect.String { return "" } - e := spewConfig.Sdump(expected) - a := spewConfig.Sdump(actual) + var e, a string + if et != reflect.TypeOf("") { + e = spewConfig.Sdump(expected) + a = spewConfig.Sdump(actual) + } else { + e = expected.(string) + a = actual.(string) + } diff, _ := difflib.GetUnifiedDiffString(difflib.UnifiedDiff{ A: difflib.SplitLines(e), @@ -1206,3 +1410,7 @@ var spewConfig = spew.ConfigState{ DisableCapacities: true, SortKeys: true, } + +type tHelper interface { + Helper() +} diff --git a/vendor/github.com/stretchr/testify/assert/http_assertions.go b/vendor/github.com/stretchr/testify/assert/http_assertions.go index ba811c04..df46fa77 100644 --- a/vendor/github.com/stretchr/testify/assert/http_assertions.go +++ b/vendor/github.com/stretchr/testify/assert/http_assertions.go @@ -12,10 +12,11 @@ import ( // an error if building a new request fails. func httpCode(handler http.HandlerFunc, method, url string, values url.Values) (int, error) { w := httptest.NewRecorder() - req, err := http.NewRequest(method, url+"?"+values.Encode(), nil) + req, err := http.NewRequest(method, url, nil) if err != nil { return -1, err } + req.URL.RawQuery = values.Encode() handler(w, req) return w.Code, nil } @@ -25,7 +26,10 @@ func httpCode(handler http.HandlerFunc, method, url string, values url.Values) ( // assert.HTTPSuccess(t, myHandler, "POST", "http://www.google.com", nil) // // Returns whether the assertion was successful (true) or not (false). -func HTTPSuccess(t TestingT, handler http.HandlerFunc, method, url string, values url.Values) bool { +func HTTPSuccess(t TestingT, handler http.HandlerFunc, method, url string, values url.Values, msgAndArgs ...interface{}) bool { + if h, ok := t.(tHelper); ok { + h.Helper() + } code, err := httpCode(handler, method, url, values) if err != nil { Fail(t, fmt.Sprintf("Failed to build test request, got error: %s", err)) @@ -45,7 +49,10 @@ func HTTPSuccess(t TestingT, handler http.HandlerFunc, method, url string, value // assert.HTTPRedirect(t, myHandler, "GET", "/a/b/c", url.Values{"a": []string{"b", "c"}} // // Returns whether the assertion was successful (true) or not (false). -func HTTPRedirect(t TestingT, handler http.HandlerFunc, method, url string, values url.Values) bool { +func HTTPRedirect(t TestingT, handler http.HandlerFunc, method, url string, values url.Values, msgAndArgs ...interface{}) bool { + if h, ok := t.(tHelper); ok { + h.Helper() + } code, err := httpCode(handler, method, url, values) if err != nil { Fail(t, fmt.Sprintf("Failed to build test request, got error: %s", err)) @@ -65,7 +72,10 @@ func HTTPRedirect(t TestingT, handler http.HandlerFunc, method, url string, valu // assert.HTTPError(t, myHandler, "POST", "/a/b/c", url.Values{"a": []string{"b", "c"}} // // Returns whether the assertion was successful (true) or not (false). -func HTTPError(t TestingT, handler http.HandlerFunc, method, url string, values url.Values) bool { +func HTTPError(t TestingT, handler http.HandlerFunc, method, url string, values url.Values, msgAndArgs ...interface{}) bool { + if h, ok := t.(tHelper); ok { + h.Helper() + } code, err := httpCode(handler, method, url, values) if err != nil { Fail(t, fmt.Sprintf("Failed to build test request, got error: %s", err)) @@ -95,10 +105,13 @@ func HTTPBody(handler http.HandlerFunc, method, url string, values url.Values) s // HTTPBodyContains asserts that a specified handler returns a // body that contains a string. // -// assert.HTTPBodyContains(t, myHandler, "www.google.com", nil, "I'm Feeling Lucky") +// assert.HTTPBodyContains(t, myHandler, "GET", "www.google.com", nil, "I'm Feeling Lucky") // // Returns whether the assertion was successful (true) or not (false). -func HTTPBodyContains(t TestingT, handler http.HandlerFunc, method, url string, values url.Values, str interface{}) bool { +func HTTPBodyContains(t TestingT, handler http.HandlerFunc, method, url string, values url.Values, str interface{}, msgAndArgs ...interface{}) bool { + if h, ok := t.(tHelper); ok { + h.Helper() + } body := HTTPBody(handler, method, url, values) contains := strings.Contains(body, fmt.Sprint(str)) @@ -112,10 +125,13 @@ func HTTPBodyContains(t TestingT, handler http.HandlerFunc, method, url string, // HTTPBodyNotContains asserts that a specified handler returns a // body that does not contain a string. // -// assert.HTTPBodyNotContains(t, myHandler, "www.google.com", nil, "I'm Feeling Lucky") +// assert.HTTPBodyNotContains(t, myHandler, "GET", "www.google.com", nil, "I'm Feeling Lucky") // // Returns whether the assertion was successful (true) or not (false). -func HTTPBodyNotContains(t TestingT, handler http.HandlerFunc, method, url string, values url.Values, str interface{}) bool { +func HTTPBodyNotContains(t TestingT, handler http.HandlerFunc, method, url string, values url.Values, str interface{}, msgAndArgs ...interface{}) bool { + if h, ok := t.(tHelper); ok { + h.Helper() + } body := HTTPBody(handler, method, url, values) contains := strings.Contains(body, fmt.Sprint(str)) diff --git a/vendor/github.com/xeipuuv/gojsonpointer/README.md b/vendor/github.com/xeipuuv/gojsonpointer/README.md index dbe4d508..00059242 100644 --- a/vendor/github.com/xeipuuv/gojsonpointer/README.md +++ b/vendor/github.com/xeipuuv/gojsonpointer/README.md @@ -1,6 +1,39 @@ # gojsonpointer An implementation of JSON Pointer - Go language +## Usage + jsonText := `{ + "name": "Bobby B", + "occupation": { + "title" : "King", + "years" : 15, + "heir" : "Joffrey B" + } + }` + + var jsonDocument map[string]interface{} + json.Unmarshal([]byte(jsonText), &jsonDocument) + + //create a JSON pointer + pointerString := "/occupation/title" + pointer, _ := NewJsonPointer(pointerString) + + //SET a new value for the "title" in the document + pointer.Set(jsonDocument, "Supreme Leader of Westeros") + + //GET the new "title" from the document + title, _, _ := pointer.Get(jsonDocument) + fmt.Println(title) //outputs "Supreme Leader of Westeros" + + //DELETE the "heir" from the document + deletePointer := NewJsonPointer("/occupation/heir") + deletePointer.Delete(jsonDocument) + + b, _ := json.Marshal(jsonDocument) + fmt.Println(string(b)) + //outputs `{"name":"Bobby B","occupation":{"title":"Supreme Leader of Westeros","years":15}}` + + ## References http://tools.ietf.org/html/draft-ietf-appsawg-json-pointer-07 diff --git a/vendor/github.com/xeipuuv/gojsonpointer/pointer.go b/vendor/github.com/xeipuuv/gojsonpointer/pointer.go index 06f1918e..7faf5d7f 100644 --- a/vendor/github.com/xeipuuv/gojsonpointer/pointer.go +++ b/vendor/github.com/xeipuuv/gojsonpointer/pointer.go @@ -90,6 +90,13 @@ func (p *JsonPointer) Set(document interface{}, value interface{}) (interface{}, } +// Uses the pointer to delete a value from a JSON document +func (p *JsonPointer) Delete(document interface{}) (interface{}, error) { + is := &implStruct{mode: "DEL", inDocument: document} + p.implementation(is) + return document, is.outError +} + // Both Get and Set functions use the same implementation to avoid code duplication func (p *JsonPointer) implementation(i *implStruct) { @@ -106,9 +113,14 @@ func (p *JsonPointer) implementation(i *implStruct) { node := i.inDocument + previousNodes := make([]interface{}, len(p.referenceTokens)) + previousTokens := make([]string, len(p.referenceTokens)) + for ti, token := range p.referenceTokens { isLastToken := ti == len(p.referenceTokens)-1 + previousNodes[ti] = node + previousTokens[ti] = token switch v := node.(type) { @@ -118,7 +130,11 @@ func (p *JsonPointer) implementation(i *implStruct) { node = v[decodedToken] if isLastToken && i.mode == "SET" { v[decodedToken] = i.setInValue + } else if isLastToken && i.mode =="DEL" { + delete(v,decodedToken) } + } else if (isLastToken && i.mode == "SET") { + v[decodedToken] = i.setInValue } else { i.outError = fmt.Errorf("Object has no key '%s'", decodedToken) i.getOutKind = reflect.Map @@ -144,6 +160,11 @@ func (p *JsonPointer) implementation(i *implStruct) { node = v[tokenIndex] if isLastToken && i.mode == "SET" { v[tokenIndex] = i.setInValue + } else if isLastToken && i.mode =="DEL" { + v[tokenIndex] = v[len(v)-1] + v[len(v)-1] = nil + v = v[:len(v)-1] + previousNodes[ti-1].(map[string]interface{})[previousTokens[ti-1]] = v } default: diff --git a/vendor/github.com/xeipuuv/gojsonreference/reference.go b/vendor/github.com/xeipuuv/gojsonreference/reference.go index d4d2eca0..64572913 100644 --- a/vendor/github.com/xeipuuv/gojsonreference/reference.go +++ b/vendor/github.com/xeipuuv/gojsonreference/reference.go @@ -27,11 +27,12 @@ package gojsonreference import ( "errors" - "github.com/xeipuuv/gojsonpointer" "net/url" "path/filepath" "runtime" "strings" + + "github.com/xeipuuv/gojsonpointer" ) const ( @@ -124,16 +125,21 @@ func (r *JsonReference) parse(jsonReferenceString string) (err error) { // Creates a new reference from a parent and a child // If the child cannot inherit from the parent, an error is returned func (r *JsonReference) Inherits(child JsonReference) (*JsonReference, error) { - childUrl := child.GetUrl() - parentUrl := r.GetUrl() - if childUrl == nil { + if child.GetUrl() == nil { return nil, errors.New("childUrl is nil!") } - if parentUrl == nil { + + if r.GetUrl() == nil { return nil, errors.New("parentUrl is nil!") } - ref, err := NewJsonReference(parentUrl.ResolveReference(childUrl).String()) + // Get a copy of the parent url to make sure we do not modify the original. + // URL reference resolving fails if the fragment of the child is empty, but the parent's is not. + // The fragment of the child must be used, so the fragment of the parent is manually removed. + parentUrl := *r.GetUrl() + parentUrl.Fragment = "" + + ref, err := NewJsonReference(parentUrl.ResolveReference(child.GetUrl()).String()) if err != nil { return nil, err } diff --git a/vendor/github.com/xeipuuv/gojsonschema/.gitignore b/vendor/github.com/xeipuuv/gojsonschema/.gitignore index c1e0636f..68e993ce 100644 --- a/vendor/github.com/xeipuuv/gojsonschema/.gitignore +++ b/vendor/github.com/xeipuuv/gojsonschema/.gitignore @@ -1 +1,3 @@ *.sw[nop] +*.iml +.vscode/ diff --git a/vendor/github.com/xeipuuv/gojsonschema/.travis.yml b/vendor/github.com/xeipuuv/gojsonschema/.travis.yml index 9cc01e8a..3289001c 100644 --- a/vendor/github.com/xeipuuv/gojsonschema/.travis.yml +++ b/vendor/github.com/xeipuuv/gojsonschema/.travis.yml @@ -1,7 +1,9 @@ language: go go: - - 1.3 + - "1.11" + - "1.12" + - "1.13" before_install: - - go get github.com/sigu-399/gojsonreference - - go get github.com/sigu-399/gojsonpointer + - go get github.com/xeipuuv/gojsonreference + - go get github.com/xeipuuv/gojsonpointer - go get github.com/stretchr/testify/assert diff --git a/vendor/github.com/xeipuuv/gojsonschema/README.md b/vendor/github.com/xeipuuv/gojsonschema/README.md index 83ad31c7..758f26df 100644 --- a/vendor/github.com/xeipuuv/gojsonschema/README.md +++ b/vendor/github.com/xeipuuv/gojsonschema/README.md @@ -1,10 +1,12 @@ +[![GoDoc](https://godoc.org/github.com/xeipuuv/gojsonschema?status.svg)](https://godoc.org/github.com/xeipuuv/gojsonschema) [![Build Status](https://travis-ci.org/xeipuuv/gojsonschema.svg)](https://travis-ci.org/xeipuuv/gojsonschema) +[![Go Report Card](https://goreportcard.com/badge/github.com/xeipuuv/gojsonschema)](https://goreportcard.com/report/github.com/xeipuuv/gojsonschema) # gojsonschema ## Description -An implementation of JSON Schema, based on IETF's draft v4 - Go language +An implementation of JSON Schema for the Go programming language. Supports draft-04, draft-06 and draft-07. References : @@ -54,7 +56,6 @@ func main() { fmt.Printf("- %s\n", desc) } } - } @@ -148,6 +149,87 @@ To check the result : } ``` + +## Loading local schemas + +By default `file` and `http(s)` references to external schemas are loaded automatically via the file system or via http(s). An external schema can also be loaded using a `SchemaLoader`. + +```go + sl := gojsonschema.NewSchemaLoader() + loader1 := gojsonschema.NewStringLoader(`{ "type" : "string" }`) + err := sl.AddSchema("http://some_host.com/string.json", loader1) +``` + +Alternatively if your schema already has an `$id` you can use the `AddSchemas` function +```go + loader2 := gojsonschema.NewStringLoader(`{ + "$id" : "http://some_host.com/maxlength.json", + "maxLength" : 5 + }`) + err = sl.AddSchemas(loader2) +``` + +The main schema should be passed to the `Compile` function. This main schema can then directly reference the added schemas without needing to download them. +```go + loader3 := gojsonschema.NewStringLoader(`{ + "$id" : "http://some_host.com/main.json", + "allOf" : [ + { "$ref" : "http://some_host.com/string.json" }, + { "$ref" : "http://some_host.com/maxlength.json" } + ] + }`) + + schema, err := sl.Compile(loader3) + + documentLoader := gojsonschema.NewStringLoader(`"hello world"`) + + result, err := schema.Validate(documentLoader) +``` + +It's also possible to pass a `ReferenceLoader` to the `Compile` function that references a loaded schema. + +```go +err = sl.AddSchemas(loader3) +schema, err := sl.Compile(gojsonschema.NewReferenceLoader("http://some_host.com/main.json")) +``` + +Schemas added by `AddSchema` and `AddSchemas` are only validated when the entire schema is compiled, unless meta-schema validation is used. + +## Using a specific draft +By default `gojsonschema` will try to detect the draft of a schema by using the `$schema` keyword and parse it in a strict draft-04, draft-06 or draft-07 mode. If `$schema` is missing, or the draft version is not explicitely set, a hybrid mode is used which merges together functionality of all drafts into one mode. + +Autodectection can be turned off with the `AutoDetect` property. Specific draft versions can be specified with the `Draft` property. + +```go +sl := gojsonschema.NewSchemaLoader() +sl.Draft = gojsonschema.Draft7 +sl.AutoDetect = false +``` + +If autodetection is on (default), a draft-07 schema can savely reference draft-04 schemas and vice-versa, as long as `$schema` is specified in all schemas. + +## Meta-schema validation +Schemas that are added using the `AddSchema`, `AddSchemas` and `Compile` can be validated against their meta-schema by setting the `Validate` property. + +The following example will produce an error as `multipleOf` must be a number. If `Validate` is off (default), this error is only returned at the `Compile` step. + +```go +sl := gojsonschema.NewSchemaLoader() +sl.Validate = true +err := sl.AddSchemas(gojsonschema.NewStringLoader(`{ + $id" : "http://some_host.com/invalid.json", + "$schema": "http://json-schema.org/draft-07/schema#", + "multipleOf" : true +}`)) + ``` +``` + ``` + +Errors returned by meta-schema validation are more readable and contain more information, which helps significantly if you are developing a schema. + +Meta-schema validation also works with a custom `$schema`. In case `$schema` is missing, or `AutoDetect` is set to `false`, the meta-schema of the used draft is used. + + ## Working with Errors The library handles string error codes which you can customize by creating your own gojsonschema.locale and setting it @@ -155,7 +237,9 @@ The library handles string error codes which you can customize by creating your gojsonschema.Locale = YourCustomLocale{} ``` -However, each error contains additional contextual information. +However, each error contains additional contextual information. + +Newer versions of `gojsonschema` may have new additional errors, so code that uses a custom locale will need to be updated when this happens. **err.Type()**: *string* Returns the "type" of error that occurred. Note you can also type check. See below @@ -169,15 +253,18 @@ Note: An error of RequiredType has an err.Type() return value of "required" "number_not": NumberNotError "missing_dependency": MissingDependencyError "internal": InternalError + "const": ConstEror "enum": EnumError "array_no_additional_items": ArrayNoAdditionalItemsError "array_min_items": ArrayMinItemsError "array_max_items": ArrayMaxItemsError "unique": ItemsMustBeUniqueError + "contains" : ArrayContainsError "array_min_properties": ArrayMinPropertiesError "array_max_properties": ArrayMaxPropertiesError "additional_property_not_allowed": AdditionalPropertyNotAllowedError "invalid_property_pattern": InvalidPropertyPatternError + "invalid_property_name": InvalidPropertyNameError "string_gte": StringLengthGTEError "string_lte": StringLengthLTEError "pattern": DoesNotMatchPatternError @@ -186,15 +273,19 @@ Note: An error of RequiredType has an err.Type() return value of "required" "number_gt": NumberGTError "number_lte": NumberLTEError "number_lt": NumberLTError + "condition_then" : ConditionThenError + "condition_else" : ConditionElseError **err.Value()**: *interface{}* Returns the value given -**err.Context()**: *gojsonschema.jsonContext* Returns the context. This has a String() method that will print something like this: (root).firstName +**err.Context()**: *gojsonschema.JsonContext* Returns the context. This has a String() method that will print something like this: (root).firstName **err.Field()**: *string* Returns the fieldname in the format firstName, or for embedded properties, person.firstName. This returns the same as the String() method on *err.Context()* but removes the (root). prefix. **err.Description()**: *string* The error description. This is based on the locale you are using. See the beginning of this section for overwriting the locale with a custom implementation. +**err.DescriptionFormat()**: *string* The error description format. This is relevant if you are adding custom validation errors afterwards to the result. + **err.Details()**: *gojsonschema.ErrorDetails* Returns a map[string]interface{} of additional error details specific to the error. For example, GTE errors will have a "min" value, LTE will have a "max" value. See errors.go for a full description of all the error details. Every error always contains a "field" key that holds the value of *err.Field()* Note in most cases, the err.Details() will be used to generate replacement strings in your locales, and not used directly. These strings follow the text/template format i.e. @@ -224,11 +315,36 @@ The above error message would then be rendered with the `field` value in capital Learn more about what types of template functions you can use in `ErrorTemplateFuncs` by referring to Go's [text/template FuncMap](https://golang.org/pkg/text/template/#FuncMap) type. ## Formats -JSON Schema allows for optional "format" property to validate strings against well-known formats. gojsonschema ships with all of the formats defined in the spec that you can use like this: +JSON Schema allows for optional "format" property to validate instances against well-known formats. gojsonschema ships with all of the formats defined in the spec that you can use like this: + ````json {"type": "string", "format": "email"} ```` -Available formats: date-time, hostname, email, ipv4, ipv6, uri, uri-reference. + +Not all formats defined in draft-07 are available. Implemented formats are: + +* `date` +* `time` +* `date-time` +* `hostname`. Subdomains that start with a number are also supported, but this means that it doesn't strictly follow [RFC1034](http://tools.ietf.org/html/rfc1034#section-3.5) and has the implication that ipv4 addresses are also recognized as valid hostnames. +* `email`. Go's email parser deviates slightly from [RFC5322](https://tools.ietf.org/html/rfc5322). Includes unicode support. +* `idn-email`. Same caveat as `email`. +* `ipv4` +* `ipv6` +* `uri`. Includes unicode support. +* `uri-reference`. Includes unicode support. +* `iri` +* `iri-reference` +* `uri-template` +* `uuid` +* `regex`. Go uses the [RE2](https://github.com/google/re2/wiki/Syntax) engine and is not [ECMA262](http://www.ecma-international.org/publications/files/ECMA-ST/Ecma-262.pdf) compatible. +* `json-pointer` +* `relative-json-pointer` + +`email`, `uri` and `uri-reference` use the same validation code as their unicode counterparts `idn-email`, `iri` and `iri-reference`. If you rely on unicode support you should use the specific +unicode enabled formats for the sake of interoperability as other implementations might not support unicode in the regular formats. + +The validation code for `uri`, `idn-email` and their relatives use mostly standard library code. For repetitive or more complex formats, you can create custom format checkers and add them to gojsonschema like this: @@ -237,8 +353,14 @@ For repetitive or more complex formats, you can create custom format checkers an type RoleFormatChecker struct {} // Ensure it meets the gojsonschema.FormatChecker interface -func (f RoleFormatChecker) IsFormat(input string) bool { - return strings.HasPrefix("ROLE_", input) +func (f RoleFormatChecker) IsFormat(input interface{}) bool { + + asString, ok := input.(string) + if ok == false { + return false + } + + return strings.HasPrefix("ROLE_", asString) } // Add it to the library @@ -250,6 +372,93 @@ Now to use in your json schema: {"type": "string", "format": "role"} ```` +Another example would be to check if the provided integer matches an id on database: + +JSON schema: +```json +{"type": "integer", "format": "ValidUserId"} +``` + +```go +// Define the format checker +type ValidUserIdFormatChecker struct {} + +// Ensure it meets the gojsonschema.FormatChecker interface +func (f ValidUserIdFormatChecker) IsFormat(input interface{}) bool { + + asFloat64, ok := input.(float64) // Numbers are always float64 here + if ok == false { + return false + } + + // XXX + // do the magic on the database looking for the int(asFloat64) + + return true +} + +// Add it to the library +gojsonschema.FormatCheckers.Add("ValidUserId", ValidUserIdFormatChecker{}) +```` + +Formats can also be removed, for example if you want to override one of the formats that is defined by default. + +```go +gojsonschema.FormatCheckers.Remove("hostname") +``` + + +## Additional custom validation +After the validation has run and you have the results, you may add additional +errors using `Result.AddError`. This is useful to maintain the same format within the resultset instead +of having to add special exceptions for your own errors. Below is an example. + +```go +type AnswerInvalidError struct { + gojsonschema.ResultErrorFields +} + +func newAnswerInvalidError(context *gojsonschema.JsonContext, value interface{}, details gojsonschema.ErrorDetails) *AnswerInvalidError { + err := AnswerInvalidError{} + err.SetContext(context) + err.SetType("custom_invalid_error") + // it is important to use SetDescriptionFormat() as this is used to call SetDescription() after it has been parsed + // using the description of err will be overridden by this. + err.SetDescriptionFormat("Answer to the Ultimate Question of Life, the Universe, and Everything is {{.answer}}") + err.SetValue(value) + err.SetDetails(details) + + return &err +} + +func main() { + // ... + schema, err := gojsonschema.NewSchema(schemaLoader) + result, err := gojsonschema.Validate(schemaLoader, documentLoader) + + if true { // some validation + jsonContext := gojsonschema.NewJsonContext("question", nil) + errDetail := gojsonschema.ErrorDetails{ + "answer": 42, + } + result.AddError( + newAnswerInvalidError( + gojsonschema.NewJsonContext("answer", jsonContext), + 52, + errDetail, + ), + errDetail, + ) + } + + return result, err + +} +``` + +This is especially useful if you want to add validation beyond what the +json schema drafts can provide such business specific logic. + ## Uses gojsonschema uses the following test suite : diff --git a/vendor/github.com/xeipuuv/gojsonschema/draft.go b/vendor/github.com/xeipuuv/gojsonschema/draft.go new file mode 100644 index 00000000..61298e7a --- /dev/null +++ b/vendor/github.com/xeipuuv/gojsonschema/draft.go @@ -0,0 +1,125 @@ +// Copyright 2018 johandorland ( https://github.com/johandorland ) +// +// 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. + +package gojsonschema + +import ( + "errors" + "math" + "reflect" + + "github.com/xeipuuv/gojsonreference" +) + +// Draft is a JSON-schema draft version +type Draft int + +// Supported Draft versions +const ( + Draft4 Draft = 4 + Draft6 Draft = 6 + Draft7 Draft = 7 + Hybrid Draft = math.MaxInt32 +) + +type draftConfig struct { + Version Draft + MetaSchemaURL string + MetaSchema string +} +type draftConfigs []draftConfig + +var drafts draftConfigs + +func init() { + drafts = []draftConfig{ + { + Version: Draft4, + MetaSchemaURL: "http://json-schema.org/draft-04/schema", + MetaSchema: `{"id":"http://json-schema.org/draft-04/schema#","$schema":"http://json-schema.org/draft-04/schema#","description":"Core schema meta-schema","definitions":{"schemaArray":{"type":"array","minItems":1,"items":{"$ref":"#"}},"positiveInteger":{"type":"integer","minimum":0},"positiveIntegerDefault0":{"allOf":[{"$ref":"#/definitions/positiveInteger"},{"default":0}]},"simpleTypes":{"enum":["array","boolean","integer","null","number","object","string"]},"stringArray":{"type":"array","items":{"type":"string"},"minItems":1,"uniqueItems":true}},"type":"object","properties":{"id":{"type":"string"},"$schema":{"type":"string"},"title":{"type":"string"},"description":{"type":"string"},"default":{},"multipleOf":{"type":"number","minimum":0,"exclusiveMinimum":true},"maximum":{"type":"number"},"exclusiveMaximum":{"type":"boolean","default":false},"minimum":{"type":"number"},"exclusiveMinimum":{"type":"boolean","default":false},"maxLength":{"$ref":"#/definitions/positiveInteger"},"minLength":{"$ref":"#/definitions/positiveIntegerDefault0"},"pattern":{"type":"string","format":"regex"},"additionalItems":{"anyOf":[{"type":"boolean"},{"$ref":"#"}],"default":{}},"items":{"anyOf":[{"$ref":"#"},{"$ref":"#/definitions/schemaArray"}],"default":{}},"maxItems":{"$ref":"#/definitions/positiveInteger"},"minItems":{"$ref":"#/definitions/positiveIntegerDefault0"},"uniqueItems":{"type":"boolean","default":false},"maxProperties":{"$ref":"#/definitions/positiveInteger"},"minProperties":{"$ref":"#/definitions/positiveIntegerDefault0"},"required":{"$ref":"#/definitions/stringArray"},"additionalProperties":{"anyOf":[{"type":"boolean"},{"$ref":"#"}],"default":{}},"definitions":{"type":"object","additionalProperties":{"$ref":"#"},"default":{}},"properties":{"type":"object","additionalProperties":{"$ref":"#"},"default":{}},"patternProperties":{"type":"object","additionalProperties":{"$ref":"#"},"default":{}},"dependencies":{"type":"object","additionalProperties":{"anyOf":[{"$ref":"#"},{"$ref":"#/definitions/stringArray"}]}},"enum":{"type":"array","minItems":1,"uniqueItems":true},"type":{"anyOf":[{"$ref":"#/definitions/simpleTypes"},{"type":"array","items":{"$ref":"#/definitions/simpleTypes"},"minItems":1,"uniqueItems":true}]},"format":{"type":"string"},"allOf":{"$ref":"#/definitions/schemaArray"},"anyOf":{"$ref":"#/definitions/schemaArray"},"oneOf":{"$ref":"#/definitions/schemaArray"},"not":{"$ref":"#"}},"dependencies":{"exclusiveMaximum":["maximum"],"exclusiveMinimum":["minimum"]},"default":{}}`, + }, + { + Version: Draft6, + MetaSchemaURL: "http://json-schema.org/draft-06/schema", + MetaSchema: `{"$schema":"http://json-schema.org/draft-06/schema#","$id":"http://json-schema.org/draft-06/schema#","title":"Core schema meta-schema","definitions":{"schemaArray":{"type":"array","minItems":1,"items":{"$ref":"#"}},"nonNegativeInteger":{"type":"integer","minimum":0},"nonNegativeIntegerDefault0":{"allOf":[{"$ref":"#/definitions/nonNegativeInteger"},{"default":0}]},"simpleTypes":{"enum":["array","boolean","integer","null","number","object","string"]},"stringArray":{"type":"array","items":{"type":"string"},"uniqueItems":true,"default":[]}},"type":["object","boolean"],"properties":{"$id":{"type":"string","format":"uri-reference"},"$schema":{"type":"string","format":"uri"},"$ref":{"type":"string","format":"uri-reference"},"title":{"type":"string"},"description":{"type":"string"},"default":{},"examples":{"type":"array","items":{}},"multipleOf":{"type":"number","exclusiveMinimum":0},"maximum":{"type":"number"},"exclusiveMaximum":{"type":"number"},"minimum":{"type":"number"},"exclusiveMinimum":{"type":"number"},"maxLength":{"$ref":"#/definitions/nonNegativeInteger"},"minLength":{"$ref":"#/definitions/nonNegativeIntegerDefault0"},"pattern":{"type":"string","format":"regex"},"additionalItems":{"$ref":"#"},"items":{"anyOf":[{"$ref":"#"},{"$ref":"#/definitions/schemaArray"}],"default":{}},"maxItems":{"$ref":"#/definitions/nonNegativeInteger"},"minItems":{"$ref":"#/definitions/nonNegativeIntegerDefault0"},"uniqueItems":{"type":"boolean","default":false},"contains":{"$ref":"#"},"maxProperties":{"$ref":"#/definitions/nonNegativeInteger"},"minProperties":{"$ref":"#/definitions/nonNegativeIntegerDefault0"},"required":{"$ref":"#/definitions/stringArray"},"additionalProperties":{"$ref":"#"},"definitions":{"type":"object","additionalProperties":{"$ref":"#"},"default":{}},"properties":{"type":"object","additionalProperties":{"$ref":"#"},"default":{}},"patternProperties":{"type":"object","additionalProperties":{"$ref":"#"},"default":{}},"dependencies":{"type":"object","additionalProperties":{"anyOf":[{"$ref":"#"},{"$ref":"#/definitions/stringArray"}]}},"propertyNames":{"$ref":"#"},"const":{},"enum":{"type":"array","minItems":1,"uniqueItems":true},"type":{"anyOf":[{"$ref":"#/definitions/simpleTypes"},{"type":"array","items":{"$ref":"#/definitions/simpleTypes"},"minItems":1,"uniqueItems":true}]},"format":{"type":"string"},"allOf":{"$ref":"#/definitions/schemaArray"},"anyOf":{"$ref":"#/definitions/schemaArray"},"oneOf":{"$ref":"#/definitions/schemaArray"},"not":{"$ref":"#"}},"default":{}}`, + }, + { + Version: Draft7, + MetaSchemaURL: "http://json-schema.org/draft-07/schema", + MetaSchema: `{"$schema":"http://json-schema.org/draft-07/schema#","$id":"http://json-schema.org/draft-07/schema#","title":"Core schema meta-schema","definitions":{"schemaArray":{"type":"array","minItems":1,"items":{"$ref":"#"}},"nonNegativeInteger":{"type":"integer","minimum":0},"nonNegativeIntegerDefault0":{"allOf":[{"$ref":"#/definitions/nonNegativeInteger"},{"default":0}]},"simpleTypes":{"enum":["array","boolean","integer","null","number","object","string"]},"stringArray":{"type":"array","items":{"type":"string"},"uniqueItems":true,"default":[]}},"type":["object","boolean"],"properties":{"$id":{"type":"string","format":"uri-reference"},"$schema":{"type":"string","format":"uri"},"$ref":{"type":"string","format":"uri-reference"},"$comment":{"type":"string"},"title":{"type":"string"},"description":{"type":"string"},"default":true,"readOnly":{"type":"boolean","default":false},"examples":{"type":"array","items":true},"multipleOf":{"type":"number","exclusiveMinimum":0},"maximum":{"type":"number"},"exclusiveMaximum":{"type":"number"},"minimum":{"type":"number"},"exclusiveMinimum":{"type":"number"},"maxLength":{"$ref":"#/definitions/nonNegativeInteger"},"minLength":{"$ref":"#/definitions/nonNegativeIntegerDefault0"},"pattern":{"type":"string","format":"regex"},"additionalItems":{"$ref":"#"},"items":{"anyOf":[{"$ref":"#"},{"$ref":"#/definitions/schemaArray"}],"default":true},"maxItems":{"$ref":"#/definitions/nonNegativeInteger"},"minItems":{"$ref":"#/definitions/nonNegativeIntegerDefault0"},"uniqueItems":{"type":"boolean","default":false},"contains":{"$ref":"#"},"maxProperties":{"$ref":"#/definitions/nonNegativeInteger"},"minProperties":{"$ref":"#/definitions/nonNegativeIntegerDefault0"},"required":{"$ref":"#/definitions/stringArray"},"additionalProperties":{"$ref":"#"},"definitions":{"type":"object","additionalProperties":{"$ref":"#"},"default":{}},"properties":{"type":"object","additionalProperties":{"$ref":"#"},"default":{}},"patternProperties":{"type":"object","additionalProperties":{"$ref":"#"},"propertyNames":{"format":"regex"},"default":{}},"dependencies":{"type":"object","additionalProperties":{"anyOf":[{"$ref":"#"},{"$ref":"#/definitions/stringArray"}]}},"propertyNames":{"$ref":"#"},"const":true,"enum":{"type":"array","items":true,"minItems":1,"uniqueItems":true},"type":{"anyOf":[{"$ref":"#/definitions/simpleTypes"},{"type":"array","items":{"$ref":"#/definitions/simpleTypes"},"minItems":1,"uniqueItems":true}]},"format":{"type":"string"},"contentMediaType":{"type":"string"},"contentEncoding":{"type":"string"},"if":{"$ref":"#"},"then":{"$ref":"#"},"else":{"$ref":"#"},"allOf":{"$ref":"#/definitions/schemaArray"},"anyOf":{"$ref":"#/definitions/schemaArray"},"oneOf":{"$ref":"#/definitions/schemaArray"},"not":{"$ref":"#"}},"default":true}`, + }, + } +} + +func (dc draftConfigs) GetMetaSchema(url string) string { + for _, config := range dc { + if config.MetaSchemaURL == url { + return config.MetaSchema + } + } + return "" +} +func (dc draftConfigs) GetDraftVersion(url string) *Draft { + for _, config := range dc { + if config.MetaSchemaURL == url { + return &config.Version + } + } + return nil +} +func (dc draftConfigs) GetSchemaURL(draft Draft) string { + for _, config := range dc { + if config.Version == draft { + return config.MetaSchemaURL + } + } + return "" +} + +func parseSchemaURL(documentNode interface{}) (string, *Draft, error) { + + if isKind(documentNode, reflect.Bool) { + return "", nil, nil + } + + if !isKind(documentNode, reflect.Map) { + return "", nil, errors.New("schema is invalid") + } + + m := documentNode.(map[string]interface{}) + + if existsMapKey(m, KEY_SCHEMA) { + if !isKind(m[KEY_SCHEMA], reflect.String) { + return "", nil, errors.New(formatErrorDescription( + Locale.MustBeOfType(), + ErrorDetails{ + "key": KEY_SCHEMA, + "type": TYPE_STRING, + }, + )) + } + + schemaReference, err := gojsonreference.NewJsonReference(m[KEY_SCHEMA].(string)) + + if err != nil { + return "", nil, err + } + + schema := schemaReference.String() + + return schema, drafts.GetDraftVersion(schema), nil + } + + return "", nil, nil +} diff --git a/vendor/github.com/xeipuuv/gojsonschema/errors.go b/vendor/github.com/xeipuuv/gojsonschema/errors.go index d39f0195..e4e9814f 100644 --- a/vendor/github.com/xeipuuv/gojsonschema/errors.go +++ b/vendor/github.com/xeipuuv/gojsonschema/errors.go @@ -6,7 +6,7 @@ import ( "text/template" ) -var errorTemplates errorTemplate = errorTemplate{template.New("errors-new"), sync.RWMutex{}} +var errorTemplates = errorTemplate{template.New("errors-new"), sync.RWMutex{}} // template.Template is not thread-safe for writing, so some locking is done // sync.RWMutex is used for efficiently locking when new templates are created @@ -16,142 +16,207 @@ type errorTemplate struct { } type ( - // RequiredError. ErrorDetails: property string + + // FalseError. ErrorDetails: - + FalseError struct { + ResultErrorFields + } + + // RequiredError indicates that a required field is missing + // ErrorDetails: property string RequiredError struct { ResultErrorFields } - // InvalidTypeError. ErrorDetails: expected, given + // InvalidTypeError indicates that a field has the incorrect type + // ErrorDetails: expected, given InvalidTypeError struct { ResultErrorFields } - // NumberAnyOfError. ErrorDetails: - + // NumberAnyOfError is produced in case of a failing "anyOf" validation + // ErrorDetails: - NumberAnyOfError struct { ResultErrorFields } - // NumberOneOfError. ErrorDetails: - + // NumberOneOfError is produced in case of a failing "oneOf" validation + // ErrorDetails: - NumberOneOfError struct { ResultErrorFields } - // NumberAllOfError. ErrorDetails: - + // NumberAllOfError is produced in case of a failing "allOf" validation + // ErrorDetails: - NumberAllOfError struct { ResultErrorFields } - // NumberNotError. ErrorDetails: - + // NumberNotError is produced if a "not" validation failed + // ErrorDetails: - NumberNotError struct { ResultErrorFields } - // MissingDependencyError. ErrorDetails: dependency + // MissingDependencyError is produced in case of a "missing dependency" problem + // ErrorDetails: dependency MissingDependencyError struct { ResultErrorFields } - // InternalError. ErrorDetails: error + // InternalError indicates an internal error + // ErrorDetails: error InternalError struct { ResultErrorFields } - // EnumError. ErrorDetails: allowed + // ConstError indicates a const error + // ErrorDetails: allowed + ConstError struct { + ResultErrorFields + } + + // EnumError indicates an enum error + // ErrorDetails: allowed EnumError struct { ResultErrorFields } - // ArrayNoAdditionalItemsError. ErrorDetails: - + // ArrayNoAdditionalItemsError is produced if additional items were found, but not allowed + // ErrorDetails: - ArrayNoAdditionalItemsError struct { ResultErrorFields } - // ArrayMinItemsError. ErrorDetails: min + // ArrayMinItemsError is produced if an array contains less items than the allowed minimum + // ErrorDetails: min ArrayMinItemsError struct { ResultErrorFields } - // ArrayMaxItemsError. ErrorDetails: max + // ArrayMaxItemsError is produced if an array contains more items than the allowed maximum + // ErrorDetails: max ArrayMaxItemsError struct { ResultErrorFields } - // ItemsMustBeUniqueError. ErrorDetails: type + // ItemsMustBeUniqueError is produced if an array requires unique items, but contains non-unique items + // ErrorDetails: type, i, j ItemsMustBeUniqueError struct { ResultErrorFields } - // ArrayMinPropertiesError. ErrorDetails: min + // ArrayContainsError is produced if an array contains invalid items + // ErrorDetails: + ArrayContainsError struct { + ResultErrorFields + } + + // ArrayMinPropertiesError is produced if an object contains less properties than the allowed minimum + // ErrorDetails: min ArrayMinPropertiesError struct { ResultErrorFields } - // ArrayMaxPropertiesError. ErrorDetails: max + // ArrayMaxPropertiesError is produced if an object contains more properties than the allowed maximum + // ErrorDetails: max ArrayMaxPropertiesError struct { ResultErrorFields } - // AdditionalPropertyNotAllowedError. ErrorDetails: property + // AdditionalPropertyNotAllowedError is produced if an object has additional properties, but not allowed + // ErrorDetails: property AdditionalPropertyNotAllowedError struct { ResultErrorFields } - // InvalidPropertyPatternError. ErrorDetails: property, pattern + // InvalidPropertyPatternError is produced if an pattern was found + // ErrorDetails: property, pattern InvalidPropertyPatternError struct { ResultErrorFields } - // StringLengthGTEError. ErrorDetails: min + // InvalidPropertyNameError is produced if an invalid-named property was found + // ErrorDetails: property + InvalidPropertyNameError struct { + ResultErrorFields + } + + // StringLengthGTEError is produced if a string is shorter than the minimum required length + // ErrorDetails: min StringLengthGTEError struct { ResultErrorFields } - // StringLengthLTEError. ErrorDetails: max + // StringLengthLTEError is produced if a string is longer than the maximum allowed length + // ErrorDetails: max StringLengthLTEError struct { ResultErrorFields } - // DoesNotMatchPatternError. ErrorDetails: pattern + // DoesNotMatchPatternError is produced if a string does not match the defined pattern + // ErrorDetails: pattern DoesNotMatchPatternError struct { ResultErrorFields } - // DoesNotMatchFormatError. ErrorDetails: format + // DoesNotMatchFormatError is produced if a string does not match the defined format + // ErrorDetails: format DoesNotMatchFormatError struct { ResultErrorFields } - // MultipleOfError. ErrorDetails: multiple + // MultipleOfError is produced if a number is not a multiple of the defined multipleOf + // ErrorDetails: multiple MultipleOfError struct { ResultErrorFields } - // NumberGTEError. ErrorDetails: min + // NumberGTEError is produced if a number is lower than the allowed minimum + // ErrorDetails: min NumberGTEError struct { ResultErrorFields } - // NumberGTError. ErrorDetails: min + // NumberGTError is produced if a number is lower than, or equal to the specified minimum, and exclusiveMinimum is set + // ErrorDetails: min NumberGTError struct { ResultErrorFields } - // NumberLTEError. ErrorDetails: max + // NumberLTEError is produced if a number is higher than the allowed maximum + // ErrorDetails: max NumberLTEError struct { ResultErrorFields } - // NumberLTError. ErrorDetails: max + // NumberLTError is produced if a number is higher than, or equal to the specified maximum, and exclusiveMaximum is set + // ErrorDetails: max NumberLTError struct { ResultErrorFields } + + // ConditionThenError is produced if a condition's "then" validation is invalid + // ErrorDetails: - + ConditionThenError struct { + ResultErrorFields + } + + // ConditionElseError is produced if a condition's "else" condition is invalid + // ErrorDetails: - + ConditionElseError struct { + ResultErrorFields + } ) // newError takes a ResultError type and sets the type, context, description, details, value, and field -func newError(err ResultError, context *jsonContext, value interface{}, locale locale, details ErrorDetails) { +func newError(err ResultError, context *JsonContext, value interface{}, locale locale, details ErrorDetails) { var t string var d string switch err.(type) { + case *FalseError: + t = "false" + d = locale.False() case *RequiredError: t = "required" d = locale.Required() @@ -176,6 +241,9 @@ func newError(err ResultError, context *jsonContext, value interface{}, locale l case *InternalError: t = "internal" d = locale.Internal() + case *ConstError: + t = "const" + d = locale.Const() case *EnumError: t = "enum" d = locale.Enum() @@ -191,6 +259,9 @@ func newError(err ResultError, context *jsonContext, value interface{}, locale l case *ItemsMustBeUniqueError: t = "unique" d = locale.Unique() + case *ArrayContainsError: + t = "contains" + d = locale.ArrayContains() case *ArrayMinPropertiesError: t = "array_min_properties" d = locale.ArrayMinProperties() @@ -203,6 +274,9 @@ func newError(err ResultError, context *jsonContext, value interface{}, locale l case *InvalidPropertyPatternError: t = "invalid_property_pattern" d = locale.InvalidPropertyPattern() + case *InvalidPropertyNameError: + t = "invalid_property_name" + d = locale.InvalidPropertyName() case *StringLengthGTEError: t = "string_gte" d = locale.StringGTE() @@ -230,19 +304,26 @@ func newError(err ResultError, context *jsonContext, value interface{}, locale l case *NumberLTError: t = "number_lt" d = locale.NumberLT() + case *ConditionThenError: + t = "condition_then" + d = locale.ConditionThen() + case *ConditionElseError: + t = "condition_else" + d = locale.ConditionElse() } err.SetType(t) err.SetContext(context) err.SetValue(value) err.SetDetails(details) + err.SetDescriptionFormat(d) details["field"] = err.Field() if _, exists := details["context"]; !exists && context != nil { details["context"] = context.String() } - err.SetDescription(formatErrorDescription(d, details)) + err.SetDescription(formatErrorDescription(err.DescriptionFormat(), details)) } // formatErrorDescription takes a string in the default text/template diff --git a/vendor/github.com/xeipuuv/gojsonschema/format_checkers.go b/vendor/github.com/xeipuuv/gojsonschema/format_checkers.go index 94bd095a..873ffc7d 100644 --- a/vendor/github.com/xeipuuv/gojsonschema/format_checkers.go +++ b/vendor/github.com/xeipuuv/gojsonschema/format_checkers.go @@ -2,17 +2,19 @@ package gojsonschema import ( "net" + "net/mail" "net/url" - "reflect" "regexp" "strings" + "sync" "time" ) type ( // FormatChecker is the interface all formatters added to FormatCheckerChain must implement FormatChecker interface { - IsFormat(input string) bool + // IsFormat checks if input has the correct format and type + IsFormat(input interface{}) bool } // FormatCheckerChain holds the formatters @@ -20,13 +22,13 @@ type ( formatters map[string]FormatChecker } - // EmailFormatter verifies email address formats + // EmailFormatChecker verifies email address formats EmailFormatChecker struct{} - // IPV4FormatChecker verifies IP addresses in the ipv4 format + // IPV4FormatChecker verifies IP addresses in the IPv4 format IPV4FormatChecker struct{} - // IPV6FormatChecker verifies IP addresses in the ipv6 format + // IPV6FormatChecker verifies IP addresses in the IPv6 format IPV6FormatChecker struct{} // DateTimeFormatChecker verifies date/time formats per RFC3339 5.6 @@ -52,12 +54,40 @@ type ( // http://tools.ietf.org/html/rfc3339#section-5.6 DateTimeFormatChecker struct{} + // DateFormatChecker verifies date formats + // + // Valid format: + // Full Date: YYYY-MM-DD + // + // Where + // YYYY = 4DIGIT year + // MM = 2DIGIT month ; 01-12 + // DD = 2DIGIT day-month ; 01-28, 01-29, 01-30, 01-31 based on month/year + DateFormatChecker struct{} + + // TimeFormatChecker verifies time formats + // + // Valid formats: + // Partial Time: HH:MM:SS + // Full Time: HH:MM:SSZ-07:00 + // + // Where + // HH = 2DIGIT hour ; 00-23 + // MM = 2DIGIT ; 00-59 + // SS = 2DIGIT ; 00-58, 00-60 based on leap second rules + // T = Literal + // Z = Literal + TimeFormatChecker struct{} + // URIFormatChecker validates a URI with a valid Scheme per RFC3986 URIFormatChecker struct{} // URIReferenceFormatChecker validates a URI or relative-reference per RFC3986 URIReferenceFormatChecker struct{} + // URITemplateFormatChecker validates a URI template per RFC6570 + URITemplateFormatChecker struct{} + // HostnameFormatChecker validates a hostname is in the correct format HostnameFormatChecker struct{} @@ -66,52 +96,78 @@ type ( // RegexFormatChecker validates a regex is in the correct format RegexFormatChecker struct{} + + // JSONPointerFormatChecker validates a JSON Pointer per RFC6901 + JSONPointerFormatChecker struct{} + + // RelativeJSONPointerFormatChecker validates a relative JSON Pointer is in the correct format + RelativeJSONPointerFormatChecker struct{} ) var ( - // Formatters holds the valid formatters, and is a public variable + // FormatCheckers holds the valid formatters, and is a public variable // so library users can add custom formatters FormatCheckers = FormatCheckerChain{ formatters: map[string]FormatChecker{ - "date-time": DateTimeFormatChecker{}, - "hostname": HostnameFormatChecker{}, - "email": EmailFormatChecker{}, - "ipv4": IPV4FormatChecker{}, - "ipv6": IPV6FormatChecker{}, - "uri": URIFormatChecker{}, - "uri-reference": URIReferenceFormatChecker{}, - "uuid": UUIDFormatChecker{}, - "regex": RegexFormatChecker{}, + "date": DateFormatChecker{}, + "time": TimeFormatChecker{}, + "date-time": DateTimeFormatChecker{}, + "hostname": HostnameFormatChecker{}, + "email": EmailFormatChecker{}, + "idn-email": EmailFormatChecker{}, + "ipv4": IPV4FormatChecker{}, + "ipv6": IPV6FormatChecker{}, + "uri": URIFormatChecker{}, + "uri-reference": URIReferenceFormatChecker{}, + "iri": URIFormatChecker{}, + "iri-reference": URIReferenceFormatChecker{}, + "uri-template": URITemplateFormatChecker{}, + "uuid": UUIDFormatChecker{}, + "regex": RegexFormatChecker{}, + "json-pointer": JSONPointerFormatChecker{}, + "relative-json-pointer": RelativeJSONPointerFormatChecker{}, }, } - // Regex credit: https://github.com/asaskevich/govalidator - rxEmail = regexp.MustCompile("^(((([a-zA-Z]|\\d|[!#\\$%&'\\*\\+\\-\\/=\\?\\^_`{\\|}~]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])+(\\.([a-zA-Z]|\\d|[!#\\$%&'\\*\\+\\-\\/=\\?\\^_`{\\|}~]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])+)*)|((\\x22)((((\\x20|\\x09)*(\\x0d\\x0a))?(\\x20|\\x09)+)?(([\\x01-\\x08\\x0b\\x0c\\x0e-\\x1f\\x7f]|\\x21|[\\x23-\\x5b]|[\\x5d-\\x7e]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])|(\\([\\x01-\\x09\\x0b\\x0c\\x0d-\\x7f]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}]))))*(((\\x20|\\x09)*(\\x0d\\x0a))?(\\x20|\\x09)+)?(\\x22)))@((([a-zA-Z]|\\d|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])|(([a-zA-Z]|\\d|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])([a-zA-Z]|\\d|-|\\.|_|~|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])*([a-zA-Z]|\\d|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])))\\.)+(([a-zA-Z]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])|(([a-zA-Z]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])([a-zA-Z]|\\d|-|\\.|_|~|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])*([a-zA-Z]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])))\\.?$") - // Regex credit: https://www.socketloop.com/tutorials/golang-validate-hostname rxHostname = regexp.MustCompile(`^([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]{0,61}[a-zA-Z0-9])(\.([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]{0,61}[a-zA-Z0-9]))*$`) + // Use a regex to make sure curly brackets are balanced properly after validating it as a AURI + rxURITemplate = regexp.MustCompile("^([^{]*({[^}]*})?)*$") + rxUUID = regexp.MustCompile("^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$") + + rxJSONPointer = regexp.MustCompile("^(?:/(?:[^~/]|~0|~1)*)*$") + + rxRelJSONPointer = regexp.MustCompile("^(?:0|[1-9][0-9]*)(?:#|(?:/(?:[^~/]|~0|~1)*)*)$") + + lock = new(sync.RWMutex) ) // Add adds a FormatChecker to the FormatCheckerChain // The name used will be the value used for the format key in your json schema func (c *FormatCheckerChain) Add(name string, f FormatChecker) *FormatCheckerChain { + lock.Lock() c.formatters[name] = f + lock.Unlock() return c } // Remove deletes a FormatChecker from the FormatCheckerChain (if it exists) func (c *FormatCheckerChain) Remove(name string) *FormatCheckerChain { + lock.Lock() delete(c.formatters, name) + lock.Unlock() return c } // Has checks to see if the FormatCheckerChain holds a FormatChecker with the given name func (c *FormatCheckerChain) Has(name string) bool { + lock.RLock() _, ok := c.formatters[name] + lock.RUnlock() return ok } @@ -119,38 +175,60 @@ func (c *FormatCheckerChain) Has(name string) bool { // IsFormat will check an input against a FormatChecker with the given name // to see if it is the correct format func (c *FormatCheckerChain) IsFormat(name string, input interface{}) bool { + lock.RLock() f, ok := c.formatters[name] + lock.RUnlock() + // If a format is unrecognized it should always pass validation if !ok { - return false + return true } - if !isKind(input, reflect.String) { + return f.IsFormat(input) +} + +// IsFormat checks if input is a correctly formatted e-mail address +func (f EmailFormatChecker) IsFormat(input interface{}) bool { + asString, ok := input.(string) + if !ok { return false } - inputString := input.(string) - - return f.IsFormat(inputString) + _, err := mail.ParseAddress(asString) + return err == nil } -func (f EmailFormatChecker) IsFormat(input string) bool { - return rxEmail.MatchString(input) -} +// IsFormat checks if input is a correctly formatted IPv4-address +func (f IPV4FormatChecker) IsFormat(input interface{}) bool { + asString, ok := input.(string) + if !ok { + return false + } -// Credit: https://github.com/asaskevich/govalidator -func (f IPV4FormatChecker) IsFormat(input string) bool { - ip := net.ParseIP(input) - return ip != nil && strings.Contains(input, ".") + // Credit: https://github.com/asaskevich/govalidator + ip := net.ParseIP(asString) + return ip != nil && strings.Contains(asString, ".") } -// Credit: https://github.com/asaskevich/govalidator -func (f IPV6FormatChecker) IsFormat(input string) bool { - ip := net.ParseIP(input) - return ip != nil && strings.Contains(input, ":") +// IsFormat checks if input is a correctly formatted IPv6=address +func (f IPV6FormatChecker) IsFormat(input interface{}) bool { + asString, ok := input.(string) + if !ok { + return false + } + + // Credit: https://github.com/asaskevich/govalidator + ip := net.ParseIP(asString) + return ip != nil && strings.Contains(asString, ":") } -func (f DateTimeFormatChecker) IsFormat(input string) bool { +// IsFormat checks if input is a correctly formatted date/time per RFC3339 5.6 +func (f DateTimeFormatChecker) IsFormat(input interface{}) bool { + asString, ok := input.(string) + if !ok { + return false + } + formats := []string{ "15:04:05", "15:04:05Z07:00", @@ -160,7 +238,7 @@ func (f DateTimeFormatChecker) IsFormat(input string) bool { } for _, format := range formats { - if _, err := time.Parse(format, input); err == nil { + if _, err := time.Parse(format, asString); err == nil { return true } } @@ -168,36 +246,123 @@ func (f DateTimeFormatChecker) IsFormat(input string) bool { return false } -func (f URIFormatChecker) IsFormat(input string) bool { - u, err := url.Parse(input) +// IsFormat checks if input is a correctly formatted date (YYYY-MM-DD) +func (f DateFormatChecker) IsFormat(input interface{}) bool { + asString, ok := input.(string) + if !ok { + return false + } + _, err := time.Parse("2006-01-02", asString) + return err == nil +} + +// IsFormat checks if input correctly formatted time (HH:MM:SS or HH:MM:SSZ-07:00) +func (f TimeFormatChecker) IsFormat(input interface{}) bool { + asString, ok := input.(string) + if !ok { + return false + } + + if _, err := time.Parse("15:04:05Z07:00", asString); err == nil { + return true + } + + _, err := time.Parse("15:04:05", asString) + return err == nil +} + +// IsFormat checks if input is correctly formatted URI with a valid Scheme per RFC3986 +func (f URIFormatChecker) IsFormat(input interface{}) bool { + asString, ok := input.(string) + if !ok { + return false + } + + u, err := url.Parse(asString) + if err != nil || u.Scheme == "" { return false } - return true + return !strings.Contains(asString, `\`) } -func (f URIReferenceFormatChecker) IsFormat(input string) bool { - _, err := url.Parse(input) - return err == nil +// IsFormat checks if input is a correctly formatted URI or relative-reference per RFC3986 +func (f URIReferenceFormatChecker) IsFormat(input interface{}) bool { + asString, ok := input.(string) + if !ok { + return false + } + + _, err := url.Parse(asString) + return err == nil && !strings.Contains(asString, `\`) +} + +// IsFormat checks if input is a correctly formatted URI template per RFC6570 +func (f URITemplateFormatChecker) IsFormat(input interface{}) bool { + asString, ok := input.(string) + if !ok { + return false + } + + u, err := url.Parse(asString) + if err != nil || strings.Contains(asString, `\`) { + return false + } + + return rxURITemplate.MatchString(u.Path) } -func (f HostnameFormatChecker) IsFormat(input string) bool { - return rxHostname.MatchString(input) && len(input) < 256 +// IsFormat checks if input is a correctly formatted hostname +func (f HostnameFormatChecker) IsFormat(input interface{}) bool { + asString, ok := input.(string) + if !ok { + return false + } + + return rxHostname.MatchString(asString) && len(asString) < 256 } -func (f UUIDFormatChecker) IsFormat(input string) bool { - return rxUUID.MatchString(input) +// IsFormat checks if input is a correctly formatted UUID +func (f UUIDFormatChecker) IsFormat(input interface{}) bool { + asString, ok := input.(string) + if !ok { + return false + } + + return rxUUID.MatchString(asString) } -// IsFormat implements FormatChecker interface. -func (f RegexFormatChecker) IsFormat(input string) bool { - if input == "" { +// IsFormat checks if input is a correctly formatted regular expression +func (f RegexFormatChecker) IsFormat(input interface{}) bool { + asString, ok := input.(string) + if !ok { + return false + } + + if asString == "" { return true } - _, err := regexp.Compile(input) - if err != nil { + _, err := regexp.Compile(asString) + return err == nil +} + +// IsFormat checks if input is a correctly formatted JSON Pointer per RFC6901 +func (f JSONPointerFormatChecker) IsFormat(input interface{}) bool { + asString, ok := input.(string) + if !ok { return false } - return true + + return rxJSONPointer.MatchString(asString) +} + +// IsFormat checks if input is a correctly formatted relative JSON Pointer +func (f RelativeJSONPointerFormatChecker) IsFormat(input interface{}) bool { + asString, ok := input.(string) + if !ok { + return false + } + + return rxRelJSONPointer.MatchString(asString) } diff --git a/vendor/github.com/xeipuuv/gojsonschema/glide.yaml b/vendor/github.com/xeipuuv/gojsonschema/glide.yaml index 7aef8c09..ab6fb867 100644 --- a/vendor/github.com/xeipuuv/gojsonschema/glide.yaml +++ b/vendor/github.com/xeipuuv/gojsonschema/glide.yaml @@ -7,6 +7,7 @@ import: - package: github.com/xeipuuv/gojsonreference -- package: github.com/stretchr/testify/assert - version: ^1.1.3 - +testImport: +- package: github.com/stretchr/testify + subpackages: + - assert diff --git a/vendor/github.com/xeipuuv/gojsonschema/go.mod b/vendor/github.com/xeipuuv/gojsonschema/go.mod new file mode 100644 index 00000000..b709d7fc --- /dev/null +++ b/vendor/github.com/xeipuuv/gojsonschema/go.mod @@ -0,0 +1,7 @@ +module github.com/xeipuuv/gojsonschema + +require ( + github.com/stretchr/testify v1.3.0 + github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f // indirect + github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 +) diff --git a/vendor/github.com/xeipuuv/gojsonschema/go.sum b/vendor/github.com/xeipuuv/gojsonschema/go.sum new file mode 100644 index 00000000..0e865ac7 --- /dev/null +++ b/vendor/github.com/xeipuuv/gojsonschema/go.sum @@ -0,0 +1,11 @@ +github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= +github.com/davecgh/go-spew v1.1.0/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= +github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f h1:J9EGpcZtP0E/raorCMxlFGSTBrsSlaDGf3jU/qvAE2c= +github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= +github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHovont7NscjpAxXsDA8S8BMYve8Y5+7cuRE7R0= +github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ= diff --git a/vendor/github.com/xeipuuv/gojsonschema/jsonContext.go b/vendor/github.com/xeipuuv/gojsonschema/jsonContext.go index fcc8d9d6..0e979707 100644 --- a/vendor/github.com/xeipuuv/gojsonschema/jsonContext.go +++ b/vendor/github.com/xeipuuv/gojsonschema/jsonContext.go @@ -26,20 +26,21 @@ package gojsonschema import "bytes" -// jsonContext implements a persistent linked-list of strings -type jsonContext struct { +// JsonContext implements a persistent linked-list of strings +type JsonContext struct { head string - tail *jsonContext + tail *JsonContext } -func newJsonContext(head string, tail *jsonContext) *jsonContext { - return &jsonContext{head, tail} +// NewJsonContext creates a new JsonContext +func NewJsonContext(head string, tail *JsonContext) *JsonContext { + return &JsonContext{head, tail} } // String displays the context in reverse. // This plays well with the data structure's persistent nature with // Cons and a json document's tree structure. -func (c *jsonContext) String(del ...string) string { +func (c *JsonContext) String(del ...string) string { byteArr := make([]byte, 0, c.stringLen()) buf := bytes.NewBuffer(byteArr) c.writeStringToBuffer(buf, del) @@ -47,7 +48,7 @@ func (c *jsonContext) String(del ...string) string { return buf.String() } -func (c *jsonContext) stringLen() int { +func (c *JsonContext) stringLen() int { length := 0 if c.tail != nil { length = c.tail.stringLen() + 1 // add 1 for "." @@ -57,7 +58,7 @@ func (c *jsonContext) stringLen() int { return length } -func (c *jsonContext) writeStringToBuffer(buf *bytes.Buffer, del []string) { +func (c *JsonContext) writeStringToBuffer(buf *bytes.Buffer, del []string) { if c.tail != nil { c.tail.writeStringToBuffer(buf, del) diff --git a/vendor/github.com/xeipuuv/gojsonschema/jsonLoader.go b/vendor/github.com/xeipuuv/gojsonschema/jsonLoader.go index a77a81e4..5d88af26 100644 --- a/vendor/github.com/xeipuuv/gojsonschema/jsonLoader.go +++ b/vendor/github.com/xeipuuv/gojsonschema/jsonLoader.go @@ -33,19 +33,18 @@ import ( "io" "io/ioutil" "net/http" + "net/url" "os" "path/filepath" "runtime" "strings" - "github.com/xeipuuv/gojsonreference" ) var osFS = osFileSystem(os.Open) -// JSON loader interface - +// JSONLoader defines the JSON loader interface type JSONLoader interface { JsonSource() interface{} LoadJSON() (interface{}, error) @@ -53,17 +52,22 @@ type JSONLoader interface { LoaderFactory() JSONLoaderFactory } +// JSONLoaderFactory defines the JSON loader factory interface type JSONLoaderFactory interface { + // New creates a new JSON loader for the given source New(source string) JSONLoader } +// DefaultJSONLoaderFactory is the default JSON loader factory type DefaultJSONLoaderFactory struct { } +// FileSystemJSONLoaderFactory is a JSON loader factory that uses http.FileSystem type FileSystemJSONLoaderFactory struct { fs http.FileSystem } +// New creates a new JSON loader for the given source func (d DefaultJSONLoaderFactory) New(source string) JSONLoader { return &jsonReferenceLoader{ fs: osFS, @@ -71,6 +75,7 @@ func (d DefaultJSONLoaderFactory) New(source string) JSONLoader { } } +// New creates a new JSON loader for the given source func (f FileSystemJSONLoaderFactory) New(source string) JSONLoader { return &jsonReferenceLoader{ fs: f.fs, @@ -81,6 +86,7 @@ func (f FileSystemJSONLoaderFactory) New(source string) JSONLoader { // osFileSystem is a functional wrapper for os.Open that implements http.FileSystem. type osFileSystem func(string) (*os.File, error) +// Opens a file with the given name func (o osFileSystem) Open(name string) (http.File, error) { return o(name) } @@ -108,7 +114,7 @@ func (l *jsonReferenceLoader) LoaderFactory() JSONLoaderFactory { } // NewReferenceLoader returns a JSON reference loader using the given source and the local OS file system. -func NewReferenceLoader(source string) *jsonReferenceLoader { +func NewReferenceLoader(source string) JSONLoader { return &jsonReferenceLoader{ fs: osFS, source: source, @@ -116,7 +122,7 @@ func NewReferenceLoader(source string) *jsonReferenceLoader { } // NewReferenceLoaderFileSystem returns a JSON reference loader using the given source and file system. -func NewReferenceLoaderFileSystem(source string, fs http.FileSystem) *jsonReferenceLoader { +func NewReferenceLoaderFileSystem(source string, fs http.FileSystem) JSONLoader { return &jsonReferenceLoader{ fs: fs, source: source, @@ -132,20 +138,24 @@ func (l *jsonReferenceLoader) LoadJSON() (interface{}, error) { return nil, err } - refToUrl := reference - refToUrl.GetUrl().Fragment = "" + refToURL := reference + refToURL.GetUrl().Fragment = "" var document interface{} if reference.HasFileScheme { - filename := strings.Replace(refToUrl.GetUrl().Path, "file://", "", -1) + filename := strings.TrimPrefix(refToURL.String(), "file://") + filename, err = url.QueryUnescape(filename) + + if err != nil { + return nil, err + } + if runtime.GOOS == "windows" { // on Windows, a file URL may have an extra leading slash, use slashes // instead of backslashes, and have spaces escaped - if strings.HasPrefix(filename, "/") { - filename = filename[1:] - } + filename = strings.TrimPrefix(filename, "/") filename = filepath.FromSlash(filename) } @@ -156,7 +166,7 @@ func (l *jsonReferenceLoader) LoadJSON() (interface{}, error) { } else { - document, err = l.loadFromHTTP(refToUrl.String()) + document, err = l.loadFromHTTP(refToURL.String()) if err != nil { return nil, err } @@ -169,6 +179,12 @@ func (l *jsonReferenceLoader) LoadJSON() (interface{}, error) { func (l *jsonReferenceLoader) loadFromHTTP(address string) (interface{}, error) { + // returned cached versions for metaschemas for drafts 4, 6 and 7 + // for performance and allow for easier offline use + if metaSchema := drafts.GetMetaSchema(address); metaSchema != "" { + return decodeJSONUsingNumber(strings.NewReader(metaSchema)) + } + resp, err := http.Get(address) if err != nil { return nil, err @@ -184,8 +200,7 @@ func (l *jsonReferenceLoader) loadFromHTTP(address string) (interface{}, error) return nil, err } - return decodeJsonUsingNumber(bytes.NewReader(bodyBuff)) - + return decodeJSONUsingNumber(bytes.NewReader(bodyBuff)) } func (l *jsonReferenceLoader) loadFromFile(path string) (interface{}, error) { @@ -200,7 +215,7 @@ func (l *jsonReferenceLoader) loadFromFile(path string) (interface{}, error) { return nil, err } - return decodeJsonUsingNumber(bytes.NewReader(bodyBuff)) + return decodeJSONUsingNumber(bytes.NewReader(bodyBuff)) } @@ -222,13 +237,14 @@ func (l *jsonStringLoader) LoaderFactory() JSONLoaderFactory { return &DefaultJSONLoaderFactory{} } -func NewStringLoader(source string) *jsonStringLoader { +// NewStringLoader creates a new JSONLoader, taking a string as source +func NewStringLoader(source string) JSONLoader { return &jsonStringLoader{source: source} } func (l *jsonStringLoader) LoadJSON() (interface{}, error) { - return decodeJsonUsingNumber(strings.NewReader(l.JsonSource().(string))) + return decodeJSONUsingNumber(strings.NewReader(l.JsonSource().(string))) } @@ -250,12 +266,13 @@ func (l *jsonBytesLoader) LoaderFactory() JSONLoaderFactory { return &DefaultJSONLoaderFactory{} } -func NewBytesLoader(source []byte) *jsonBytesLoader { +// NewBytesLoader creates a new JSONLoader, taking a `[]byte` as source +func NewBytesLoader(source []byte) JSONLoader { return &jsonBytesLoader{source: source} } func (l *jsonBytesLoader) LoadJSON() (interface{}, error) { - return decodeJsonUsingNumber(bytes.NewReader(l.JsonSource().([]byte))) + return decodeJSONUsingNumber(bytes.NewReader(l.JsonSource().([]byte))) } // JSON Go (types) loader @@ -277,7 +294,8 @@ func (l *jsonGoLoader) LoaderFactory() JSONLoaderFactory { return &DefaultJSONLoaderFactory{} } -func NewGoLoader(source interface{}) *jsonGoLoader { +// NewGoLoader creates a new JSONLoader from a given Go struct +func NewGoLoader(source interface{}) JSONLoader { return &jsonGoLoader{source: source} } @@ -290,7 +308,7 @@ func (l *jsonGoLoader) LoadJSON() (interface{}, error) { return nil, err } - return decodeJsonUsingNumber(bytes.NewReader(jsonBytes)) + return decodeJSONUsingNumber(bytes.NewReader(jsonBytes)) } @@ -298,12 +316,14 @@ type jsonIOLoader struct { buf *bytes.Buffer } -func NewReaderLoader(source io.Reader) (*jsonIOLoader, io.Reader) { +// NewReaderLoader creates a new JSON loader using the provided io.Reader +func NewReaderLoader(source io.Reader) (JSONLoader, io.Reader) { buf := &bytes.Buffer{} return &jsonIOLoader{buf: buf}, io.TeeReader(source, buf) } -func NewWriterLoader(source io.Writer) (*jsonIOLoader, io.Writer) { +// NewWriterLoader creates a new JSON loader using the provided io.Writer +func NewWriterLoader(source io.Writer) (JSONLoader, io.Writer) { buf := &bytes.Buffer{} return &jsonIOLoader{buf: buf}, io.MultiWriter(source, buf) } @@ -313,7 +333,7 @@ func (l *jsonIOLoader) JsonSource() interface{} { } func (l *jsonIOLoader) LoadJSON() (interface{}, error) { - return decodeJsonUsingNumber(l.buf) + return decodeJSONUsingNumber(l.buf) } func (l *jsonIOLoader) JsonReference() (gojsonreference.JsonReference, error) { @@ -324,7 +344,32 @@ func (l *jsonIOLoader) LoaderFactory() JSONLoaderFactory { return &DefaultJSONLoaderFactory{} } -func decodeJsonUsingNumber(r io.Reader) (interface{}, error) { +// JSON raw loader +// In case the JSON is already marshalled to interface{} use this loader +// This is used for testing as otherwise there is no guarantee the JSON is marshalled +// "properly" by using https://golang.org/pkg/encoding/json/#Decoder.UseNumber +type jsonRawLoader struct { + source interface{} +} + +// NewRawLoader creates a new JSON raw loader for the given source +func NewRawLoader(source interface{}) JSONLoader { + return &jsonRawLoader{source: source} +} +func (l *jsonRawLoader) JsonSource() interface{} { + return l.source +} +func (l *jsonRawLoader) LoadJSON() (interface{}, error) { + return l.source, nil +} +func (l *jsonRawLoader) JsonReference() (gojsonreference.JsonReference, error) { + return gojsonreference.NewJsonReference("#") +} +func (l *jsonRawLoader) LoaderFactory() JSONLoaderFactory { + return &DefaultJSONLoaderFactory{} +} + +func decodeJSONUsingNumber(r io.Reader) (interface{}, error) { var document interface{} @@ -335,7 +380,7 @@ func decodeJsonUsingNumber(r io.Reader) (interface{}, error) { if err != nil { return nil, err } - + return document, nil } diff --git a/vendor/github.com/xeipuuv/gojsonschema/locales.go b/vendor/github.com/xeipuuv/gojsonschema/locales.go index ee41484a..a416225c 100644 --- a/vendor/github.com/xeipuuv/gojsonschema/locales.go +++ b/vendor/github.com/xeipuuv/gojsonschema/locales.go @@ -28,55 +28,163 @@ package gojsonschema type ( // locale is an interface for defining custom error strings locale interface { + + // False returns a format-string for "false" schema validation errors + False() string + + // Required returns a format-string for "required" schema validation errors Required() string + + // InvalidType returns a format-string for "invalid type" schema validation errors InvalidType() string + + // NumberAnyOf returns a format-string for "anyOf" schema validation errors NumberAnyOf() string + + // NumberOneOf returns a format-string for "oneOf" schema validation errors NumberOneOf() string + + // NumberAllOf returns a format-string for "allOf" schema validation errors NumberAllOf() string + + // NumberNot returns a format-string to format a NumberNotError NumberNot() string + + // MissingDependency returns a format-string for "missing dependency" schema validation errors MissingDependency() string + + // Internal returns a format-string for internal errors Internal() string + + // Const returns a format-string to format a ConstError + Const() string + + // Enum returns a format-string to format an EnumError Enum() string + + // ArrayNotEnoughItems returns a format-string to format an error for arrays having not enough items to match positional list of schema ArrayNotEnoughItems() string + + // ArrayNoAdditionalItems returns a format-string to format an ArrayNoAdditionalItemsError ArrayNoAdditionalItems() string + + // ArrayMinItems returns a format-string to format an ArrayMinItemsError ArrayMinItems() string + + // ArrayMaxItems returns a format-string to format an ArrayMaxItemsError ArrayMaxItems() string + + // Unique returns a format-string to format an ItemsMustBeUniqueError Unique() string + + // ArrayContains returns a format-string to format an ArrayContainsError + ArrayContains() string + + // ArrayMinProperties returns a format-string to format an ArrayMinPropertiesError ArrayMinProperties() string + + // ArrayMaxProperties returns a format-string to format an ArrayMaxPropertiesError ArrayMaxProperties() string + + // AdditionalPropertyNotAllowed returns a format-string to format an AdditionalPropertyNotAllowedError AdditionalPropertyNotAllowed() string + + // InvalidPropertyPattern returns a format-string to format an InvalidPropertyPatternError InvalidPropertyPattern() string + + // InvalidPropertyName returns a format-string to format an InvalidPropertyNameError + InvalidPropertyName() string + + // StringGTE returns a format-string to format an StringLengthGTEError StringGTE() string + + // StringLTE returns a format-string to format an StringLengthLTEError StringLTE() string + + // DoesNotMatchPattern returns a format-string to format an DoesNotMatchPatternError DoesNotMatchPattern() string + + // DoesNotMatchFormat returns a format-string to format an DoesNotMatchFormatError DoesNotMatchFormat() string + + // MultipleOf returns a format-string to format an MultipleOfError MultipleOf() string + + // NumberGTE returns a format-string to format an NumberGTEError NumberGTE() string + + // NumberGT returns a format-string to format an NumberGTError NumberGT() string + + // NumberLTE returns a format-string to format an NumberLTEError NumberLTE() string + + // NumberLT returns a format-string to format an NumberLTError NumberLT() string // Schema validations + + // RegexPattern returns a format-string to format a regex-pattern error RegexPattern() string + + // GreaterThanZero returns a format-string to format an error where a number must be greater than zero GreaterThanZero() string + + // MustBeOfA returns a format-string to format an error where a value is of the wrong type MustBeOfA() string + + // MustBeOfAn returns a format-string to format an error where a value is of the wrong type MustBeOfAn() string + + // CannotBeUsedWithout returns a format-string to format a "cannot be used without" error CannotBeUsedWithout() string + + // CannotBeGT returns a format-string to format an error where a value are greater than allowed CannotBeGT() string + + // MustBeOfType returns a format-string to format an error where a value does not match the required type MustBeOfType() string + + // MustBeValidRegex returns a format-string to format an error where a regex is invalid MustBeValidRegex() string + + // MustBeValidFormat returns a format-string to format an error where a value does not match the expected format MustBeValidFormat() string + + // MustBeGTEZero returns a format-string to format an error where a value must be greater or equal than 0 MustBeGTEZero() string + + // KeyCannotBeGreaterThan returns a format-string to format an error where a key is greater than the maximum allowed KeyCannotBeGreaterThan() string + + // KeyItemsMustBeOfType returns a format-string to format an error where a key is of the wrong type KeyItemsMustBeOfType() string + + // KeyItemsMustBeUnique returns a format-string to format an error where keys are not unique KeyItemsMustBeUnique() string + + // ReferenceMustBeCanonical returns a format-string to format a "reference must be canonical" error ReferenceMustBeCanonical() string + + // NotAValidType returns a format-string to format an invalid type error NotAValidType() string + + // Duplicated returns a format-string to format an error where types are duplicated Duplicated() string + + // HttpBadStatus returns a format-string for errors when loading a schema using HTTP HttpBadStatus() string + + // ParseError returns a format-string for JSON parsing errors ParseError() string - // ErrorFormat + // ConditionThen returns a format-string for ConditionThenError errors + ConditionThen() string + + // ConditionElse returns a format-string for ConditionElseError errors + ConditionElse() string + + // ErrorFormat returns a format string for errors ErrorFormat() string } @@ -84,198 +192,276 @@ type ( DefaultLocale struct{} ) +// False returns a format-string for "false" schema validation errors +func (l DefaultLocale) False() string { + return "False always fails validation" +} + +// Required returns a format-string for "required" schema validation errors func (l DefaultLocale) Required() string { return `{{.property}} is required` } +// InvalidType returns a format-string for "invalid type" schema validation errors func (l DefaultLocale) InvalidType() string { return `Invalid type. Expected: {{.expected}}, given: {{.given}}` } +// NumberAnyOf returns a format-string for "anyOf" schema validation errors func (l DefaultLocale) NumberAnyOf() string { return `Must validate at least one schema (anyOf)` } +// NumberOneOf returns a format-string for "oneOf" schema validation errors func (l DefaultLocale) NumberOneOf() string { return `Must validate one and only one schema (oneOf)` } +// NumberAllOf returns a format-string for "allOf" schema validation errors func (l DefaultLocale) NumberAllOf() string { return `Must validate all the schemas (allOf)` } +// NumberNot returns a format-string to format a NumberNotError func (l DefaultLocale) NumberNot() string { return `Must not validate the schema (not)` } +// MissingDependency returns a format-string for "missing dependency" schema validation errors func (l DefaultLocale) MissingDependency() string { return `Has a dependency on {{.dependency}}` } +// Internal returns a format-string for internal errors func (l DefaultLocale) Internal() string { return `Internal Error {{.error}}` } +// Const returns a format-string to format a ConstError +func (l DefaultLocale) Const() string { + return `{{.field}} does not match: {{.allowed}}` +} + +// Enum returns a format-string to format an EnumError func (l DefaultLocale) Enum() string { return `{{.field}} must be one of the following: {{.allowed}}` } +// ArrayNoAdditionalItems returns a format-string to format an ArrayNoAdditionalItemsError func (l DefaultLocale) ArrayNoAdditionalItems() string { return `No additional items allowed on array` } +// ArrayNotEnoughItems returns a format-string to format an error for arrays having not enough items to match positional list of schema func (l DefaultLocale) ArrayNotEnoughItems() string { return `Not enough items on array to match positional list of schema` } +// ArrayMinItems returns a format-string to format an ArrayMinItemsError func (l DefaultLocale) ArrayMinItems() string { return `Array must have at least {{.min}} items` } +// ArrayMaxItems returns a format-string to format an ArrayMaxItemsError func (l DefaultLocale) ArrayMaxItems() string { return `Array must have at most {{.max}} items` } +// Unique returns a format-string to format an ItemsMustBeUniqueError func (l DefaultLocale) Unique() string { - return `{{.type}} items must be unique` + return `{{.type}} items[{{.i}},{{.j}}] must be unique` +} + +// ArrayContains returns a format-string to format an ArrayContainsError +func (l DefaultLocale) ArrayContains() string { + return `At least one of the items must match` } +// ArrayMinProperties returns a format-string to format an ArrayMinPropertiesError func (l DefaultLocale) ArrayMinProperties() string { return `Must have at least {{.min}} properties` } +// ArrayMaxProperties returns a format-string to format an ArrayMaxPropertiesError func (l DefaultLocale) ArrayMaxProperties() string { return `Must have at most {{.max}} properties` } +// AdditionalPropertyNotAllowed returns a format-string to format an AdditionalPropertyNotAllowedError func (l DefaultLocale) AdditionalPropertyNotAllowed() string { return `Additional property {{.property}} is not allowed` } +// InvalidPropertyPattern returns a format-string to format an InvalidPropertyPatternError func (l DefaultLocale) InvalidPropertyPattern() string { return `Property "{{.property}}" does not match pattern {{.pattern}}` } +// InvalidPropertyName returns a format-string to format an InvalidPropertyNameError +func (l DefaultLocale) InvalidPropertyName() string { + return `Property name of "{{.property}}" does not match` +} + +// StringGTE returns a format-string to format an StringLengthGTEError func (l DefaultLocale) StringGTE() string { return `String length must be greater than or equal to {{.min}}` } +// StringLTE returns a format-string to format an StringLengthLTEError func (l DefaultLocale) StringLTE() string { return `String length must be less than or equal to {{.max}}` } +// DoesNotMatchPattern returns a format-string to format an DoesNotMatchPatternError func (l DefaultLocale) DoesNotMatchPattern() string { return `Does not match pattern '{{.pattern}}'` } +// DoesNotMatchFormat returns a format-string to format an DoesNotMatchFormatError func (l DefaultLocale) DoesNotMatchFormat() string { return `Does not match format '{{.format}}'` } +// MultipleOf returns a format-string to format an MultipleOfError func (l DefaultLocale) MultipleOf() string { return `Must be a multiple of {{.multiple}}` } +// NumberGTE returns the format string to format a NumberGTEError func (l DefaultLocale) NumberGTE() string { return `Must be greater than or equal to {{.min}}` } +// NumberGT returns the format string to format a NumberGTError func (l DefaultLocale) NumberGT() string { return `Must be greater than {{.min}}` } +// NumberLTE returns the format string to format a NumberLTEError func (l DefaultLocale) NumberLTE() string { return `Must be less than or equal to {{.max}}` } +// NumberLT returns the format string to format a NumberLTError func (l DefaultLocale) NumberLT() string { return `Must be less than {{.max}}` } // Schema validators + +// RegexPattern returns a format-string to format a regex-pattern error func (l DefaultLocale) RegexPattern() string { return `Invalid regex pattern '{{.pattern}}'` } +// GreaterThanZero returns a format-string to format an error where a number must be greater than zero func (l DefaultLocale) GreaterThanZero() string { return `{{.number}} must be strictly greater than 0` } +// MustBeOfA returns a format-string to format an error where a value is of the wrong type func (l DefaultLocale) MustBeOfA() string { return `{{.x}} must be of a {{.y}}` } +// MustBeOfAn returns a format-string to format an error where a value is of the wrong type func (l DefaultLocale) MustBeOfAn() string { return `{{.x}} must be of an {{.y}}` } +// CannotBeUsedWithout returns a format-string to format a "cannot be used without" error func (l DefaultLocale) CannotBeUsedWithout() string { return `{{.x}} cannot be used without {{.y}}` } +// CannotBeGT returns a format-string to format an error where a value are greater than allowed func (l DefaultLocale) CannotBeGT() string { return `{{.x}} cannot be greater than {{.y}}` } +// MustBeOfType returns a format-string to format an error where a value does not match the required type func (l DefaultLocale) MustBeOfType() string { return `{{.key}} must be of type {{.type}}` } +// MustBeValidRegex returns a format-string to format an error where a regex is invalid func (l DefaultLocale) MustBeValidRegex() string { return `{{.key}} must be a valid regex` } +// MustBeValidFormat returns a format-string to format an error where a value does not match the expected format func (l DefaultLocale) MustBeValidFormat() string { return `{{.key}} must be a valid format {{.given}}` } +// MustBeGTEZero returns a format-string to format an error where a value must be greater or equal than 0 func (l DefaultLocale) MustBeGTEZero() string { return `{{.key}} must be greater than or equal to 0` } +// KeyCannotBeGreaterThan returns a format-string to format an error where a value is greater than the maximum allowed func (l DefaultLocale) KeyCannotBeGreaterThan() string { return `{{.key}} cannot be greater than {{.y}}` } +// KeyItemsMustBeOfType returns a format-string to format an error where a key is of the wrong type func (l DefaultLocale) KeyItemsMustBeOfType() string { return `{{.key}} items must be {{.type}}` } +// KeyItemsMustBeUnique returns a format-string to format an error where keys are not unique func (l DefaultLocale) KeyItemsMustBeUnique() string { return `{{.key}} items must be unique` } +// ReferenceMustBeCanonical returns a format-string to format a "reference must be canonical" error func (l DefaultLocale) ReferenceMustBeCanonical() string { return `Reference {{.reference}} must be canonical` } +// NotAValidType returns a format-string to format an invalid type error func (l DefaultLocale) NotAValidType() string { return `has a primitive type that is NOT VALID -- given: {{.given}} Expected valid values are:{{.expected}}` } +// Duplicated returns a format-string to format an error where types are duplicated func (l DefaultLocale) Duplicated() string { return `{{.type}} type is duplicated` } +// HttpBadStatus returns a format-string for errors when loading a schema using HTTP func (l DefaultLocale) HttpBadStatus() string { return `Could not read schema from HTTP, response status is {{.status}}` } +// ErrorFormat returns a format string for errors // Replacement options: field, description, context, value func (l DefaultLocale) ErrorFormat() string { return `{{.field}}: {{.description}}` } -//Parse error +// ParseError returns a format-string for JSON parsing errors func (l DefaultLocale) ParseError() string { - return `Expected: %expected%, given: Invalid JSON` + return `Expected: {{.expected}}, given: Invalid JSON` +} + +// ConditionThen returns a format-string for ConditionThenError errors +// If/Else +func (l DefaultLocale) ConditionThen() string { + return `Must validate "then" as "if" was valid` +} + +// ConditionElse returns a format-string for ConditionElseError errors +func (l DefaultLocale) ConditionElse() string { + return `Must validate "else" as "if" was not valid` } +// constants const ( STRING_NUMBER = "number" STRING_ARRAY_OF_STRINGS = "array of strings" STRING_ARRAY_OF_SCHEMAS = "array of schemas" - STRING_SCHEMA = "schema" + STRING_SCHEMA = "valid schema" STRING_SCHEMA_OR_ARRAY_OF_STRINGS = "schema or array of strings" STRING_PROPERTIES = "properties" STRING_DEPENDENCY = "dependency" diff --git a/vendor/github.com/xeipuuv/gojsonschema/result.go b/vendor/github.com/xeipuuv/gojsonschema/result.go index 6ad56ae8..0a017914 100644 --- a/vendor/github.com/xeipuuv/gojsonschema/result.go +++ b/vendor/github.com/xeipuuv/gojsonschema/result.go @@ -37,17 +37,34 @@ type ( // ResultError is the interface that library errors must implement ResultError interface { + // Field returns the field name without the root context + // i.e. firstName or person.firstName instead of (root).firstName or (root).person.firstName Field() string + // SetType sets the error-type SetType(string) + // Type returns the error-type Type() string - SetContext(*jsonContext) - Context() *jsonContext + // SetContext sets the JSON-context for the error + SetContext(*JsonContext) + // Context returns the JSON-context of the error + Context() *JsonContext + // SetDescription sets a description for the error SetDescription(string) + // Description returns the description of the error Description() string + // SetDescriptionFormat sets the format for the description in the default text/template format + SetDescriptionFormat(string) + // DescriptionFormat returns the format for the description in the default text/template format + DescriptionFormat() string + // SetValue sets the value related to the error SetValue(interface{}) + // Value returns the value related to the error Value() interface{} + // SetDetails sets the details specific to the error SetDetails(ErrorDetails) + // Details returns details about the error Details() ErrorDetails + // String returns a string representation of the error String() string } @@ -55,13 +72,15 @@ type ( // ResultErrorFields implements the ResultError interface, so custom errors // can be defined by just embedding this type ResultErrorFields struct { - errorType string // A string with the type of error (i.e. invalid_type) - context *jsonContext // Tree like notation of the part that failed the validation. ex (root).a.b ... - description string // A human readable error message - value interface{} // Value given by the JSON file that is the source of the error - details ErrorDetails + errorType string // A string with the type of error (i.e. invalid_type) + context *JsonContext // Tree like notation of the part that failed the validation. ex (root).a.b ... + description string // A human readable error message + descriptionFormat string // A format for human readable error message + value interface{} // Value given by the JSON file that is the source of the error + details ErrorDetails } + // Result holds the result of a validation Result struct { errors []ResultError // Scores how well the validation matched. Useful in generating @@ -70,58 +89,73 @@ type ( } ) -// Field outputs the field name without the root context +// Field returns the field name without the root context // i.e. firstName or person.firstName instead of (root).firstName or (root).person.firstName func (v *ResultErrorFields) Field() string { - if p, ok := v.Details()["property"]; ok { - if str, isString := p.(string); isString { - return str - } - } - return strings.TrimPrefix(v.context.String(), STRING_ROOT_SCHEMA_PROPERTY+".") } +// SetType sets the error-type func (v *ResultErrorFields) SetType(errorType string) { v.errorType = errorType } +// Type returns the error-type func (v *ResultErrorFields) Type() string { return v.errorType } -func (v *ResultErrorFields) SetContext(context *jsonContext) { +// SetContext sets the JSON-context for the error +func (v *ResultErrorFields) SetContext(context *JsonContext) { v.context = context } -func (v *ResultErrorFields) Context() *jsonContext { +// Context returns the JSON-context of the error +func (v *ResultErrorFields) Context() *JsonContext { return v.context } +// SetDescription sets a description for the error func (v *ResultErrorFields) SetDescription(description string) { v.description = description } +// Description returns the description of the error func (v *ResultErrorFields) Description() string { return v.description } +// SetDescriptionFormat sets the format for the description in the default text/template format +func (v *ResultErrorFields) SetDescriptionFormat(descriptionFormat string) { + v.descriptionFormat = descriptionFormat +} + +// DescriptionFormat returns the format for the description in the default text/template format +func (v *ResultErrorFields) DescriptionFormat() string { + return v.descriptionFormat +} + +// SetValue sets the value related to the error func (v *ResultErrorFields) SetValue(value interface{}) { v.value = value } +// Value returns the value related to the error func (v *ResultErrorFields) Value() interface{} { return v.value } +// SetDetails sets the details specific to the error func (v *ResultErrorFields) SetDetails(details ErrorDetails) { v.details = details } +// Details returns details about the error func (v *ResultErrorFields) Details() ErrorDetails { return v.details } +// String returns a string representation of the error func (v ResultErrorFields) String() string { // as a fallback, the value is displayed go style valueString := fmt.Sprintf("%v", v.value) @@ -130,7 +164,7 @@ func (v ResultErrorFields) String() string { if v.value == nil { valueString = TYPE_NULL } else { - if vs, err := marshalToJsonString(v.value); err == nil { + if vs, err := marshalToJSONString(v.value); err == nil { if vs == nil { valueString = TYPE_NULL } else { @@ -147,15 +181,29 @@ func (v ResultErrorFields) String() string { }) } +// Valid indicates if no errors were found func (v *Result) Valid() bool { return len(v.errors) == 0 } +// Errors returns the errors that were found func (v *Result) Errors() []ResultError { return v.errors } -func (v *Result) addError(err ResultError, context *jsonContext, value interface{}, details ErrorDetails) { +// AddError appends a fully filled error to the error set +// SetDescription() will be called with the result of the parsed err.DescriptionFormat() +func (v *Result) AddError(err ResultError, details ErrorDetails) { + if _, exists := details["context"]; !exists && err.Context() != nil { + details["context"] = err.Context().String() + } + + err.SetDescription(formatErrorDescription(err.DescriptionFormat(), details)) + + v.errors = append(v.errors, err) +} + +func (v *Result) addInternalError(err ResultError, context *JsonContext, value interface{}, details ErrorDetails) { newError(err, context, value, Locale, details) v.errors = append(v.errors, err) v.score -= 2 // results in a net -1 when added to the +1 we get at the end of the validation function diff --git a/vendor/github.com/xeipuuv/gojsonschema/schema.go b/vendor/github.com/xeipuuv/gojsonschema/schema.go index cc6cdbc0..9e93cd79 100644 --- a/vendor/github.com/xeipuuv/gojsonschema/schema.go +++ b/vendor/github.com/xeipuuv/gojsonschema/schema.go @@ -27,8 +27,8 @@ package gojsonschema import ( - // "encoding/json" "errors" + "math/big" "reflect" "regexp" "text/template" @@ -45,42 +45,12 @@ var ( ErrorTemplateFuncs template.FuncMap ) +// NewSchema instances a schema using the given JSONLoader func NewSchema(l JSONLoader) (*Schema, error) { - ref, err := l.JsonReference() - if err != nil { - return nil, err - } - - d := Schema{} - d.pool = newSchemaPool(l.LoaderFactory()) - d.documentReference = ref - d.referencePool = newSchemaReferencePool() - - var doc interface{} - if ref.String() != "" { - // Get document from schema pool - spd, err := d.pool.GetDocument(d.documentReference) - if err != nil { - return nil, err - } - doc = spd.Document - } else { - // Load JSON directly - doc, err = l.LoadJSON() - if err != nil { - return nil, err - } - d.pool.SetStandaloneDocument(doc) - } - - err = d.parse(doc) - if err != nil { - return nil, err - } - - return &d, nil + return NewSchemaLoader().Compile(l) } +// Schema holds a schema type Schema struct { documentReference gojsonreference.JsonReference rootSchema *subSchema @@ -88,11 +58,12 @@ type Schema struct { referencePool *schemaReferencePool } -func (d *Schema) parse(document interface{}) error { - d.rootSchema = &subSchema{property: STRING_ROOT_SCHEMA_PROPERTY} +func (d *Schema) parse(document interface{}, draft Draft) error { + d.rootSchema = &subSchema{property: STRING_ROOT_SCHEMA_PROPERTY, draft: &draft} return d.parseSchema(document, d.rootSchema) } +// SetRootSchemaName sets the root-schema name func (d *Schema) SetRootSchemaName(name string) { d.rootSchema.property = name } @@ -105,6 +76,20 @@ func (d *Schema) SetRootSchemaName(name string) { // func (d *Schema) parseSchema(documentNode interface{}, currentSchema *subSchema) error { + if currentSchema.draft == nil { + if currentSchema.parent == nil { + return errors.New("Draft not set") + } + currentSchema.draft = currentSchema.parent.draft + } + + // As of draft 6 "true" is equivalent to an empty schema "{}" and false equals "{"not":{}}" + if *currentSchema.draft >= Draft6 && isKind(documentNode, reflect.Bool) { + b := documentNode.(bool) + currentSchema.pass = &b + return nil + } + if !isKind(documentNode, reflect.Map) { return errors.New(formatErrorDescription( Locale.ParseError(), @@ -116,81 +101,67 @@ func (d *Schema) parseSchema(documentNode interface{}, currentSchema *subSchema) m := documentNode.(map[string]interface{}) - if currentSchema == d.rootSchema { + if currentSchema.parent == nil { currentSchema.ref = &d.documentReference + currentSchema.id = &d.documentReference } - // $subSchema - if existsMapKey(m, KEY_SCHEMA) { - if !isKind(m[KEY_SCHEMA], reflect.String) { - return errors.New(formatErrorDescription( - Locale.InvalidType(), - ErrorDetails{ - "expected": TYPE_STRING, - "given": KEY_SCHEMA, - }, - )) - } - schemaRef := m[KEY_SCHEMA].(string) - schemaReference, err := gojsonreference.NewJsonReference(schemaRef) - currentSchema.subSchema = &schemaReference - if err != nil { - return err - } + if currentSchema.id == nil && currentSchema.parent != nil { + currentSchema.id = currentSchema.parent.id } - // $ref - if existsMapKey(m, KEY_REF) && !isKind(m[KEY_REF], reflect.String) { + // In draft 6 the id keyword was renamed to $id + // Hybrid mode uses the old id by default + var keyID string + + switch *currentSchema.draft { + case Draft4: + keyID = KEY_ID + case Hybrid: + keyID = KEY_ID_NEW + if existsMapKey(m, KEY_ID) { + keyID = KEY_ID + } + default: + keyID = KEY_ID_NEW + } + if existsMapKey(m, keyID) && !isKind(m[keyID], reflect.String) { return errors.New(formatErrorDescription( Locale.InvalidType(), ErrorDetails{ "expected": TYPE_STRING, - "given": KEY_REF, + "given": keyID, }, )) } - if k, ok := m[KEY_REF].(string); ok { - + if k, ok := m[keyID].(string); ok { jsonReference, err := gojsonreference.NewJsonReference(k) if err != nil { return err } - - if jsonReference.HasFullUrl { - currentSchema.ref = &jsonReference + if currentSchema == d.rootSchema { + currentSchema.id = &jsonReference } else { - inheritedReference, err := currentSchema.ref.Inherits(jsonReference) + ref, err := currentSchema.parent.id.Inherits(jsonReference) if err != nil { return err } - - currentSchema.ref = inheritedReference - } - - if sch, ok := d.referencePool.Get(currentSchema.ref.String() + k); ok { - currentSchema.refSchema = sch - - } else { - err := d.parseReference(documentNode, currentSchema, k) - if err != nil { - return err - } - - return nil + currentSchema.id = ref } } // definitions if existsMapKey(m, KEY_DEFINITIONS) { - if isKind(m[KEY_DEFINITIONS], reflect.Map) { - currentSchema.definitions = make(map[string]*subSchema) - for dk, dv := range m[KEY_DEFINITIONS].(map[string]interface{}) { - if isKind(dv, reflect.Map) { - newSchema := &subSchema{property: KEY_DEFINITIONS, parent: currentSchema, ref: currentSchema.ref} - currentSchema.definitions[dk] = newSchema + if isKind(m[KEY_DEFINITIONS], reflect.Map, reflect.Bool) { + for _, dv := range m[KEY_DEFINITIONS].(map[string]interface{}) { + if isKind(dv, reflect.Map, reflect.Bool) { + + newSchema := &subSchema{property: KEY_DEFINITIONS, parent: currentSchema} + err := d.parseSchema(dv, newSchema) + if err != nil { - return errors.New(err.Error()) + return err } } else { return errors.New(formatErrorDescription( @@ -214,20 +185,6 @@ func (d *Schema) parseSchema(documentNode interface{}, currentSchema *subSchema) } - // id - if existsMapKey(m, KEY_ID) && !isKind(m[KEY_ID], reflect.String) { - return errors.New(formatErrorDescription( - Locale.InvalidType(), - ErrorDetails{ - "expected": TYPE_STRING, - "given": KEY_ID, - }, - )) - } - if k, ok := m[KEY_ID].(string); ok { - currentSchema.id = &k - } - // title if existsMapKey(m, KEY_TITLE) && !isKind(m[KEY_TITLE], reflect.String) { return errors.New(formatErrorDescription( @@ -256,6 +213,39 @@ func (d *Schema) parseSchema(documentNode interface{}, currentSchema *subSchema) currentSchema.description = &k } + // $ref + if existsMapKey(m, KEY_REF) && !isKind(m[KEY_REF], reflect.String) { + return errors.New(formatErrorDescription( + Locale.InvalidType(), + ErrorDetails{ + "expected": TYPE_STRING, + "given": KEY_REF, + }, + )) + } + + if k, ok := m[KEY_REF].(string); ok { + + jsonReference, err := gojsonreference.NewJsonReference(k) + if err != nil { + return err + } + + currentSchema.ref = &jsonReference + + if sch, ok := d.referencePool.Get(currentSchema.ref.String()); ok { + currentSchema.refSchema = sch + } else { + err := d.parseReference(documentNode, currentSchema) + + if err != nil { + return err + } + + return nil + } + } + // type if existsMapKey(m, KEY_TYPE) { if isKind(m[KEY_TYPE], reflect.String) { @@ -277,8 +267,9 @@ func (d *Schema) parseSchema(documentNode interface{}, currentSchema *subSchema) "given": KEY_TYPE, }, )) - } else { - currentSchema.types.Add(typeInArray.(string)) + } + if err := currentSchema.types.Add(typeInArray.(string)); err != nil { + return err } } @@ -357,6 +348,26 @@ func (d *Schema) parseSchema(documentNode interface{}, currentSchema *subSchema) } } + // propertyNames + if existsMapKey(m, KEY_PROPERTY_NAMES) && *currentSchema.draft >= Draft6 { + if isKind(m[KEY_PROPERTY_NAMES], reflect.Map, reflect.Bool) { + newSchema := &subSchema{property: KEY_PROPERTY_NAMES, parent: currentSchema, ref: currentSchema.ref} + currentSchema.propertyNames = newSchema + err := d.parseSchema(m[KEY_PROPERTY_NAMES], newSchema) + if err != nil { + return err + } + } else { + return errors.New(formatErrorDescription( + Locale.InvalidType(), + ErrorDetails{ + "expected": STRING_SCHEMA, + "given": KEY_PATTERN_PROPERTIES, + }, + )) + } + } + // dependencies if existsMapKey(m, KEY_DEPENDENCIES) { err := d.parseDependencies(m[KEY_DEPENDENCIES], currentSchema) @@ -369,10 +380,10 @@ func (d *Schema) parseSchema(documentNode interface{}, currentSchema *subSchema) if existsMapKey(m, KEY_ITEMS) { if isKind(m[KEY_ITEMS], reflect.Slice) { for _, itemElement := range m[KEY_ITEMS].([]interface{}) { - if isKind(itemElement, reflect.Map) { + if isKind(itemElement, reflect.Map, reflect.Bool) { newSchema := &subSchema{parent: currentSchema, property: KEY_ITEMS} newSchema.ref = currentSchema.ref - currentSchema.AddItemsChild(newSchema) + currentSchema.itemsChildren = append(currentSchema.itemsChildren, newSchema) err := d.parseSchema(itemElement, newSchema) if err != nil { return err @@ -388,10 +399,10 @@ func (d *Schema) parseSchema(documentNode interface{}, currentSchema *subSchema) } currentSchema.itemsChildrenIsSingleSchema = false } - } else if isKind(m[KEY_ITEMS], reflect.Map) { + } else if isKind(m[KEY_ITEMS], reflect.Map, reflect.Bool) { newSchema := &subSchema{parent: currentSchema, property: KEY_ITEMS} newSchema.ref = currentSchema.ref - currentSchema.AddItemsChild(newSchema) + currentSchema.itemsChildren = append(currentSchema.itemsChildren, newSchema) err := d.parseSchema(m[KEY_ITEMS], newSchema) if err != nil { return err @@ -443,7 +454,7 @@ func (d *Schema) parseSchema(documentNode interface{}, currentSchema *subSchema) }, )) } - if *multipleOfValue <= 0 { + if multipleOfValue.Cmp(big.NewRat(0, 1)) <= 0 { return errors.New(formatErrorDescription( Locale.GreaterThanZero(), ErrorDetails{"number": KEY_MULTIPLE_OF}, @@ -464,20 +475,62 @@ func (d *Schema) parseSchema(documentNode interface{}, currentSchema *subSchema) } if existsMapKey(m, KEY_EXCLUSIVE_MINIMUM) { - if isKind(m[KEY_EXCLUSIVE_MINIMUM], reflect.Bool) { + switch *currentSchema.draft { + case Draft4: + if !isKind(m[KEY_EXCLUSIVE_MINIMUM], reflect.Bool) { + return errors.New(formatErrorDescription( + Locale.InvalidType(), + ErrorDetails{ + "expected": TYPE_BOOLEAN, + "given": KEY_EXCLUSIVE_MINIMUM, + }, + )) + } if currentSchema.minimum == nil { return errors.New(formatErrorDescription( Locale.CannotBeUsedWithout(), ErrorDetails{"x": KEY_EXCLUSIVE_MINIMUM, "y": KEY_MINIMUM}, )) } - exclusiveMinimumValue := m[KEY_EXCLUSIVE_MINIMUM].(bool) - currentSchema.exclusiveMinimum = exclusiveMinimumValue - } else { - return errors.New(formatErrorDescription( - Locale.MustBeOfA(), - ErrorDetails{"x": KEY_EXCLUSIVE_MINIMUM, "y": TYPE_BOOLEAN}, - )) + if m[KEY_EXCLUSIVE_MINIMUM].(bool) { + currentSchema.exclusiveMinimum = currentSchema.minimum + currentSchema.minimum = nil + } + case Hybrid: + if isKind(m[KEY_EXCLUSIVE_MINIMUM], reflect.Bool) { + if currentSchema.minimum == nil { + return errors.New(formatErrorDescription( + Locale.CannotBeUsedWithout(), + ErrorDetails{"x": KEY_EXCLUSIVE_MINIMUM, "y": KEY_MINIMUM}, + )) + } + if m[KEY_EXCLUSIVE_MINIMUM].(bool) { + currentSchema.exclusiveMinimum = currentSchema.minimum + currentSchema.minimum = nil + } + } else if isJSONNumber(m[KEY_EXCLUSIVE_MINIMUM]) { + currentSchema.exclusiveMinimum = mustBeNumber(m[KEY_EXCLUSIVE_MINIMUM]) + } else { + return errors.New(formatErrorDescription( + Locale.InvalidType(), + ErrorDetails{ + "expected": TYPE_BOOLEAN + "/" + TYPE_NUMBER, + "given": KEY_EXCLUSIVE_MINIMUM, + }, + )) + } + default: + if isJSONNumber(m[KEY_EXCLUSIVE_MINIMUM]) { + currentSchema.exclusiveMinimum = mustBeNumber(m[KEY_EXCLUSIVE_MINIMUM]) + } else { + return errors.New(formatErrorDescription( + Locale.InvalidType(), + ErrorDetails{ + "expected": TYPE_NUMBER, + "given": KEY_EXCLUSIVE_MINIMUM, + }, + )) + } } } @@ -493,29 +546,62 @@ func (d *Schema) parseSchema(documentNode interface{}, currentSchema *subSchema) } if existsMapKey(m, KEY_EXCLUSIVE_MAXIMUM) { - if isKind(m[KEY_EXCLUSIVE_MAXIMUM], reflect.Bool) { + switch *currentSchema.draft { + case Draft4: + if !isKind(m[KEY_EXCLUSIVE_MAXIMUM], reflect.Bool) { + return errors.New(formatErrorDescription( + Locale.InvalidType(), + ErrorDetails{ + "expected": TYPE_BOOLEAN, + "given": KEY_EXCLUSIVE_MAXIMUM, + }, + )) + } if currentSchema.maximum == nil { return errors.New(formatErrorDescription( Locale.CannotBeUsedWithout(), ErrorDetails{"x": KEY_EXCLUSIVE_MAXIMUM, "y": KEY_MAXIMUM}, )) } - exclusiveMaximumValue := m[KEY_EXCLUSIVE_MAXIMUM].(bool) - currentSchema.exclusiveMaximum = exclusiveMaximumValue - } else { - return errors.New(formatErrorDescription( - Locale.MustBeOfA(), - ErrorDetails{"x": KEY_EXCLUSIVE_MAXIMUM, "y": STRING_NUMBER}, - )) - } - } - - if currentSchema.minimum != nil && currentSchema.maximum != nil { - if *currentSchema.minimum > *currentSchema.maximum { - return errors.New(formatErrorDescription( - Locale.CannotBeGT(), - ErrorDetails{"x": KEY_MINIMUM, "y": KEY_MAXIMUM}, - )) + if m[KEY_EXCLUSIVE_MAXIMUM].(bool) { + currentSchema.exclusiveMaximum = currentSchema.maximum + currentSchema.maximum = nil + } + case Hybrid: + if isKind(m[KEY_EXCLUSIVE_MAXIMUM], reflect.Bool) { + if currentSchema.maximum == nil { + return errors.New(formatErrorDescription( + Locale.CannotBeUsedWithout(), + ErrorDetails{"x": KEY_EXCLUSIVE_MAXIMUM, "y": KEY_MAXIMUM}, + )) + } + if m[KEY_EXCLUSIVE_MAXIMUM].(bool) { + currentSchema.exclusiveMaximum = currentSchema.maximum + currentSchema.maximum = nil + } + } else if isJSONNumber(m[KEY_EXCLUSIVE_MAXIMUM]) { + currentSchema.exclusiveMaximum = mustBeNumber(m[KEY_EXCLUSIVE_MAXIMUM]) + } else { + return errors.New(formatErrorDescription( + Locale.InvalidType(), + ErrorDetails{ + "expected": TYPE_BOOLEAN + "/" + TYPE_NUMBER, + "given": KEY_EXCLUSIVE_MAXIMUM, + }, + )) + } + default: + if isJSONNumber(m[KEY_EXCLUSIVE_MAXIMUM]) { + currentSchema.exclusiveMaximum = mustBeNumber(m[KEY_EXCLUSIVE_MAXIMUM]) + } else { + return errors.New(formatErrorDescription( + Locale.InvalidType(), + ErrorDetails{ + "expected": TYPE_NUMBER, + "given": KEY_EXCLUSIVE_MAXIMUM, + }, + )) + } } } @@ -584,14 +670,13 @@ func (d *Schema) parseSchema(documentNode interface{}, currentSchema *subSchema) if existsMapKey(m, KEY_FORMAT) { formatString, ok := m[KEY_FORMAT].(string) - if ok && FormatCheckers.Has(formatString) { - currentSchema.format = formatString - } else { + if !ok { return errors.New(formatErrorDescription( - Locale.MustBeValidFormat(), - ErrorDetails{"key": KEY_FORMAT, "given": m[KEY_FORMAT]}, + Locale.MustBeOfType(), + ErrorDetails{"key": KEY_FORMAT, "type": TYPE_STRING}, )) } + currentSchema.format = formatString } // validation : object @@ -644,10 +729,13 @@ func (d *Schema) parseSchema(documentNode interface{}, currentSchema *subSchema) requiredValues := m[KEY_REQUIRED].([]interface{}) for _, requiredValue := range requiredValues { if isKind(requiredValue, reflect.String) { - err := currentSchema.AddRequired(requiredValue.(string)) - if err != nil { - return err + if isStringInSlice(currentSchema.required, requiredValue.(string)) { + return errors.New(formatErrorDescription( + Locale.KeyItemsMustBeUnique(), + ErrorDetails{"key": KEY_REQUIRED}, + )) } + currentSchema.required = append(currentSchema.required, requiredValue.(string)) } else { return errors.New(formatErrorDescription( Locale.KeyItemsMustBeOfType(), @@ -710,15 +798,39 @@ func (d *Schema) parseSchema(documentNode interface{}, currentSchema *subSchema) } } + if existsMapKey(m, KEY_CONTAINS) && *currentSchema.draft >= Draft6 { + newSchema := &subSchema{property: KEY_CONTAINS, parent: currentSchema, ref: currentSchema.ref} + currentSchema.contains = newSchema + err := d.parseSchema(m[KEY_CONTAINS], newSchema) + if err != nil { + return err + } + } + // validation : all + if existsMapKey(m, KEY_CONST) && *currentSchema.draft >= Draft6 { + is, err := marshalWithoutNumber(m[KEY_CONST]) + if err != nil { + return err + } + currentSchema._const = is + } + if existsMapKey(m, KEY_ENUM) { if isKind(m[KEY_ENUM], reflect.Slice) { for _, v := range m[KEY_ENUM].([]interface{}) { - err := currentSchema.AddEnum(v) + is, err := marshalWithoutNumber(v) if err != nil { return err } + if isStringInSlice(currentSchema.enum, *is) { + return errors.New(formatErrorDescription( + Locale.KeyItemsMustBeUnique(), + ErrorDetails{"key": KEY_ENUM}, + )) + } + currentSchema.enum = append(currentSchema.enum, *is) } } else { return errors.New(formatErrorDescription( @@ -734,7 +846,7 @@ func (d *Schema) parseSchema(documentNode interface{}, currentSchema *subSchema) if isKind(m[KEY_ONE_OF], reflect.Slice) { for _, v := range m[KEY_ONE_OF].([]interface{}) { newSchema := &subSchema{property: KEY_ONE_OF, parent: currentSchema, ref: currentSchema.ref} - currentSchema.AddOneOf(newSchema) + currentSchema.oneOf = append(currentSchema.oneOf, newSchema) err := d.parseSchema(v, newSchema) if err != nil { return err @@ -752,7 +864,7 @@ func (d *Schema) parseSchema(documentNode interface{}, currentSchema *subSchema) if isKind(m[KEY_ANY_OF], reflect.Slice) { for _, v := range m[KEY_ANY_OF].([]interface{}) { newSchema := &subSchema{property: KEY_ANY_OF, parent: currentSchema, ref: currentSchema.ref} - currentSchema.AddAnyOf(newSchema) + currentSchema.anyOf = append(currentSchema.anyOf, newSchema) err := d.parseSchema(v, newSchema) if err != nil { return err @@ -770,7 +882,7 @@ func (d *Schema) parseSchema(documentNode interface{}, currentSchema *subSchema) if isKind(m[KEY_ALL_OF], reflect.Slice) { for _, v := range m[KEY_ALL_OF].([]interface{}) { newSchema := &subSchema{property: KEY_ALL_OF, parent: currentSchema, ref: currentSchema.ref} - currentSchema.AddAllOf(newSchema) + currentSchema.allOf = append(currentSchema.allOf, newSchema) err := d.parseSchema(v, newSchema) if err != nil { return err @@ -785,9 +897,9 @@ func (d *Schema) parseSchema(documentNode interface{}, currentSchema *subSchema) } if existsMapKey(m, KEY_NOT) { - if isKind(m[KEY_NOT], reflect.Map) { + if isKind(m[KEY_NOT], reflect.Map, reflect.Bool) { newSchema := &subSchema{property: KEY_NOT, parent: currentSchema, ref: currentSchema.ref} - currentSchema.SetNot(newSchema) + currentSchema.not = newSchema err := d.parseSchema(m[KEY_NOT], newSchema) if err != nil { return err @@ -800,48 +912,91 @@ func (d *Schema) parseSchema(documentNode interface{}, currentSchema *subSchema) } } + if *currentSchema.draft >= Draft7 { + if existsMapKey(m, KEY_IF) { + if isKind(m[KEY_IF], reflect.Map, reflect.Bool) { + newSchema := &subSchema{property: KEY_IF, parent: currentSchema, ref: currentSchema.ref} + currentSchema._if = newSchema + err := d.parseSchema(m[KEY_IF], newSchema) + if err != nil { + return err + } + } else { + return errors.New(formatErrorDescription( + Locale.MustBeOfAn(), + ErrorDetails{"x": KEY_IF, "y": TYPE_OBJECT}, + )) + } + } + + if existsMapKey(m, KEY_THEN) { + if isKind(m[KEY_THEN], reflect.Map, reflect.Bool) { + newSchema := &subSchema{property: KEY_THEN, parent: currentSchema, ref: currentSchema.ref} + currentSchema._then = newSchema + err := d.parseSchema(m[KEY_THEN], newSchema) + if err != nil { + return err + } + } else { + return errors.New(formatErrorDescription( + Locale.MustBeOfAn(), + ErrorDetails{"x": KEY_THEN, "y": TYPE_OBJECT}, + )) + } + } + + if existsMapKey(m, KEY_ELSE) { + if isKind(m[KEY_ELSE], reflect.Map, reflect.Bool) { + newSchema := &subSchema{property: KEY_ELSE, parent: currentSchema, ref: currentSchema.ref} + currentSchema._else = newSchema + err := d.parseSchema(m[KEY_ELSE], newSchema) + if err != nil { + return err + } + } else { + return errors.New(formatErrorDescription( + Locale.MustBeOfAn(), + ErrorDetails{"x": KEY_ELSE, "y": TYPE_OBJECT}, + )) + } + } + } + return nil } -func (d *Schema) parseReference(documentNode interface{}, currentSchema *subSchema, reference string) error { - var refdDocumentNode interface{} - jsonPointer := currentSchema.ref.GetPointer() - standaloneDocument := d.pool.GetStandaloneDocument() +func (d *Schema) parseReference(documentNode interface{}, currentSchema *subSchema) error { + var ( + refdDocumentNode interface{} + dsp *schemaPoolDocument + err error + ) - if standaloneDocument != nil { + newSchema := &subSchema{property: KEY_REF, parent: currentSchema, ref: currentSchema.ref} - var err error - refdDocumentNode, _, err = jsonPointer.Get(standaloneDocument) - if err != nil { - return err - } + d.referencePool.Add(currentSchema.ref.String(), newSchema) - } else { - dsp, err := d.pool.GetDocument(*currentSchema.ref) - if err != nil { - return err - } + dsp, err = d.pool.GetDocument(*currentSchema.ref) + if err != nil { + return err + } + newSchema.id = currentSchema.ref - refdDocumentNode, _, err = jsonPointer.Get(dsp.Document) - if err != nil { - return err - } + refdDocumentNode = dsp.Document + newSchema.draft = dsp.Draft + if err != nil { + return err } - if !isKind(refdDocumentNode, reflect.Map) { + if !isKind(refdDocumentNode, reflect.Map, reflect.Bool) { return errors.New(formatErrorDescription( Locale.MustBeOfType(), ErrorDetails{"key": STRING_SCHEMA, "type": TYPE_OBJECT}, )) } - // returns the loaded referenced subSchema for the caller to update its current subSchema - newSchemaDocument := refdDocumentNode.(map[string]interface{}) - newSchema := &subSchema{property: KEY_REF, parent: currentSchema, ref: currentSchema.ref} - d.referencePool.Add(currentSchema.ref.String()+reference, newSchema) - - err := d.parseSchema(newSchemaDocument, newSchema) + err = d.parseSchema(refdDocumentNode, newSchema) if err != nil { return err } @@ -865,7 +1020,7 @@ func (d *Schema) parseProperties(documentNode interface{}, currentSchema *subSch for k := range m { schemaProperty := k newSchema := &subSchema{property: schemaProperty, parent: currentSchema, ref: currentSchema.ref} - currentSchema.AddPropertiesChild(newSchema) + currentSchema.propertiesChildren = append(currentSchema.propertiesChildren, newSchema) err := d.parseSchema(m[k], newSchema) if err != nil { return err @@ -903,13 +1058,12 @@ func (d *Schema) parseDependencies(documentNode interface{}, currentSchema *subS "type": STRING_SCHEMA_OR_ARRAY_OF_STRINGS, }, )) - } else { - valuesToRegister = append(valuesToRegister, value.(string)) } + valuesToRegister = append(valuesToRegister, value.(string)) currentSchema.dependencies[k] = valuesToRegister } - case reflect.Map: + case reflect.Map, reflect.Bool: depSchema := &subSchema{property: k, parent: currentSchema, ref: currentSchema.ref} err := d.parseSchema(m[k], depSchema) if err != nil { diff --git a/vendor/github.com/xeipuuv/gojsonschema/schemaLoader.go b/vendor/github.com/xeipuuv/gojsonschema/schemaLoader.go new file mode 100644 index 00000000..20db0c1f --- /dev/null +++ b/vendor/github.com/xeipuuv/gojsonschema/schemaLoader.go @@ -0,0 +1,206 @@ +// Copyright 2018 johandorland ( https://github.com/johandorland ) +// +// 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. + +package gojsonschema + +import ( + "bytes" + "errors" + + "github.com/xeipuuv/gojsonreference" +) + +// SchemaLoader is used to load schemas +type SchemaLoader struct { + pool *schemaPool + AutoDetect bool + Validate bool + Draft Draft +} + +// NewSchemaLoader creates a new NewSchemaLoader +func NewSchemaLoader() *SchemaLoader { + + ps := &SchemaLoader{ + pool: &schemaPool{ + schemaPoolDocuments: make(map[string]*schemaPoolDocument), + }, + AutoDetect: true, + Validate: false, + Draft: Hybrid, + } + ps.pool.autoDetect = &ps.AutoDetect + + return ps +} + +func (sl *SchemaLoader) validateMetaschema(documentNode interface{}) error { + + var ( + schema string + err error + ) + if sl.AutoDetect { + schema, _, err = parseSchemaURL(documentNode) + if err != nil { + return err + } + } + + // If no explicit "$schema" is used, use the default metaschema associated with the draft used + if schema == "" { + if sl.Draft == Hybrid { + return nil + } + schema = drafts.GetSchemaURL(sl.Draft) + } + + //Disable validation when loading the metaschema to prevent an infinite recursive loop + sl.Validate = false + + metaSchema, err := sl.Compile(NewReferenceLoader(schema)) + + if err != nil { + return err + } + + sl.Validate = true + + result := metaSchema.validateDocument(documentNode) + + if !result.Valid() { + var res bytes.Buffer + for _, err := range result.Errors() { + res.WriteString(err.String()) + res.WriteString("\n") + } + return errors.New(res.String()) + } + + return nil +} + +// AddSchemas adds an arbritrary amount of schemas to the schema cache. As this function does not require +// an explicit URL, every schema should contain an $id, so that it can be referenced by the main schema +func (sl *SchemaLoader) AddSchemas(loaders ...JSONLoader) error { + emptyRef, _ := gojsonreference.NewJsonReference("") + + for _, loader := range loaders { + doc, err := loader.LoadJSON() + + if err != nil { + return err + } + + if sl.Validate { + if err := sl.validateMetaschema(doc); err != nil { + return err + } + } + + // Directly use the Recursive function, so that it get only added to the schema pool by $id + // and not by the ref of the document as it's empty + if err = sl.pool.parseReferences(doc, emptyRef, false); err != nil { + return err + } + } + + return nil +} + +//AddSchema adds a schema under the provided URL to the schema cache +func (sl *SchemaLoader) AddSchema(url string, loader JSONLoader) error { + + ref, err := gojsonreference.NewJsonReference(url) + + if err != nil { + return err + } + + doc, err := loader.LoadJSON() + + if err != nil { + return err + } + + if sl.Validate { + if err := sl.validateMetaschema(doc); err != nil { + return err + } + } + + return sl.pool.parseReferences(doc, ref, true) +} + +// Compile loads and compiles a schema +func (sl *SchemaLoader) Compile(rootSchema JSONLoader) (*Schema, error) { + + ref, err := rootSchema.JsonReference() + + if err != nil { + return nil, err + } + + d := Schema{} + d.pool = sl.pool + d.pool.jsonLoaderFactory = rootSchema.LoaderFactory() + d.documentReference = ref + d.referencePool = newSchemaReferencePool() + + var doc interface{} + if ref.String() != "" { + // Get document from schema pool + spd, err := d.pool.GetDocument(d.documentReference) + if err != nil { + return nil, err + } + doc = spd.Document + } else { + // Load JSON directly + doc, err = rootSchema.LoadJSON() + if err != nil { + return nil, err + } + // References need only be parsed if loading JSON directly + // as pool.GetDocument already does this for us if loading by reference + err = sl.pool.parseReferences(doc, ref, true) + if err != nil { + return nil, err + } + } + + if sl.Validate { + if err := sl.validateMetaschema(doc); err != nil { + return nil, err + } + } + + draft := sl.Draft + if sl.AutoDetect { + _, detectedDraft, err := parseSchemaURL(doc) + if err != nil { + return nil, err + } + if detectedDraft != nil { + draft = *detectedDraft + } + } + + err = d.parse(doc, draft) + if err != nil { + return nil, err + } + + return &d, nil +} diff --git a/vendor/github.com/xeipuuv/gojsonschema/schemaPool.go b/vendor/github.com/xeipuuv/gojsonschema/schemaPool.go index f2ad641a..35b1cc63 100644 --- a/vendor/github.com/xeipuuv/gojsonschema/schemaPool.go +++ b/vendor/github.com/xeipuuv/gojsonschema/schemaPool.go @@ -28,82 +28,188 @@ package gojsonschema import ( "errors" + "fmt" + "reflect" "github.com/xeipuuv/gojsonreference" ) type schemaPoolDocument struct { Document interface{} + Draft *Draft } type schemaPool struct { schemaPoolDocuments map[string]*schemaPoolDocument - standaloneDocument interface{} jsonLoaderFactory JSONLoaderFactory + autoDetect *bool } -func newSchemaPool(f JSONLoaderFactory) *schemaPool { +func (p *schemaPool) parseReferences(document interface{}, ref gojsonreference.JsonReference, pooled bool) error { - p := &schemaPool{} - p.schemaPoolDocuments = make(map[string]*schemaPoolDocument) - p.standaloneDocument = nil - p.jsonLoaderFactory = f + var ( + draft *Draft + err error + reference = ref.String() + ) + // Only the root document should be added to the schema pool if pooled is true + if _, ok := p.schemaPoolDocuments[reference]; pooled && ok { + return fmt.Errorf("Reference already exists: \"%s\"", reference) + } - return p -} + if *p.autoDetect { + _, draft, err = parseSchemaURL(document) + if err != nil { + return err + } + } + + err = p.parseReferencesRecursive(document, ref, draft) -func (p *schemaPool) SetStandaloneDocument(document interface{}) { - p.standaloneDocument = document + if pooled { + p.schemaPoolDocuments[reference] = &schemaPoolDocument{Document: document, Draft: draft} + } + + return err } -func (p *schemaPool) GetStandaloneDocument() (document interface{}) { - return p.standaloneDocument +func (p *schemaPool) parseReferencesRecursive(document interface{}, ref gojsonreference.JsonReference, draft *Draft) error { + // parseReferencesRecursive parses a JSON document and resolves all $id and $ref references. + // For $ref references it takes into account the $id scope it is in and replaces + // the reference by the absolute resolved reference + + // When encountering errors it fails silently. Error handling is done when the schema + // is syntactically parsed and any error encountered here should also come up there. + switch m := document.(type) { + case []interface{}: + for _, v := range m { + p.parseReferencesRecursive(v, ref, draft) + } + case map[string]interface{}: + localRef := &ref + + keyID := KEY_ID_NEW + if existsMapKey(m, KEY_ID) { + keyID = KEY_ID + } + if existsMapKey(m, keyID) && isKind(m[keyID], reflect.String) { + jsonReference, err := gojsonreference.NewJsonReference(m[keyID].(string)) + if err == nil { + localRef, err = ref.Inherits(jsonReference) + if err == nil { + if _, ok := p.schemaPoolDocuments[localRef.String()]; ok { + return fmt.Errorf("Reference already exists: \"%s\"", localRef.String()) + } + p.schemaPoolDocuments[localRef.String()] = &schemaPoolDocument{Document: document, Draft: draft} + } + } + } + + if existsMapKey(m, KEY_REF) && isKind(m[KEY_REF], reflect.String) { + jsonReference, err := gojsonreference.NewJsonReference(m[KEY_REF].(string)) + if err == nil { + absoluteRef, err := localRef.Inherits(jsonReference) + if err == nil { + m[KEY_REF] = absoluteRef.String() + } + } + } + + for k, v := range m { + // const and enums should be interpreted literally, so ignore them + if k == KEY_CONST || k == KEY_ENUM { + continue + } + // Something like a property or a dependency is not a valid schema, as it might describe properties named "$ref", "$id" or "const", etc + // Therefore don't treat it like a schema. + if k == KEY_PROPERTIES || k == KEY_DEPENDENCIES || k == KEY_PATTERN_PROPERTIES { + if child, ok := v.(map[string]interface{}); ok { + for _, v := range child { + p.parseReferencesRecursive(v, *localRef, draft) + } + } + } else { + p.parseReferencesRecursive(v, *localRef, draft) + } + } + } + return nil } func (p *schemaPool) GetDocument(reference gojsonreference.JsonReference) (*schemaPoolDocument, error) { + var ( + spd *schemaPoolDocument + draft *Draft + ok bool + err error + ) + if internalLogEnabled { internalLog("Get Document ( %s )", reference.String()) } - var err error + // Create a deep copy, so we can remove the fragment part later on without altering the original + refToURL, _ := gojsonreference.NewJsonReference(reference.String()) - // It is not possible to load anything that is not canonical... - if !reference.IsCanonical() { - return nil, errors.New(formatErrorDescription( - Locale.ReferenceMustBeCanonical(), - ErrorDetails{"reference": reference}, - )) + // First check if the given fragment is a location independent identifier + // http://json-schema.org/latest/json-schema-core.html#rfc.section.8.2.3 + + if spd, ok = p.schemaPoolDocuments[refToURL.String()]; ok { + if internalLogEnabled { + internalLog(" From pool") + } + return spd, nil } - refToUrl := reference - refToUrl.GetUrl().Fragment = "" + // If the given reference is not a location independent identifier, + // strip the fragment and look for a document with it's base URI + + refToURL.GetUrl().Fragment = "" - var spd *schemaPoolDocument + if cachedSpd, ok := p.schemaPoolDocuments[refToURL.String()]; ok { + document, _, err := reference.GetPointer().Get(cachedSpd.Document) - // Try to find the requested document in the pool - for k := range p.schemaPoolDocuments { - if k == refToUrl.String() { - spd = p.schemaPoolDocuments[k] + if err != nil { + return nil, err } - } - if spd != nil { if internalLogEnabled { internalLog(" From pool") } + + spd = &schemaPoolDocument{Document: document, Draft: cachedSpd.Draft} + p.schemaPoolDocuments[reference.String()] = spd + return spd, nil } + // It is not possible to load anything remotely that is not canonical... + if !reference.IsCanonical() { + return nil, errors.New(formatErrorDescription( + Locale.ReferenceMustBeCanonical(), + ErrorDetails{"reference": reference.String()}, + )) + } + jsonReferenceLoader := p.jsonLoaderFactory.New(reference.String()) document, err := jsonReferenceLoader.LoadJSON() + if err != nil { return nil, err } - spd = &schemaPoolDocument{Document: document} - // add the document to the pool for potential later use - p.schemaPoolDocuments[refToUrl.String()] = spd + // add the whole document to the pool for potential re-use + p.parseReferences(document, refToURL, true) + + _, draft, _ = parseSchemaURL(document) + + // resolve the potential fragment and also cache it + document, _, err = reference.GetPointer().Get(document) + + if err != nil { + return nil, err + } - return spd, nil + return &schemaPoolDocument{Document: document, Draft: draft}, nil } diff --git a/vendor/github.com/xeipuuv/gojsonschema/schemaReferencePool.go b/vendor/github.com/xeipuuv/gojsonschema/schemaReferencePool.go index 294e36a7..6e5e1b5c 100644 --- a/vendor/github.com/xeipuuv/gojsonschema/schemaReferencePool.go +++ b/vendor/github.com/xeipuuv/gojsonschema/schemaReferencePool.go @@ -62,6 +62,7 @@ func (p *schemaReferencePool) Add(ref string, sch *subSchema) { if internalLogEnabled { internalLog(fmt.Sprintf("Add Schema Reference %s to pool", ref)) } - - p.documents[ref] = sch + if _, ok := p.documents[ref]; !ok { + p.documents[ref] = sch + } } diff --git a/vendor/github.com/xeipuuv/gojsonschema/subSchema.go b/vendor/github.com/xeipuuv/gojsonschema/subSchema.go index 9ddbb5fc..ec779812 100644 --- a/vendor/github.com/xeipuuv/gojsonschema/subSchema.go +++ b/vendor/github.com/xeipuuv/gojsonschema/subSchema.go @@ -27,16 +27,16 @@ package gojsonschema import ( - "errors" - "regexp" - "strings" - "github.com/xeipuuv/gojsonreference" + "math/big" + "regexp" ) +// Constants const ( - KEY_SCHEMA = "$subSchema" - KEY_ID = "$id" + KEY_SCHEMA = "$schema" + KEY_ID = "id" + KEY_ID_NEW = "$id" KEY_REF = "$ref" KEY_TITLE = "title" KEY_DESCRIPTION = "description" @@ -46,6 +46,7 @@ const ( KEY_PROPERTIES = "properties" KEY_PATTERN_PROPERTIES = "patternProperties" KEY_ADDITIONAL_PROPERTIES = "additionalProperties" + KEY_PROPERTY_NAMES = "propertyNames" KEY_DEFINITIONS = "definitions" KEY_MULTIPLE_OF = "multipleOf" KEY_MINIMUM = "minimum" @@ -63,22 +64,31 @@ const ( KEY_MIN_ITEMS = "minItems" KEY_MAX_ITEMS = "maxItems" KEY_UNIQUE_ITEMS = "uniqueItems" + KEY_CONTAINS = "contains" + KEY_CONST = "const" KEY_ENUM = "enum" KEY_ONE_OF = "oneOf" KEY_ANY_OF = "anyOf" KEY_ALL_OF = "allOf" KEY_NOT = "not" + KEY_IF = "if" + KEY_THEN = "then" + KEY_ELSE = "else" ) type subSchema struct { + draft *Draft // basic subSchema meta properties - id *string + id *gojsonreference.JsonReference title *string description *string property string + // Quick pass/fail for boolean schemas + pass *bool + // Types associated with the subSchema types jsonSchemaType @@ -86,23 +96,19 @@ type subSchema struct { ref *gojsonreference.JsonReference // Schema referenced refSchema *subSchema - // Json reference - subSchema *gojsonreference.JsonReference // hierarchy parent *subSchema - definitions map[string]*subSchema - definitionsChildren []*subSchema itemsChildren []*subSchema itemsChildrenIsSingleSchema bool propertiesChildren []*subSchema // validation : number / integer - multipleOf *float64 - maximum *float64 - exclusiveMaximum bool - minimum *float64 - exclusiveMinimum bool + multipleOf *big.Rat + maximum *big.Rat + exclusiveMaximum *big.Rat + minimum *big.Rat + exclusiveMinimum *big.Rat // validation : string minLength *int @@ -118,110 +124,26 @@ type subSchema struct { dependencies map[string]interface{} additionalProperties interface{} patternProperties map[string]*subSchema + propertyNames *subSchema // validation : array minItems *int maxItems *int uniqueItems bool + contains *subSchema additionalItems interface{} // validation : all - enum []string + _const *string //const is a golang keyword + enum []string // validation : subSchema oneOf []*subSchema anyOf []*subSchema allOf []*subSchema not *subSchema -} - -func (s *subSchema) AddEnum(i interface{}) error { - - is, err := marshalToJsonString(i) - if err != nil { - return err - } - - if isStringInSlice(s.enum, *is) { - return errors.New(formatErrorDescription( - Locale.KeyItemsMustBeUnique(), - ErrorDetails{"key": KEY_ENUM}, - )) - } - - s.enum = append(s.enum, *is) - - return nil -} - -func (s *subSchema) ContainsEnum(i interface{}) (bool, error) { - - is, err := marshalToJsonString(i) - if err != nil { - return false, err - } - - return isStringInSlice(s.enum, *is), nil -} - -func (s *subSchema) AddOneOf(subSchema *subSchema) { - s.oneOf = append(s.oneOf, subSchema) -} - -func (s *subSchema) AddAllOf(subSchema *subSchema) { - s.allOf = append(s.allOf, subSchema) -} - -func (s *subSchema) AddAnyOf(subSchema *subSchema) { - s.anyOf = append(s.anyOf, subSchema) -} - -func (s *subSchema) SetNot(subSchema *subSchema) { - s.not = subSchema -} - -func (s *subSchema) AddRequired(value string) error { - - if isStringInSlice(s.required, value) { - return errors.New(formatErrorDescription( - Locale.KeyItemsMustBeUnique(), - ErrorDetails{"key": KEY_REQUIRED}, - )) - } - - s.required = append(s.required, value) - - return nil -} - -func (s *subSchema) AddDefinitionChild(child *subSchema) { - s.definitionsChildren = append(s.definitionsChildren, child) -} - -func (s *subSchema) AddItemsChild(child *subSchema) { - s.itemsChildren = append(s.itemsChildren, child) -} - -func (s *subSchema) AddPropertiesChild(child *subSchema) { - s.propertiesChildren = append(s.propertiesChildren, child) -} - -func (s *subSchema) PatternPropertiesString() string { - - if s.patternProperties == nil || len(s.patternProperties) == 0 { - return STRING_UNDEFINED // should never happen - } - - patternPropertiesKeySlice := []string{} - for pk := range s.patternProperties { - patternPropertiesKeySlice = append(patternPropertiesKeySlice, `"`+pk+`"`) - } - - if len(patternPropertiesKeySlice) == 1 { - return patternPropertiesKeySlice[0] - } - - return "[" + strings.Join(patternPropertiesKeySlice, ",") + "]" - + _if *subSchema // if/else are golang keywords + _then *subSchema + _else *subSchema } diff --git a/vendor/github.com/xeipuuv/gojsonschema/types.go b/vendor/github.com/xeipuuv/gojsonschema/types.go index 952d22ef..0e6fd517 100644 --- a/vendor/github.com/xeipuuv/gojsonschema/types.go +++ b/vendor/github.com/xeipuuv/gojsonschema/types.go @@ -25,6 +25,7 @@ package gojsonschema +// Type constants const ( TYPE_ARRAY = `array` TYPE_BOOLEAN = `boolean` @@ -35,7 +36,10 @@ const ( TYPE_STRING = `string` ) +// JSON_TYPES hosts the list of type that are supported in JSON var JSON_TYPES []string + +// SCHEMA_TYPES hosts the list of type that are supported in schemas var SCHEMA_TYPES []string func init() { diff --git a/vendor/github.com/xeipuuv/gojsonschema/utils.go b/vendor/github.com/xeipuuv/gojsonschema/utils.go index 26cf75eb..a17d22e3 100644 --- a/vendor/github.com/xeipuuv/gojsonschema/utils.go +++ b/vendor/github.com/xeipuuv/gojsonschema/utils.go @@ -27,19 +27,23 @@ package gojsonschema import ( "encoding/json" - "fmt" - "math" + "math/big" "reflect" - "strconv" ) -func isKind(what interface{}, kind reflect.Kind) bool { +func isKind(what interface{}, kinds ...reflect.Kind) bool { target := what - if isJsonNumber(what) { + if isJSONNumber(what) { // JSON Numbers are strings! target = *mustBeNumber(what) } - return reflect.ValueOf(target).Kind() == kind + targetKind := reflect.ValueOf(target).Kind() + for _, kind := range kinds { + if targetKind == kind { + return true + } + } + return false } func existsMapKey(m map[string]interface{}, k string) bool { @@ -56,7 +60,17 @@ func isStringInSlice(s []string, what string) bool { return false } -func marshalToJsonString(value interface{}) (*string, error) { +// indexStringInSlice returns the index of the first instance of 'what' in s or -1 if it is not found in s. +func indexStringInSlice(s []string, what string) int { + for i := range s { + if s[i] == what { + return i + } + } + return -1 +} + +func marshalToJSONString(value interface{}) (*string, error) { mBytes, err := json.Marshal(value) if err != nil { @@ -67,7 +81,29 @@ func marshalToJsonString(value interface{}) (*string, error) { return &sBytes, nil } -func isJsonNumber(what interface{}) bool { +func marshalWithoutNumber(value interface{}) (*string, error) { + + // The JSON is decoded using https://golang.org/pkg/encoding/json/#Decoder.UseNumber + // This means the numbers are internally still represented as strings and therefore 1.00 is unequal to 1 + // One way to eliminate these differences is to decode and encode the JSON one more time without Decoder.UseNumber + // so that these differences in representation are removed + + jsonString, err := marshalToJSONString(value) + if err != nil { + return nil, err + } + + var document interface{} + + err = json.Unmarshal([]byte(*jsonString), &document) + if err != nil { + return nil, err + } + + return marshalToJSONString(document) +} + +func isJSONNumber(what interface{}) bool { switch what.(type) { @@ -78,48 +114,31 @@ func isJsonNumber(what interface{}) bool { return false } -func checkJsonNumber(what interface{}) (isValidFloat64 bool, isValidInt64 bool, isValidInt32 bool) { +func checkJSONInteger(what interface{}) (isInt bool) { jsonNumber := what.(json.Number) - f64, errFloat64 := jsonNumber.Float64() - s64 := strconv.FormatFloat(f64, 'f', -1, 64) - _, errInt64 := strconv.ParseInt(s64, 10, 64) + bigFloat, isValidNumber := new(big.Rat).SetString(string(jsonNumber)) - isValidFloat64 = errFloat64 == nil - isValidInt64 = errInt64 == nil - - _, errInt32 := strconv.ParseInt(s64, 10, 32) - isValidInt32 = isValidInt64 && errInt32 == nil - - return + return isValidNumber && bigFloat.IsInt() } // same as ECMA Number.MAX_SAFE_INTEGER and Number.MIN_SAFE_INTEGER const ( - max_json_float = float64(1<<53 - 1) // 9007199254740991.0 2^53 - 1 - min_json_float = -float64(1<<53 - 1) //-9007199254740991.0 -2^53 - 1 + maxJSONFloat = float64(1<<53 - 1) // 9007199254740991.0 2^53 - 1 + minJSONFloat = -float64(1<<53 - 1) //-9007199254740991.0 -2^53 - 1 ) -func isFloat64AnInteger(f float64) bool { - - if math.IsNaN(f) || math.IsInf(f, 0) || f < min_json_float || f > max_json_float { - return false - } - - return f == float64(int64(f)) || f == float64(uint64(f)) -} - func mustBeInteger(what interface{}) *int { - if isJsonNumber(what) { + if isJSONNumber(what) { number := what.(json.Number) - _, _, isValidInt32 := checkJsonNumber(number) + isInt := checkJSONInteger(number) - if isValidInt32 { + if isInt { int64Value, err := number.Int64() if err != nil { @@ -128,9 +147,6 @@ func mustBeInteger(what interface{}) *int { int32Value := int(int64Value) return &int32Value - - } else { - return nil } } @@ -138,47 +154,20 @@ func mustBeInteger(what interface{}) *int { return nil } -func mustBeNumber(what interface{}) *float64 { - - if isJsonNumber(what) { +func mustBeNumber(what interface{}) *big.Rat { + if isJSONNumber(what) { number := what.(json.Number) - float64Value, err := number.Float64() - - if err == nil { - return &float64Value - } else { - return nil + float64Value, success := new(big.Rat).SetString(string(number)) + if success { + return float64Value } - } return nil } -// formats a number so that it is displayed as the smallest string possible -func resultErrorFormatJsonNumber(n json.Number) string { - - if int64Value, err := n.Int64(); err == nil { - return fmt.Sprintf("%d", int64Value) - } - - float64Value, _ := n.Float64() - - return fmt.Sprintf("%g", float64Value) -} - -// formats a number so that it is displayed as the smallest string possible -func resultErrorFormatNumber(n float64) string { - - if isFloat64AnInteger(n) { - return fmt.Sprintf("%d", int64(n)) - } - - return fmt.Sprintf("%g", n) -} - func convertDocumentNode(val interface{}) interface{} { if lval, ok := val.([]interface{}); ok { diff --git a/vendor/github.com/xeipuuv/gojsonschema/validation.go b/vendor/github.com/xeipuuv/gojsonschema/validation.go index 6140bd8c..74091bca 100644 --- a/vendor/github.com/xeipuuv/gojsonschema/validation.go +++ b/vendor/github.com/xeipuuv/gojsonschema/validation.go @@ -27,6 +27,7 @@ package gojsonschema import ( "encoding/json" + "math/big" "reflect" "regexp" "strconv" @@ -34,56 +35,59 @@ import ( "unicode/utf8" ) +// Validate loads and validates a JSON schema func Validate(ls JSONLoader, ld JSONLoader) (*Result, error) { - - var err error - // load schema - schema, err := NewSchema(ls) if err != nil { return nil, err } - - // begine validation - return schema.Validate(ld) - } +// Validate loads and validates a JSON document func (v *Schema) Validate(l JSONLoader) (*Result, error) { - - // load document - root, err := l.LoadJSON() if err != nil { return nil, err } + return v.validateDocument(root), nil +} - // begin validation - +func (v *Schema) validateDocument(root interface{}) *Result { result := &Result{} - context := newJsonContext(STRING_CONTEXT_ROOT, nil) + context := NewJsonContext(STRING_CONTEXT_ROOT, nil) v.rootSchema.validateRecursive(v.rootSchema, root, result, context) - - return result, nil - + return result } -func (v *subSchema) subValidateWithContext(document interface{}, context *jsonContext) *Result { +func (v *subSchema) subValidateWithContext(document interface{}, context *JsonContext) *Result { result := &Result{} v.validateRecursive(v, document, result, context) return result } // Walker function to validate the json recursively against the subSchema -func (v *subSchema) validateRecursive(currentSubSchema *subSchema, currentNode interface{}, result *Result, context *jsonContext) { +func (v *subSchema) validateRecursive(currentSubSchema *subSchema, currentNode interface{}, result *Result, context *JsonContext) { if internalLogEnabled { internalLog("validateRecursive %s", context.String()) internalLog(" %v", currentNode) } + // Handle true/false schema as early as possible as all other fields will be nil + if currentSubSchema.pass != nil { + if !*currentSubSchema.pass { + result.addInternalError( + new(FalseError), + context, + currentNode, + ErrorDetails{}, + ) + } + return + } + // Handle referenced schemas, returns directly when a $ref is found if currentSubSchema.refSchema != nil { v.validateRecursive(currentSubSchema.refSchema, currentNode, result, context) @@ -93,7 +97,7 @@ func (v *subSchema) validateRecursive(currentSubSchema *subSchema, currentNode i // Check for null value if currentNode == nil { if currentSubSchema.types.IsTyped() && !currentSubSchema.types.Contains(TYPE_NULL) { - result.addError( + result.addInternalError( new(InvalidTypeError), context, currentNode, @@ -110,22 +114,22 @@ func (v *subSchema) validateRecursive(currentSubSchema *subSchema, currentNode i } else { // Not a null value - if isJsonNumber(currentNode) { + if isJSONNumber(currentNode) { value := currentNode.(json.Number) - _, isValidInt64, _ := checkJsonNumber(value) + isInt := checkJSONInteger(value) - validType := currentSubSchema.types.Contains(TYPE_NUMBER) || (isValidInt64 && currentSubSchema.types.Contains(TYPE_INTEGER)) + validType := currentSubSchema.types.Contains(TYPE_NUMBER) || (isInt && currentSubSchema.types.Contains(TYPE_INTEGER)) if currentSubSchema.types.IsTyped() && !validType { givenType := TYPE_INTEGER - if !isValidInt64 { + if !isInt { givenType = TYPE_NUMBER } - result.addError( + result.addInternalError( new(InvalidTypeError), context, currentNode, @@ -154,7 +158,7 @@ func (v *subSchema) validateRecursive(currentSubSchema *subSchema, currentNode i case reflect.Slice: if currentSubSchema.types.IsTyped() && !currentSubSchema.types.Contains(TYPE_ARRAY) { - result.addError( + result.addInternalError( new(InvalidTypeError), context, currentNode, @@ -177,7 +181,7 @@ func (v *subSchema) validateRecursive(currentSubSchema *subSchema, currentNode i case reflect.Map: if currentSubSchema.types.IsTyped() && !currentSubSchema.types.Contains(TYPE_OBJECT) { - result.addError( + result.addInternalError( new(InvalidTypeError), context, currentNode, @@ -202,7 +206,7 @@ func (v *subSchema) validateRecursive(currentSubSchema *subSchema, currentNode i for _, pSchema := range currentSubSchema.propertiesChildren { nextNode, ok := castCurrentNode[pSchema.property] if ok { - subContext := newJsonContext(pSchema.property, context) + subContext := NewJsonContext(pSchema.property, context) v.validateRecursive(pSchema, nextNode, result, subContext) } } @@ -212,7 +216,7 @@ func (v *subSchema) validateRecursive(currentSubSchema *subSchema, currentNode i case reflect.Bool: if currentSubSchema.types.IsTyped() && !currentSubSchema.types.Contains(TYPE_BOOLEAN) { - result.addError( + result.addInternalError( new(InvalidTypeError), context, currentNode, @@ -234,7 +238,7 @@ func (v *subSchema) validateRecursive(currentSubSchema *subSchema, currentNode i case reflect.String: if currentSubSchema.types.IsTyped() && !currentSubSchema.types.Contains(TYPE_STRING) { - result.addError( + result.addInternalError( new(InvalidTypeError), context, currentNode, @@ -263,7 +267,7 @@ func (v *subSchema) validateRecursive(currentSubSchema *subSchema, currentNode i } // Different kinds of validation there, subSchema / common / array / object / string... -func (v *subSchema) validateSchema(currentSubSchema *subSchema, currentNode interface{}, result *Result, context *jsonContext) { +func (v *subSchema) validateSchema(currentSubSchema *subSchema, currentNode interface{}, result *Result, context *JsonContext) { if internalLogEnabled { internalLog("validateSchema %s", context.String()) @@ -287,7 +291,7 @@ func (v *subSchema) validateSchema(currentSubSchema *subSchema, currentNode inte } if !validatedAnyOf { - result.addError(new(NumberAnyOfError), context, currentNode, ErrorDetails{}) + result.addInternalError(new(NumberAnyOfError), context, currentNode, ErrorDetails{}) if bestValidationResult != nil { // add error messages of closest matching subSchema as @@ -313,7 +317,7 @@ func (v *subSchema) validateSchema(currentSubSchema *subSchema, currentNode inte if nbValidated != 1 { - result.addError(new(NumberOneOfError), context, currentNode, ErrorDetails{}) + result.addInternalError(new(NumberOneOfError), context, currentNode, ErrorDetails{}) if nbValidated == 0 { // add error messages of closest matching subSchema as @@ -336,14 +340,14 @@ func (v *subSchema) validateSchema(currentSubSchema *subSchema, currentNode inte } if nbValidated != len(currentSubSchema.allOf) { - result.addError(new(NumberAllOfError), context, currentNode, ErrorDetails{}) + result.addInternalError(new(NumberAllOfError), context, currentNode, ErrorDetails{}) } } if currentSubSchema.not != nil { validationResult := currentSubSchema.not.subValidateWithContext(currentNode, context) if validationResult.Valid() { - result.addError(new(NumberNotError), context, currentNode, ErrorDetails{}) + result.addInternalError(new(NumberNotError), context, currentNode, ErrorDetails{}) } } @@ -356,7 +360,7 @@ func (v *subSchema) validateSchema(currentSubSchema *subSchema, currentNode inte case []string: for _, dependOnKey := range dependency { if _, dependencyResolved := currentNode.(map[string]interface{})[dependOnKey]; !dependencyResolved { - result.addError( + result.addInternalError( new(MissingDependencyError), context, currentNode, @@ -367,31 +371,65 @@ func (v *subSchema) validateSchema(currentSubSchema *subSchema, currentNode inte case *subSchema: dependency.validateRecursive(dependency, currentNode, result, context) - } } } } } + if currentSubSchema._if != nil { + validationResultIf := currentSubSchema._if.subValidateWithContext(currentNode, context) + if currentSubSchema._then != nil && validationResultIf.Valid() { + validationResultThen := currentSubSchema._then.subValidateWithContext(currentNode, context) + if !validationResultThen.Valid() { + result.addInternalError(new(ConditionThenError), context, currentNode, ErrorDetails{}) + result.mergeErrors(validationResultThen) + } + } + if currentSubSchema._else != nil && !validationResultIf.Valid() { + validationResultElse := currentSubSchema._else.subValidateWithContext(currentNode, context) + if !validationResultElse.Valid() { + result.addInternalError(new(ConditionElseError), context, currentNode, ErrorDetails{}) + result.mergeErrors(validationResultElse) + } + } + } + result.incrementScore() } -func (v *subSchema) validateCommon(currentSubSchema *subSchema, value interface{}, result *Result, context *jsonContext) { +func (v *subSchema) validateCommon(currentSubSchema *subSchema, value interface{}, result *Result, context *JsonContext) { if internalLogEnabled { internalLog("validateCommon %s", context.String()) internalLog(" %v", value) } + // const: + if currentSubSchema._const != nil { + vString, err := marshalWithoutNumber(value) + if err != nil { + result.addInternalError(new(InternalError), context, value, ErrorDetails{"error": err}) + } + if *vString != *currentSubSchema._const { + result.addInternalError(new(ConstError), + context, + value, + ErrorDetails{ + "allowed": *currentSubSchema._const, + }, + ) + } + } + // enum: if len(currentSubSchema.enum) > 0 { - has, err := currentSubSchema.ContainsEnum(value) + vString, err := marshalWithoutNumber(value) if err != nil { - result.addError(new(InternalError), context, value, ErrorDetails{"error": err}) + result.addInternalError(new(InternalError), context, value, ErrorDetails{"error": err}) } - if !has { - result.addError( + if !isStringInSlice(currentSubSchema.enum, *vString) { + result.addInternalError( new(EnumError), context, value, @@ -405,7 +443,7 @@ func (v *subSchema) validateCommon(currentSubSchema *subSchema, value interface{ result.incrementScore() } -func (v *subSchema) validateArray(currentSubSchema *subSchema, value []interface{}, result *Result, context *jsonContext) { +func (v *subSchema) validateArray(currentSubSchema *subSchema, value []interface{}, result *Result, context *JsonContext) { if internalLogEnabled { internalLog("validateArray %s", context.String()) @@ -417,7 +455,7 @@ func (v *subSchema) validateArray(currentSubSchema *subSchema, value []interface // TODO explain if currentSubSchema.itemsChildrenIsSingleSchema { for i := range value { - subContext := newJsonContext(strconv.Itoa(i), context) + subContext := NewJsonContext(strconv.Itoa(i), context) validationResult := currentSubSchema.itemsChildren[0].subValidateWithContext(value[i], subContext) result.mergeErrors(validationResult) } @@ -428,7 +466,7 @@ func (v *subSchema) validateArray(currentSubSchema *subSchema, value []interface // while we have both schemas and values, check them against each other for i := 0; i != nbItems && i != nbValues; i++ { - subContext := newJsonContext(strconv.Itoa(i), context) + subContext := NewJsonContext(strconv.Itoa(i), context) validationResult := currentSubSchema.itemsChildren[i].subValidateWithContext(value[i], subContext) result.mergeErrors(validationResult) } @@ -440,12 +478,12 @@ func (v *subSchema) validateArray(currentSubSchema *subSchema, value []interface switch currentSubSchema.additionalItems.(type) { case bool: if !currentSubSchema.additionalItems.(bool) { - result.addError(new(ArrayNoAdditionalItemsError), context, value, ErrorDetails{}) + result.addInternalError(new(ArrayNoAdditionalItemsError), context, value, ErrorDetails{}) } case *subSchema: additionalItemSchema := currentSubSchema.additionalItems.(*subSchema) for i := nbItems; i != nbValues; i++ { - subContext := newJsonContext(strconv.Itoa(i), context) + subContext := NewJsonContext(strconv.Itoa(i), context) validationResult := additionalItemSchema.subValidateWithContext(value[i], subContext) result.mergeErrors(validationResult) } @@ -457,7 +495,7 @@ func (v *subSchema) validateArray(currentSubSchema *subSchema, value []interface // minItems & maxItems if currentSubSchema.minItems != nil { if nbValues < int(*currentSubSchema.minItems) { - result.addError( + result.addInternalError( new(ArrayMinItemsError), context, value, @@ -467,7 +505,7 @@ func (v *subSchema) validateArray(currentSubSchema *subSchema, value []interface } if currentSubSchema.maxItems != nil { if nbValues > int(*currentSubSchema.maxItems) { - result.addError( + result.addInternalError( new(ArrayMaxItemsError), context, value, @@ -478,28 +516,60 @@ func (v *subSchema) validateArray(currentSubSchema *subSchema, value []interface // uniqueItems: if currentSubSchema.uniqueItems { - var stringifiedItems []string - for _, v := range value { - vString, err := marshalToJsonString(v) + var stringifiedItems = make(map[string]int) + for j, v := range value { + vString, err := marshalWithoutNumber(v) if err != nil { - result.addError(new(InternalError), context, value, ErrorDetails{"err": err}) + result.addInternalError(new(InternalError), context, value, ErrorDetails{"err": err}) } - if isStringInSlice(stringifiedItems, *vString) { - result.addError( + if i, ok := stringifiedItems[*vString]; ok { + result.addInternalError( new(ItemsMustBeUniqueError), context, value, - ErrorDetails{"type": TYPE_ARRAY}, + ErrorDetails{"type": TYPE_ARRAY, "i": i, "j": j}, ) } - stringifiedItems = append(stringifiedItems, *vString) + stringifiedItems[*vString] = j + } + } + + // contains: + + if currentSubSchema.contains != nil { + validatedOne := false + var bestValidationResult *Result + + for i, v := range value { + subContext := NewJsonContext(strconv.Itoa(i), context) + + validationResult := currentSubSchema.contains.subValidateWithContext(v, subContext) + if validationResult.Valid() { + validatedOne = true + break + } else { + if bestValidationResult == nil || validationResult.score > bestValidationResult.score { + bestValidationResult = validationResult + } + } + } + if !validatedOne { + result.addInternalError( + new(ArrayContainsError), + context, + value, + ErrorDetails{}, + ) + if bestValidationResult != nil { + result.mergeErrors(bestValidationResult) + } } } result.incrementScore() } -func (v *subSchema) validateObject(currentSubSchema *subSchema, value map[string]interface{}, result *Result, context *jsonContext) { +func (v *subSchema) validateObject(currentSubSchema *subSchema, value map[string]interface{}, result *Result, context *JsonContext) { if internalLogEnabled { internalLog("validateObject %s", context.String()) @@ -509,7 +579,7 @@ func (v *subSchema) validateObject(currentSubSchema *subSchema, value map[string // minProperties & maxProperties: if currentSubSchema.minProperties != nil { if len(value) < int(*currentSubSchema.minProperties) { - result.addError( + result.addInternalError( new(ArrayMinPropertiesError), context, value, @@ -519,7 +589,7 @@ func (v *subSchema) validateObject(currentSubSchema *subSchema, value map[string } if currentSubSchema.maxProperties != nil { if len(value) > int(*currentSubSchema.maxProperties) { - result.addError( + result.addInternalError( new(ArrayMaxPropertiesError), context, value, @@ -534,7 +604,7 @@ func (v *subSchema) validateObject(currentSubSchema *subSchema, value map[string if ok { result.incrementScore() } else { - result.addError( + result.addInternalError( new(RequiredError), context, value, @@ -544,143 +614,88 @@ func (v *subSchema) validateObject(currentSubSchema *subSchema, value map[string } // additionalProperty & patternProperty: - if currentSubSchema.additionalProperties != nil { + for pk := range value { - switch currentSubSchema.additionalProperties.(type) { - case bool: - - if !currentSubSchema.additionalProperties.(bool) { - - for pk := range value { - - found := false - for _, spValue := range currentSubSchema.propertiesChildren { - if pk == spValue.property { - found = true - } - } - - pp_has, pp_match := v.validatePatternProperty(currentSubSchema, pk, value[pk], result, context) - - if found { - - if pp_has && !pp_match { - result.addError( - new(AdditionalPropertyNotAllowedError), - context, - value[pk], - ErrorDetails{"property": pk}, - ) - } - - } else { - - if !pp_has || !pp_match { - result.addError( - new(AdditionalPropertyNotAllowedError), - context, - value[pk], - ErrorDetails{"property": pk}, - ) - } - - } - } + // Check whether this property is described by "properties" + found := false + for _, spValue := range currentSubSchema.propertiesChildren { + if pk == spValue.property { + found = true } + } - case *subSchema: - - additionalPropertiesSchema := currentSubSchema.additionalProperties.(*subSchema) - for pk := range value { - - found := false - for _, spValue := range currentSubSchema.propertiesChildren { - if pk == spValue.property { - found = true - } - } - - pp_has, pp_match := v.validatePatternProperty(currentSubSchema, pk, value[pk], result, context) - - if found { - - if pp_has && !pp_match { - validationResult := additionalPropertiesSchema.subValidateWithContext(value[pk], context) - result.mergeErrors(validationResult) - } - - } else { - - if !pp_has || !pp_match { - validationResult := additionalPropertiesSchema.subValidateWithContext(value[pk], context) - result.mergeErrors(validationResult) - } + // Check whether this property is described by "patternProperties" + ppMatch := v.validatePatternProperty(currentSubSchema, pk, value[pk], result, context) + + // If it is not described by neither "properties" nor "patternProperties" it must pass "additionalProperties" + if !found && !ppMatch { + switch ap := currentSubSchema.additionalProperties.(type) { + case bool: + // Handle the boolean case separately as it's cleaner to return a specific error than failing to pass the false schema + if !ap { + result.addInternalError( + new(AdditionalPropertyNotAllowedError), + context, + value[pk], + ErrorDetails{"property": pk}, + ) } - + case *subSchema: + validationResult := ap.subValidateWithContext(value[pk], NewJsonContext(pk, context)) + result.mergeErrors(validationResult) } } - } else { + } + // propertyNames: + if currentSubSchema.propertyNames != nil { for pk := range value { - - pp_has, pp_match := v.validatePatternProperty(currentSubSchema, pk, value[pk], result, context) - - if pp_has && !pp_match { - - result.addError( - new(InvalidPropertyPatternError), + validationResult := currentSubSchema.propertyNames.subValidateWithContext(pk, context) + if !validationResult.Valid() { + result.addInternalError(new(InvalidPropertyNameError), context, - value[pk], - ErrorDetails{ + value, ErrorDetails{ "property": pk, - "pattern": currentSubSchema.PatternPropertiesString(), - }, - ) + }) + result.mergeErrors(validationResult) } - } } result.incrementScore() } -func (v *subSchema) validatePatternProperty(currentSubSchema *subSchema, key string, value interface{}, result *Result, context *jsonContext) (has bool, matched bool) { +func (v *subSchema) validatePatternProperty(currentSubSchema *subSchema, key string, value interface{}, result *Result, context *JsonContext) bool { if internalLogEnabled { internalLog("validatePatternProperty %s", context.String()) internalLog(" %s %v", key, value) } - has = false - - validatedkey := false + validated := false for pk, pv := range currentSubSchema.patternProperties { if matches, _ := regexp.MatchString(pk, key); matches { - has = true - subContext := newJsonContext(key, context) + validated = true + subContext := NewJsonContext(key, context) validationResult := pv.subValidateWithContext(value, subContext) result.mergeErrors(validationResult) - if validationResult.Valid() { - validatedkey = true - } } } - if !validatedkey { - return has, false + if !validated { + return false } result.incrementScore() - - return has, true + return true } -func (v *subSchema) validateString(currentSubSchema *subSchema, value interface{}, result *Result, context *jsonContext) { +func (v *subSchema) validateString(currentSubSchema *subSchema, value interface{}, result *Result, context *JsonContext) { // Ignore JSON numbers - if isJsonNumber(value) { + if isJSONNumber(value) { return } @@ -699,7 +714,7 @@ func (v *subSchema) validateString(currentSubSchema *subSchema, value interface{ // minLength & maxLength: if currentSubSchema.minLength != nil { if utf8.RuneCount([]byte(stringValue)) < int(*currentSubSchema.minLength) { - result.addError( + result.addInternalError( new(StringLengthGTEError), context, value, @@ -709,7 +724,7 @@ func (v *subSchema) validateString(currentSubSchema *subSchema, value interface{ } if currentSubSchema.maxLength != nil { if utf8.RuneCount([]byte(stringValue)) > int(*currentSubSchema.maxLength) { - result.addError( + result.addInternalError( new(StringLengthLTEError), context, value, @@ -721,7 +736,7 @@ func (v *subSchema) validateString(currentSubSchema *subSchema, value interface{ // pattern: if currentSubSchema.pattern != nil { if !currentSubSchema.pattern.MatchString(stringValue) { - result.addError( + result.addInternalError( new(DoesNotMatchPatternError), context, value, @@ -734,7 +749,7 @@ func (v *subSchema) validateString(currentSubSchema *subSchema, value interface{ // format if currentSubSchema.format != "" { if !FormatCheckers.IsFormat(currentSubSchema.format, stringValue) { - result.addError( + result.addInternalError( new(DoesNotMatchFormatError), context, value, @@ -746,10 +761,10 @@ func (v *subSchema) validateString(currentSubSchema *subSchema, value interface{ result.incrementScore() } -func (v *subSchema) validateNumber(currentSubSchema *subSchema, value interface{}, result *Result, context *jsonContext) { +func (v *subSchema) validateNumber(currentSubSchema *subSchema, value interface{}, result *Result, context *JsonContext) { // Ignore non numbers - if !isJsonNumber(value) { + if !isJSONNumber(value) { return } @@ -759,72 +774,83 @@ func (v *subSchema) validateNumber(currentSubSchema *subSchema, value interface{ } number := value.(json.Number) - float64Value, _ := number.Float64() + float64Value, _ := new(big.Rat).SetString(string(number)) // multipleOf: if currentSubSchema.multipleOf != nil { - - if !isFloat64AnInteger(float64Value / *currentSubSchema.multipleOf) { - result.addError( + if q := new(big.Rat).Quo(float64Value, currentSubSchema.multipleOf); !q.IsInt() { + result.addInternalError( new(MultipleOfError), context, - resultErrorFormatJsonNumber(number), - ErrorDetails{"multiple": *currentSubSchema.multipleOf}, + number, + ErrorDetails{ + "multiple": new(big.Float).SetRat(currentSubSchema.multipleOf), + }, ) } } //maximum & exclusiveMaximum: if currentSubSchema.maximum != nil { - if currentSubSchema.exclusiveMaximum { - if float64Value >= *currentSubSchema.maximum { - result.addError( - new(NumberLTError), - context, - resultErrorFormatJsonNumber(number), - ErrorDetails{ - "max": resultErrorFormatNumber(*currentSubSchema.maximum), - }, - ) - } - } else { - if float64Value > *currentSubSchema.maximum { - result.addError( - new(NumberLTEError), - context, - resultErrorFormatJsonNumber(number), - ErrorDetails{ - "max": resultErrorFormatNumber(*currentSubSchema.maximum), - }, - ) - } + if float64Value.Cmp(currentSubSchema.maximum) == 1 { + result.addInternalError( + new(NumberLTEError), + context, + number, + ErrorDetails{ + "max": new(big.Float).SetRat(currentSubSchema.maximum), + }, + ) + } + } + if currentSubSchema.exclusiveMaximum != nil { + if float64Value.Cmp(currentSubSchema.exclusiveMaximum) >= 0 { + result.addInternalError( + new(NumberLTError), + context, + number, + ErrorDetails{ + "max": new(big.Float).SetRat(currentSubSchema.exclusiveMaximum), + }, + ) } } //minimum & exclusiveMinimum: if currentSubSchema.minimum != nil { - if currentSubSchema.exclusiveMinimum { - if float64Value <= *currentSubSchema.minimum { - result.addError( - new(NumberGTError), - context, - resultErrorFormatJsonNumber(number), - ErrorDetails{ - "min": resultErrorFormatNumber(*currentSubSchema.minimum), - }, - ) - } - } else { - if float64Value < *currentSubSchema.minimum { - result.addError( - new(NumberGTEError), - context, - resultErrorFormatJsonNumber(number), - ErrorDetails{ - "min": resultErrorFormatNumber(*currentSubSchema.minimum), - }, - ) - } + if float64Value.Cmp(currentSubSchema.minimum) == -1 { + result.addInternalError( + new(NumberGTEError), + context, + number, + ErrorDetails{ + "min": new(big.Float).SetRat(currentSubSchema.minimum), + }, + ) + } + } + if currentSubSchema.exclusiveMinimum != nil { + if float64Value.Cmp(currentSubSchema.exclusiveMinimum) <= 0 { + result.addInternalError( + new(NumberGTError), + context, + number, + ErrorDetails{ + "min": new(big.Float).SetRat(currentSubSchema.exclusiveMinimum), + }, + ) + } + } + + // format + if currentSubSchema.format != "" { + if !FormatCheckers.IsFormat(currentSubSchema.format, float64Value) { + result.addInternalError( + new(DoesNotMatchFormatError), + context, + value, + ErrorDetails{"format": currentSubSchema.format}, + ) } } diff --git a/vendor/modules.txt b/vendor/modules.txt index d8cc3aed..401ce760 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -23,7 +23,6 @@ github.com/opencontainers/runtime-spec/specs-go github.com/opencontainers/selinux/go-selinux github.com/opencontainers/selinux/go-selinux/label # github.com/pmezard/go-difflib v1.0.0 -## explicit github.com/pmezard/go-difflib/difflib # github.com/satori/go.uuid v1.1.0 ## explicit @@ -31,7 +30,7 @@ github.com/satori/go.uuid # github.com/sirupsen/logrus v1.0.2-0.20170713114250-a3f95b5c4235 ## explicit github.com/sirupsen/logrus -# github.com/stretchr/testify v1.1.5-0.20170809224252-890a5c3458b4 +# github.com/stretchr/testify v1.3.0 ## explicit github.com/stretchr/testify/assert # github.com/syndtr/gocapability v0.0.0-20170704070218-db04d3cc01c8 @@ -40,13 +39,11 @@ github.com/syndtr/gocapability/capability # github.com/urfave/cli v1.19.1 ## explicit github.com/urfave/cli -# github.com/xeipuuv/gojsonpointer v0.0.0-20170225233418-6fe8760cad35 -## explicit +# github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f github.com/xeipuuv/gojsonpointer -# github.com/xeipuuv/gojsonreference v0.0.0-20150808065054-e02fc20de94c -## explicit +# github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 github.com/xeipuuv/gojsonreference -# github.com/xeipuuv/gojsonschema v0.0.0-20170528113821-0c8571ac0ce1 +# github.com/xeipuuv/gojsonschema v1.2.0 ## explicit github.com/xeipuuv/gojsonschema # golang.org/x/sys v0.0.0-20170407050850-f3918c30c5c2