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

Implement yaml support for schema and target doc [READY FOR REVIEW] #6

Merged
merged 11 commits into from
Mar 22, 2020
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
yajsv
build/
coverage.out
4 changes: 3 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@ module github.com/neilpa/yajsv
go 1.12

require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/ghodss/yaml v1.0.0
github.com/mitchellh/go-homedir v1.1.0
github.com/xeipuuv/gojsonschema v1.2.0
neilpa.me/go-x v0.1.0
gopkg.in/yaml.v2 v2.2.8 // indirect
)
10 changes: 8 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
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/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk=
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
Expand All @@ -13,5 +17,7 @@ github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHo
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=
neilpa.me/go-x v0.1.0 h1:bry050ou4HtEhZ3vZEFRKrzqvObodseVvfcQvK/M8U4=
neilpa.me/go-x v0.1.0/go.mod h1:aIemU+pQYLLV3dygXotHKF7SantXe5HzZR6VIjzY/4g=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
93 changes: 59 additions & 34 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,21 +6,25 @@ import (
"bufio"
"flag"
"fmt"
"io/ioutil"
"log"
"os"
"path/filepath"
"runtime"
"github.com/ghodss/yaml"
zendril marked this conversation as resolved.
Show resolved Hide resolved
"strings"
"sync"

"github.com/mitchellh/go-homedir"
"github.com/xeipuuv/gojsonschema"
"neilpa.me/go-x/fileuri"

)

