Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

IZE-581 Missing parameters showed at once #482

Merged
merged 2 commits into from
Oct 4, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -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=
Expand Down
2 changes: 1 addition & 1 deletion internal/schema/ize-spec.json
Original file line number Diff line number Diff line change
@@ -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": {
Expand Down
126 changes: 25 additions & 101 deletions internal/schema/schema.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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 of config file (or environment variables)", 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)

Expand All @@ -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
}