Skip to content

Commit

Permalink
feature(39): Created new implementation of SchemaValidator interface,…
Browse files Browse the repository at this point in the history
… updated code
  • Loading branch information
Paweł Chmielewski authored and pawelWritesCode committed Sep 6, 2022
1 parent 9b26144 commit 0b4735f
Show file tree
Hide file tree
Showing 6 changed files with 169 additions and 62 deletions.
4 changes: 2 additions & 2 deletions context.go
Original file line number Diff line number Diff line change
Expand Up @@ -117,8 +117,8 @@ func NewDefaultAPIContext(isDebug bool, jsonSchemaDir string) *APIContext {
defaultHttpClient := &http.Client{Transport: tr}

jsonSchemaValidators := SchemaValidators{
StringValidator: schema.NewJSONSchemaRawValidator(),
ReferenceValidator: schema.NewDefaultJSONSchemaReferenceValidator(jsonSchemaDir),
StringValidator: schema.NewJSONSchemaRawXGValidator(),
ReferenceValidator: schema.NewDefaultJSONSchemaReferenceXGValidator(jsonSchemaDir),
}

pathFinders := PathFinders{
Expand Down
8 changes: 4 additions & 4 deletions context_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -168,9 +168,9 @@ func TestState_SetTemplateEngine(t *testing.T) {
func TestState_SetSchemaStringValidator(t *testing.T) {
s := NewDefaultAPIContext(false, "")

_, isDefault := s.SchemaValidators.StringValidator.(schema.JSONSchemaRawValidator)
_, isDefault := s.SchemaValidators.StringValidator.(schema.JSONSchemaRawXGValidator)
if !isDefault {
t.Errorf("default StringValidator is not schema.JSONSchemaRawValidator")
t.Errorf("default StringValidator is not schema.JSONSchemaRawXGValidator")
}

s.SetSchemaStringValidator(newStringValidator{})
Expand All @@ -184,9 +184,9 @@ func TestState_SetSchemaStringValidator(t *testing.T) {
func TestState_SetSchemaReferenceValidator(t *testing.T) {
s := NewDefaultAPIContext(false, "")

_, isDefault := s.SchemaValidators.ReferenceValidator.(schema.JSONSchemaReferenceValidator)
_, isDefault := s.SchemaValidators.ReferenceValidator.(schema.JSONSchemaReferenceXGValidator)
if !isDefault {
t.Errorf("default ReferenceValidator is not schema.JSONSchemaReferenceValidator")
t.Errorf("default ReferenceValidator is not schema.JSONSchemaReferenceXGValidator")
}

s.SetSchemaReferenceValidator(newStringValidator{})
Expand Down
2 changes: 2 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ require (
github.com/mattn/go-colorable v0.1.12 // indirect
github.com/mattn/go-isatty v0.0.14 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/qri-io/jsonpointer v0.1.1 // indirect
github.com/qri-io/jsonschema v0.2.1 // indirect
github.com/smartystreets/goconvey v1.6.4 // indirect
github.com/stretchr/objx v0.1.1 // indirect
github.com/tidwall/match v1.1.1 // indirect
Expand Down
5 changes: 5 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,11 @@ github.com/pawelWritesCode/qjson v1.0.1 h1:MreBuXjjQj2XROgrGK5e9rWDiwLzDO4TyMyo8
github.com/pawelWritesCode/qjson v1.0.1/go.mod h1:BBj5FLhYUYGE8lNCKdz+MjJab+2fFcs+s9NFDDFjjnk=
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/qri-io/jsonpointer v0.1.1 h1:prVZBZLL6TW5vsSB9fFHFAMBLI4b0ri5vribQlTJiBA=
github.com/qri-io/jsonpointer v0.1.1/go.mod h1:DnJPaYgiKu56EuDp8TU5wFLdZIcAnb/uH9v37ZaMV64=
github.com/qri-io/jsonschema v0.2.1 h1:NNFoKms+kut6ABPf6xiKNM5214jzxAhDBrPHCJ97Wg0=
github.com/qri-io/jsonschema v0.2.1/go.mod h1:g7DPkiOsK1xv6T/Ao5scXRkd+yTFygcANPBaaqW+VrI=
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM=
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s=
Expand Down
124 changes: 89 additions & 35 deletions pkg/schema/validator.go
Original file line number Diff line number Diff line change
@@ -1,49 +1,77 @@
// Package schema holds services that allows to validate JSON string against a schema.
//
// Package contains two types of JSON schema validators:
//
// raw - which accepts JSON schema string,
// reference - which accepts reference to JSON schema,
//
// JSONSchemaRawXGValidator has ability to validate JSON schema string written with draft v4 v6 or v7.
// JSONSchemaReferenceXGValidator has ability to validate JSON schema passed as URL or OS path written with draft v4 v6 or v7.
//
// 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.
//
// JSONSchemaRawQIValidator has ability to validate JSON schema string written with draft 7 & 2019-09
package schema

import (
"context"
"encoding/json"
"errors"
"fmt"
"path"

"github.com/qri-io/jsonschema"
jschema "github.com/xeipuuv/gojsonschema"

"github.com/pawelWritesCode/gdutils/pkg/httpctx"
"github.com/pawelWritesCode/gdutils/pkg/osutils"
v "github.com/pawelWritesCode/gdutils/pkg/validator"
)

// JSONSchemaReferenceValidator is entity that has ability to validate data against JSON schema passed as reference.
type JSONSchemaReferenceValidator struct {
// JSONSchemaReferenceXGValidator is entity that has ability to validate data against JSON schema passed as reference.
// xeipuuv/gojsonschema is used under the hood.
type JSONSchemaReferenceXGValidator struct {
fileValidator v.Validator
urlValidator v.Validator

// schemasDir represents absolute path to JSON schemas directory.
schemasDir string
}

// JSONSchemaRawValidator is entity that has ability to validate data against JSON schema passed as string
type JSONSchemaRawValidator struct{}
// JSONSchemaRawXGValidator is entity that has ability to validate data against JSON schema passed as string.
// xeipuuv/gojsonschema is used under the hood
type JSONSchemaRawXGValidator struct{}

func NewDefaultJSONSchemaReferenceValidator(schemasDir string) JSONSchemaReferenceValidator {
return NewJSONSchemaReferenceValidator(schemasDir, osutils.NewFileValidator(), httpctx.NewURLValidator())
// JSONSchemaRawQIValidator is entity that has ability to validate data against JSON schema passed as string
// qri-io/jsonschema is used under the hood
type JSONSchemaRawQIValidator struct{}

// NewDefaultJSONSchemaReferenceXGValidator creates new JSONSchemaReferenceXGValidator with fixed services
func NewDefaultJSONSchemaReferenceXGValidator(schemasDir string) JSONSchemaReferenceXGValidator {
return NewJSONSchemaReferenceXGValidator(schemasDir, osutils.NewFileValidator(), httpctx.NewURLValidator())
}

func NewJSONSchemaReferenceValidator(schemasDir string, fileValidator v.Validator, urlValidator v.Validator) JSONSchemaReferenceValidator {
return JSONSchemaReferenceValidator{
// NewJSONSchemaReferenceXGValidator creates new JSONSchemaReferenceXGValidator with provided services
func NewJSONSchemaReferenceXGValidator(schemasDir string, fileValidator v.Validator, urlValidator v.Validator) JSONSchemaReferenceXGValidator {
return JSONSchemaReferenceXGValidator{
fileValidator: fileValidator,
urlValidator: urlValidator,
schemasDir: schemasDir,
}
}

func NewJSONSchemaRawValidator() JSONSchemaRawValidator {
return JSONSchemaRawValidator{}
// NewJSONSchemaRawXGValidator creates new JSONSchemaRawXGValidator
func NewJSONSchemaRawXGValidator() JSONSchemaRawXGValidator {
return JSONSchemaRawXGValidator{}
}

// Validate validates document against JSON schema located in schemaPath.
// schemaPath may be URL or relative/full path to json schema on user OS
func (jsv JSONSchemaReferenceValidator) Validate(document, schemaPath string) error {
source, err := jsv.getSource(schemaPath)
// according to xeipuuv/gojsonschema library it covers JSON Schema, draft v4 v6 & v7
func (jsv JSONSchemaReferenceXGValidator) Validate(document, schemaPath string) error {
source, err := getSource(jsv.urlValidator, jsv.fileValidator, jsv.schemasDir, schemaPath)
if err != nil {
return err
}
Expand All @@ -65,14 +93,59 @@ func (jsv JSONSchemaReferenceValidator) Validate(document, schemaPath string) er
return nil
}

// Validate validates document against jsonSchema.
// according to xeipuuv/gojsonschema library it covers JSON Schema, draft v4 v6 & v7
func (j JSONSchemaRawXGValidator) Validate(document, jsonSchema string) error {
result, err := jschema.Validate(jschema.NewStringLoader(jsonSchema), jschema.NewStringLoader(document))
if err != nil {
return err
}

if !result.Valid() {
errSum := ""
for _, err := range result.Errors() {
errSum += err.String()
}

return errors.New(errSum)
}

return nil
}

// Validate validates document against json schema.
// according to library documentation it covers https://json-schema.org drafts 7 & 2019-09
func (j JSONSchemaRawQIValidator) Validate(document, jsonSchema string) error {
rs := &jsonschema.Schema{}
if err := json.Unmarshal([]byte(jsonSchema), rs); err != nil {
return err
}

errs, err := rs.ValidateBytes(context.Background(), []byte(document))
if err != nil {
return err
}

var errStr string
if len(errs) > 0 {
for _, e := range errs {
errStr += e.Error() + " "
}

err = errors.New(errStr)
}

return err
}

// getSource accepts rawSource, validate it and returns valid source
// available sources are: file system os path and URL
func (jsv JSONSchemaReferenceValidator) getSource(rawSource string) (string, error) {
func getSource(urlValidator, fileValidator v.Validator, schemasDir, rawSource string) (string, error) {
if rawSource == "" {
return rawSource, errors.New("provided rawSource should not be empty string")
}

errURL := jsv.urlValidator.Validate(rawSource)
errURL := urlValidator.Validate(rawSource)
if errURL == nil { // is valid URL
return rawSource, nil
}
Expand All @@ -82,32 +155,13 @@ func (jsv JSONSchemaReferenceValidator) getSource(rawSource string) (string, err
if path.IsAbs(rawSource) { // rawSource is valid absolute path
pth = rawSource
} else {
pth = path.Clean(path.Join(jsv.schemasDir, rawSource))
pth = path.Clean(path.Join(schemasDir, rawSource))
}

errPath := jsv.fileValidator.Validate(pth)
errPath := fileValidator.Validate(pth)
if errPath == nil { // pth points at some resource in user OS
return fmt.Sprintf("%s%s", "file://", pth), nil
}

return "", fmt.Errorf("%s isn't valid path to any resource on your OS, nor valid URL", rawSource)
}

// Validate validates document against jsonSchema
func (J JSONSchemaRawValidator) Validate(document, jsonSchema string) error {
result, err := jschema.Validate(jschema.NewStringLoader(jsonSchema), jschema.NewStringLoader(document))
if err != nil {
return err
}

if !result.Valid() {
errSum := ""
for _, err := range result.Errors() {
errSum += err.String()
}

return errors.New(errSum)
}

return nil
}
88 changes: 67 additions & 21 deletions pkg/schema/validator_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,31 @@ import (
"github.com/pawelWritesCode/gdutils/pkg/validator"
)

var document = `{
"latitude": 48.858093,
"longitude": 2.294694
}`
var jsonSchema = `{
"$id": "https://example.com/geographical-location.schema.json",
"$schema": "https://json-schema.org/draft/2020-12/schema",
"title": "Longitude and Latitude Values",
"description": "A geographical coordinate.",
"required": [ "latitude", "longitude" ],
"type": "object",
"properties": {
"latitude": {
"type": "number",
"minimum": -90,
"maximum": 90
},
"longitude": {
"type": "number",
"minimum": -180,
"maximum": 180
}
}
}`

type mockedFileValidator struct {
mock.Mock
}
Expand Down Expand Up @@ -97,15 +122,9 @@ func TestJSONSchemaValidator_getSource(t *testing.T) {
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
jsv := JSONSchemaReferenceValidator{
fileValidator: tt.fields.fileValidator,
urlValidator: tt.fields.urlValidator,
schemasDir: tt.fields.schemasDir,
}

tt.fields.mockFunc()

got, err := jsv.getSource(tt.args.rawSource)
got, err := getSource(tt.fields.urlValidator, tt.fields.fileValidator, tt.fields.schemasDir, tt.args.rawSource)
if (err != nil) != tt.wantErr {
t.Errorf("getSource() error = %v, wantErr %v", err, tt.wantErr)
return
Expand All @@ -118,11 +137,31 @@ func TestJSONSchemaValidator_getSource(t *testing.T) {
}

func TestJSONSchemaRawValidator_Validate(t *testing.T) {
document := `{
"latitude": 48.858093,
"longitude": 2.294694
}`
jsonSchema := `{

type args struct {
document string
jsonSchema string
}
tests := []struct {
name string
args args
wantErr bool
}{
{name: "valid data #1", args: args{
document: document,
jsonSchema: jsonSchema,
}, wantErr: false},
{name: "no document", args: args{
document: "",
jsonSchema: jsonSchema,
}, wantErr: true},
{name: "no json schema", args: args{
document: document,
jsonSchema: ``,
}, wantErr: true},
{name: "invalid json schema", args: args{
document: document,
jsonSchema: `{
"$id": "https://example.com/geographical-location.schema.json",
"$schema": "https://json-schema.org/draft/2020-12/schema",
"title": "Longitude and Latitude Values",
Expand All @@ -131,18 +170,26 @@ func TestJSONSchemaRawValidator_Validate(t *testing.T) {
"type": "object",
"properties": {
"latitude": {
"type": "number",
"minimum": -90,
"maximum": 90
"type": "string"
},
"longitude": {
"type": "number",
"minimum": -180,
"maximum": 180
"type": "string"
}
}
}`
}`,
}, wantErr: true},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
J := JSONSchemaRawXGValidator{}
if err := J.Validate(tt.args.document, tt.args.jsonSchema); (err != nil) != tt.wantErr {
t.Errorf("Validate() error = %v, wantErr %v", err, tt.wantErr)
}
})
}
}

func TestJSONSchemaRawQriioValidator_Validate(t *testing.T) {
type args struct {
document string
jsonSchema string
Expand All @@ -168,7 +215,6 @@ func TestJSONSchemaRawValidator_Validate(t *testing.T) {
document: document,
jsonSchema: `{
"$id": "https://example.com/geographical-location.schema.json",
"$schema": "https://json-schema.org/draft/2020-12/schema",
"title": "Longitude and Latitude Values",
"description": "A geographical coordinate.",
"required": [ "latitude", "longitude" ],
Expand All @@ -186,7 +232,7 @@ func TestJSONSchemaRawValidator_Validate(t *testing.T) {
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
J := JSONSchemaRawValidator{}
J := JSONSchemaRawQIValidator{}
if err := J.Validate(tt.args.document, tt.args.jsonSchema); (err != nil) != tt.wantErr {
t.Errorf("Validate() error = %v, wantErr %v", err, tt.wantErr)
}
Expand Down

0 comments on commit 0b4735f

Please # to comment.