var (
version = "undefined"
zendril marked this conversation as resolved.
Show resolved Hide resolved
const (
version = "v1.1.0"
)

var (
schemaFlag = flag.String("s", "", "primary JSON schema to validate against, required")
quietFlag = flag.Bool("q", false, "quiet, only print validation failures and errors")
versionFlag = flag.Bool("v", false, "print version and exit")
Expand Down Expand Up @@ -77,26 +81,33 @@ func realMain(args []string) int {
}
}
if len(docs) == 0 {
return usageError("no JSON documents to validate")
return usageError("no documents to validate")
}

// Compile target schema
sl := gojsonschema.NewSchemaLoader()
schemaUri := fileUri(*schemaFlag)
schemaUri := *schemaFlag
zendril marked this conversation as resolved.
Show resolved Hide resolved
for _, ref := range refFlags {
for _, p := range glob(ref) {
uri := fileUri(p)
if uri == schemaUri {
if p == schemaUri {
zendril marked this conversation as resolved.
Show resolved Hide resolved
continue
}
loader := gojsonschema.NewReferenceLoader(uri)
err := sl.AddSchemas(loader)

loader, err := jsonLoader(p)
if err != nil {
log.Fatalf("%s: unable to load schema ref: %s\n", *schemaFlag, err)
}
addSchemaErr := sl.AddSchemas(loader)
if addSchemaErr != nil {
zendril marked this conversation as resolved.
Show resolved Hide resolved
log.Fatalf("%s: invalid schema: %s\n", p, err)
}
}
}
schemaLoader := gojsonschema.NewReferenceLoader(schemaUri)

schemaLoader, err := jsonLoader(schemaUri)
if err != nil {
log.Fatalf("%s: unable to load schema: %s\n", *schemaFlag, err)
}
schema, err := sl.Compile(schemaLoader)
if err != nil {
log.Fatalf("%s: invalid schema: %s\n", *schemaFlag, err)
Expand All @@ -115,25 +126,32 @@ func realMain(args []string) int {
sem <- 0
defer func() { <-sem }()

loader := gojsonschema.NewReferenceLoader(fileUri(path))
result, err := schema.Validate(loader)
switch {
case err != nil:
msg := fmt.Sprintf("%s: error: %s", path, err)

loader, err := jsonLoader(path)
if err != nil {
msg := fmt.Sprintf("%s: unable to load doc: %s\n", *schemaFlag, err)
zendril marked this conversation as resolved.
Show resolved Hide resolved
fmt.Println(msg)
errors = append(errors, msg)
zendril marked this conversation as resolved.
Show resolved Hide resolved

case !result.Valid():
lines := make([]string, len(result.Errors()))
for i, desc := range result.Errors() {
lines[i] = fmt.Sprintf("%s: fail: %s", path, desc)
} else {
result, err := schema.Validate(loader)
switch {
case err != nil:
msg := fmt.Sprintf("%s: error: %s", path, err)
zendril marked this conversation as resolved.
Show resolved Hide resolved
fmt.Println(msg)
errors = append(errors, msg)

case !result.Valid():
lines := make([]string, len(result.Errors()))
for i, desc := range result.Errors() {
lines[i] = fmt.Sprintf("%s: fail: %s", path, desc)
}
msg := strings.Join(lines, "\n")
fmt.Println(msg)
failures = append(failures, msg)

case !*quietFlag:
fmt.Printf("%s: pass\n", path)
}
msg := strings.Join(lines, "\n")
fmt.Println(msg)
failures = append(failures, msg)

case !*quietFlag:
fmt.Printf("%s: pass\n", path)
}
}(p)
}
Expand All @@ -160,6 +178,21 @@ func realMain(args []string) int {
return exit
}

func jsonLoader(path string) (gojsonschema.JSONLoader, error) {
buf, err := ioutil.ReadFile(path)
if err != nil {
return nil, err
}
switch filepath.Ext(path) {
case ".yml", ".yaml":
buf, err = yaml.YAMLToJSON(buf)
}
if err != nil {
return nil, err
}
return gojsonschema.NewBytesLoader(buf), nil
}

func printUsage() {
fmt.Fprintf(os.Stderr, `Usage: %s -s schema.json [options] document.json ...

Expand Down Expand Up @@ -189,14 +222,6 @@ func usageError(msg string) int {
return 4
}

func fileUri(path string) string {
uri, err := fileuri.FromPath(path)
if err != nil {
log.Fatalf("%s: %s", path, err)
}
return uri
}

// glob is a wrapper that also resolves `~` since we may be skipping
// the shell expansion when single-quoting globs at the command line
func glob(pattern string) []string {
Expand Down
82 changes: 78 additions & 4 deletions main_windows_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,25 @@ import (
"log"
)

func ExampleMain_pass() {
func ExampleMain_pass_ymlschema_ymldoc() {
exit := realMain([]string{"-s", "testdata\\schema.yml", "testdata\\data-pass.yml"})
if exit != 0 {
log.Fatalf("exit: got %d, want 0", exit)
}
// Output:
// testdata\data-pass.yml: pass
}

func ExampleMain_pass_jsonschema_ymldoc() {
exit := realMain([]string{"-s", "testdata\\schema.json", "testdata\\data-pass.yml"})
if exit != 0 {
log.Fatalf("exit: got %d, want 0", exit)
}
// Output:
// testdata\data-pass.yml: pass
}

func ExampleMain_pass_jsonschema_jsondoc() {
exit := realMain([]string{"-s", "testdata\\schema.json", "testdata\\data-pass.json"})
if exit != 0 {
log.Fatalf("exit: got %d, want 0", exit)
Expand All @@ -15,7 +33,34 @@ func ExampleMain_pass() {
// testdata\data-pass.json: pass
}

func ExampleMain_fail() {
func ExampleMain_pass_ymlschema_jsondoc() {
exit := realMain([]string{"-s", "testdata\\schema.yml", "testdata\\data-pass.json"})
if exit != 0 {
log.Fatalf("exit: got %d, want 0", exit)
}
// Output:
// testdata\data-pass.json: pass
}

func ExampleMain_fail_ymlschema_ymldoc() {
exit := realMain([]string{"-q", "-s", "testdata\\schema.yml", "testdata\\data-fail.yml"})
if exit != 1 {
log.Fatalf("exit: got %d, want 1", exit)
}
// Output:
// testdata\data-fail.yml: fail: (root): foo is required
}

func ExampleMain_fail_jsonschema_ymldoc() {
exit := realMain([]string{"-q", "-s", "testdata\\schema.json", "testdata\\data-fail.yml"})
if exit != 1 {
log.Fatalf("exit: got %d, want 1", exit)
}
// Output:
// testdata\data-fail.yml: fail: (root): foo is required
}

func ExampleMain_fail_jsonschema_jsondoc() {
exit := realMain([]string{"-q", "-s", "testdata\\schema.json", "testdata\\data-fail.json"})
if exit != 1 {
log.Fatalf("exit: got %d, want 1", exit)
Expand All @@ -24,7 +69,16 @@ func ExampleMain_fail() {
// testdata\data-fail.json: fail: (root): foo is required
}

func ExampleMain_error() {
func ExampleMain_fail_ymlschema_jsondoc() {
exit := realMain([]string{"-q", "-s", "testdata\\schema.yml", "testdata\\data-fail.json"})
if exit != 1 {
log.Fatalf("exit: got %d, want 1", exit)
}
// Output:
// testdata\data-fail.json: fail: (root): foo is required
}

func ExampleMain_error_jsonschema_jsondoc() {
exit := realMain([]string{"-q", "-s", "testdata\\schema.json", "testdata\\data-error.json"})
if exit != 2 {
log.Fatalf("exit: got %d, want 2", exit)
Expand All @@ -33,7 +87,16 @@ func ExampleMain_error() {
// testdata\data-error.json: error: invalid character 'o' in literal null (expecting 'u')
}

func ExampleMain_glob() {
func ExampleMain_error_ymlschema_ymldoc() {
exit := realMain([]string{"-q", "-s", "testdata\\schema.yml", "testdata\\data-error.yml"})
if exit != 2 {
log.Fatalf("exit: got %d, want 2", exit)
}
// Output:
// testdata\schema.yml: unable to load doc: yaml: found unexpected end of stream
}

func ExampleMain_glob_jsonschema_jsondoc() {
exit := realMain([]string{"-q", "-s", "testdata\\schema.json", "testdata\\data-*.json"})
if exit != 3 {
log.Fatalf("exit: got %d, want 3", exit)
Expand All @@ -42,3 +105,14 @@ func ExampleMain_glob() {
// testdata\data-error.json: error: invalid character 'o' in literal null (expecting 'u')
// testdata\data-fail.json: fail: (root): foo is required
}

func ExampleMain_glob_ymlschema_ymldoc() {
exit := realMain([]string{"-q", "-s", "testdata\\schema.yml", "testdata\\data-*.yml"})
if exit != 3 {
log.Fatalf("exit: got %d, want 3", exit)
}
// Unordered output:
// testdata\schema.yml: unable to load doc: yaml: found unexpected end of stream
//
// testdata\data-fail.yml: fail: (root): foo is required
}
1 change: 1 addition & 0 deletions testdata/data-error.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
invalid: "an escaped \' single quote is not valid yaml
2 changes: 2 additions & 0 deletions testdata/data-fail.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
---
bar: missing foo
3 changes: 3 additions & 0 deletions testdata/data-pass.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
---
foo: asdf
bar: zxcv
7 changes: 7 additions & 0 deletions testdata/schema.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
properties:
foo:
type: string
bar: {}
required:
- foo