From 6e828fc283917d3d6cd4c88aedc108df16818738 Mon Sep 17 00:00:00 2001 From: psihachina Date: Tue, 4 Oct 2022 14:25:45 +0300 Subject: [PATCH 1/2] added new jsonschema validator Old validator unsupported new drafts and bad handle errors. --- go.mod | 1 + go.sum | 2 + internal/schema/ize-spec.json | 2 +- internal/schema/schema.go | 126 +++++++--------------------------- 4 files changed, 29 insertions(+), 102 deletions(-) diff --git a/go.mod b/go.mod index 5eddcd9d..424357c8 100644 --- a/go.mod +++ b/go.mod @@ -100,6 +100,7 @@ require ( github.com/rivo/uniseg v0.2.0 // indirect github.com/rogpeppe/go-internal v1.6.1 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect + github.com/santhosh-tekuri/jsonschema v1.2.4 // indirect github.com/sergi/go-diff v1.2.0 // indirect github.com/spf13/afero v1.8.2 // indirect github.com/spf13/cast v1.5.0 // indirect diff --git a/go.sum b/go.sum index 2802fe47..62164835 100644 --- a/go.sum +++ b/go.sum @@ -912,6 +912,8 @@ github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQD github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= github.com/safchain/ethtool v0.0.0-20190326074333-42ed695e3de8/go.mod h1:Z0q5wiBQGYcxhMZ6gUqHn6pYNLypFAvaL3UvgZLR0U4= github.com/safchain/ethtool v0.0.0-20210803160452-9aa261dae9b1/go.mod h1:Z0q5wiBQGYcxhMZ6gUqHn6pYNLypFAvaL3UvgZLR0U4= +github.com/santhosh-tekuri/jsonschema v1.2.4 h1:hNhW8e7t+H1vgY+1QeEQpveR6D4+OwKPXCfD2aieJis= +github.com/santhosh-tekuri/jsonschema v1.2.4/go.mod h1:TEAUOeZSmIxTTuHatJzrvARHiuO9LYd+cIxzgEHCQI4= github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= github.com/sclevine/agouti v3.0.0+incompatible/go.mod h1:b4WX9W9L1sfQKXeJf1mUTLZKJ48R1S7H23Ji7oFO5Bw= github.com/sclevine/spec v1.2.0/go.mod h1:W4J29eT/Kzv7/b9IWLB055Z+qvVC9vt0Arko24q7p+U= diff --git a/internal/schema/ize-spec.json b/internal/schema/ize-spec.json index 792d9501..0fb46f19 100644 --- a/internal/schema/ize-spec.json +++ b/internal/schema/ize-spec.json @@ -1,5 +1,5 @@ { - "$schema": "http://json-schema.org/draft-04/schema#", + "$schema": "http://json-schema.org/draft-07/schema#", "type": "object", "properties": { "aws_profile": { diff --git a/internal/schema/schema.go b/internal/schema/schema.go index 08e64c37..35767964 100644 --- a/internal/schema/schema.go +++ b/internal/schema/schema.go @@ -2,9 +2,9 @@ package schema import ( "fmt" - "strings" - + "github.com/santhosh-tekuri/jsonschema" "github.com/xeipuuv/gojsonschema" + "strings" // Enable support for embedded static resources _ "embed" @@ -13,28 +13,38 @@ import ( //go:embed ize-spec.json var Schema string -const ( - jsonschemaOneOf = "number_one_of" - jsonschemaAnyOf = "number_any_of" -) - // Validate uses the jsonschema to validate the configuration func Validate(config map[string]interface{}) error { - schemaLoader := gojsonschema.NewStringLoader(Schema) - dataLoader := gojsonschema.NewGoLoader(config) - - result, err := gojsonschema.Validate(schemaLoader, dataLoader) + compiler := jsonschema.NewCompiler() + compiler.Draft = jsonschema.Draft7 + if err := compiler.AddResource("schema.json", strings.NewReader(Schema)); err != nil { + panic(err) + } + schema, err := compiler.Compile("schema.json") if err != nil { - return err + panic(err) } - - if !result.Valid() { - return toError(result) + err = schema.ValidateInterface(config) + if err != nil { + i, m := GetErrorMessage(err.(*jsonschema.ValidationError)) + if i == "#" { + i = "root" + } else { + i = strings.ReplaceAll(i[2:], "/", ".") + } + return fmt.Errorf("%s in %s", m, i) } return nil } +func GetErrorMessage(err *jsonschema.ValidationError) (string, string) { + if len(err.Causes) == 0 { + return err.InstancePtr, err.Message + } + return GetErrorMessage(err.Causes[0]) +} + func GetJsonSchema() interface{} { schemaLoader := gojsonschema.NewStringLoader(Schema) @@ -45,89 +55,3 @@ func GetJsonSchema() interface{} { return json } - -func toError(result *gojsonschema.Result) error { - err := getMostSpecificError(result.Errors()) - return err -} - -func (err validationError) Error() string { - description := getDescription(err) - return fmt.Sprintf("%s %s", err.parent.Field(), description) -} - -func getDescription(err validationError) string { - switch err.parent.Type() { - case "invalid_type": - if expectedType, ok := err.parent.Details()["expected"].(string); ok { - return fmt.Sprintf("must be a %s", humanReadableType(expectedType)) - } - case jsonschemaOneOf, jsonschemaAnyOf: - if err.child == nil { - return err.parent.Description() - } - return err.child.Description() - } - return err.parent.Description() -} - -func humanReadableType(definition string) string { - if definition[0:1] == "[" { - allTypes := strings.Split(definition[1:len(definition)-1], ",") - for i, t := range allTypes { - allTypes[i] = humanReadableType(t) - } - return fmt.Sprintf( - "%s or %s", - strings.Join(allTypes[0:len(allTypes)-1], ", "), - allTypes[len(allTypes)-1], - ) - } - if definition == "object" { - return "mapping" - } - if definition == "array" { - return "list" - } - return definition -} - -func getMostSpecificError(errors []gojsonschema.ResultError) validationError { - mostSpecificError := 0 - for i, err := range errors { - if specificity(err) > specificity(errors[mostSpecificError]) { - mostSpecificError = i - continue - } - - if specificity(err) == specificity(errors[mostSpecificError]) { - // Invalid type errors win in a tie-breaker for most specific field name - if err.Type() == "invalid_type" && errors[mostSpecificError].Type() != "invalid_type" { - mostSpecificError = i - } - } - } - - if mostSpecificError+1 == len(errors) { - return validationError{parent: errors[mostSpecificError]} - } - - switch errors[mostSpecificError].Type() { - case "number_one_of", "number_any_of": - return validationError{ - parent: errors[mostSpecificError], - child: errors[mostSpecificError+1], - } - default: - return validationError{parent: errors[mostSpecificError]} - } -} - -func specificity(err gojsonschema.ResultError) int { - return len(strings.Split(err.Field(), ".")) -} - -type validationError struct { - parent gojsonschema.ResultError - child gojsonschema.ResultError -} From ba0d38984edcb1cf53e2d4776ac1d77a90eab81c Mon Sep 17 00:00:00 2001 From: psihachina Date: Tue, 4 Oct 2022 19:14:18 +0300 Subject: [PATCH 2/2] added additional message --- internal/schema/schema.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/schema/schema.go b/internal/schema/schema.go index 35767964..0c17c6de 100644 --- a/internal/schema/schema.go +++ b/internal/schema/schema.go @@ -32,7 +32,7 @@ func Validate(config map[string]interface{}) error { } else { i = strings.ReplaceAll(i[2:], "/", ".") } - return fmt.Errorf("%s in %s", m, i) + return fmt.Errorf("%s in %s of config file (or environment variables)", m, i) } return nil