Skip to content

Commit

Permalink
Merge pull request #237 from carolynvs/jsonschema-lock
Browse files Browse the repository at this point in the history
Make validating a bundle thread-safe
  • Loading branch information
carolynvs committed Jun 2, 2021
2 parents 0082cac + 742da2c commit c9aabe2
Show file tree
Hide file tree
Showing 5 changed files with 37 additions and 21 deletions.
5 changes: 3 additions & 2 deletions bundle/definition/validation.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package definition

import (
"context"
"encoding/json"

"github.com/pkg/errors"
Expand All @@ -17,7 +18,7 @@ type ValidationError struct {

// ValidateSchema validates that the Schema is valid JSON Schema.
// If no errors occur, the validated jsonschema.Schema is returned.
func (s *Schema) ValidateSchema() (*jsonschema.RootSchema, error) {
func (s *Schema) ValidateSchema() (*jsonschema.Schema, error) {
b, err := json.Marshal(s)
if err != nil {
return nil, errors.Wrap(err, "unable to load schema")
Expand All @@ -43,7 +44,7 @@ func (s *Schema) Validate(data interface{}) ([]ValidationError, error) {
if err != nil {
return nil, errors.Wrap(err, "unable to process data")
}
valErrs, err := def.ValidateBytes(payload)
valErrs, err := def.ValidateBytes(context.Background(), payload)
if err != nil {
return nil, errors.Wrap(err, "unable to perform validation")
}
Expand Down
10 changes: 5 additions & 5 deletions bundle/definition/validation_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -80,8 +80,8 @@ func TestObjectValidationValid_CustomValidator_ContentEncoding_base64(t *testing
File: "SGVsbG8gV29ybGQhCg===",
}
valErrors, err = definition.Validate(invalidVal)
assert.NoError(t, err)
assert.Len(t, valErrors, 1, "expected 1 validation error")
require.NoError(t, err)
require.Len(t, valErrors, 1, "expected 1 validation error")
assert.Equal(t, "invalid base64 value: SGVsbG8gV29ybGQhCg===", valErrors[0].Error)
}

Expand Down Expand Up @@ -146,7 +146,7 @@ func TestObjectValidationInValidMinimum(t *testing.T) {
valErr := valErrors[0]
assert.NotNil(t, valErr, "expected the obtain the validation error")
assert.Equal(t, "/port", valErr.Path, "expected validation error to reference port")
assert.Equal(t, "must be greater than or equal to 100.000000", valErr.Error, "expected validation error to reference port")
assert.Equal(t, "must be greater than or equal to 100", valErr.Error, "expected validation error to reference port")
}

func TestObjectValidationPropertyRequired(t *testing.T) {
Expand Down Expand Up @@ -229,7 +229,7 @@ func TestObjectValidationNoAdditionalPropertiesAllowed(t *testing.T) {
assert.Len(t, valErrors, 1, "expected a validation error")
assert.NoError(t, err)
assert.Equal(t, "/badActor", valErrors[0].Path, "expected the error to be on badActor")
assert.Equal(t, "cannot match schema", valErrors[0].Error)
assert.Equal(t, "additional properties are not allowed", valErrors[0].Error)
}

func TestObjectValidationAdditionalPropertiesAreStrings(t *testing.T) {
Expand Down Expand Up @@ -276,5 +276,5 @@ func TestObjectValidationAdditionalPropertiesAreStrings(t *testing.T) {
valErrors, err := definition.Validate(val)
assert.Len(t, valErrors, 1, "expected a validation error")
assert.NoError(t, err)
assert.Equal(t, "type should be string", valErrors[0].Error)
assert.Equal(t, "type should be string, got boolean", valErrors[0].Error)
}
27 changes: 18 additions & 9 deletions bundle/definition/validators.go
Original file line number Diff line number Diff line change
@@ -1,45 +1,54 @@
package definition

import (
"context"
"encoding/base64"
"fmt"

"github.com/qri-io/jsonpointer"
"github.com/qri-io/jsonschema"
)

// ContentEncoding represents a "custom" Schema property
type ContentEncoding string

// NewContentEncoding allocates a new ContentEncoding validator
func NewContentEncoding() jsonschema.Validator {
func NewContentEncoding() jsonschema.Keyword {
return new(ContentEncoding)
}

// Validate implements the Validator interface for ContentEncoding
// which, as of writing, isn't included by default in the jsonschema library we consume
func (c ContentEncoding) Validate(propPath string, data interface{}, errs *[]jsonschema.ValError) {
func (c ContentEncoding) Validate(propPath string, data interface{}, errs *[]jsonschema.KeyError) {}

func (c ContentEncoding) ValidateKeyword(ctx context.Context, currentState *jsonschema.ValidationState, data interface{}) {
if obj, ok := data.(string); ok {
switch c {
case "base64":
_, err := base64.StdEncoding.DecodeString(obj)
if err != nil {
jsonschema.AddError(errs, propPath, data, fmt.Sprintf("invalid %s value: %s", c, obj))
currentState.AddError(data, fmt.Sprintf("invalid %s value: %s", c, obj))
}
// Add validation support for other encodings as needed
// See https://json-schema.org/latest/json-schema-validation.html#rfc.section.8.3
default:
jsonschema.AddError(errs, propPath, data, fmt.Sprintf("unsupported or invalid contentEncoding type of %s", c))
currentState.AddError(data, fmt.Sprintf("unsupported or invalid contentEncoding type of %s", c))
}
}
}

func (c ContentEncoding) Register(uri string, registry *jsonschema.SchemaRegistry) {}

func (c ContentEncoding) Resolve(pointer jsonpointer.Pointer, uri string) *jsonschema.Schema {
return nil
}

// NewRootSchema returns a jsonschema.RootSchema with any needed custom
// jsonschema.Validators pre-registered
func NewRootSchema() *jsonschema.RootSchema {
func NewRootSchema() *jsonschema.Schema {
// Register custom validators here
// Note: as of writing, jsonschema doesn't have a stock validator for instances of type `contentEncoding`
// There may be others missing in the library that exist in http://json-schema.org/draft-07/schema#
// and thus, we'd need to create/register them here (if not included upstream)
jsonschema.RegisterValidator("contentEncoding", NewContentEncoding)
return new(jsonschema.RootSchema)
jsonschema.RegisterKeyword("contentEncoding", NewContentEncoding)
jsonschema.LoadDraft2019_09()
return &jsonschema.Schema{}
}
8 changes: 7 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,11 @@ module github.com/cnabio/cnab-go

go 1.13

// Make our use of jsonschema thread-safe
// Upstream Issue: https://github.com/qri-io/jsonschema/issues/80
// Local Fix: https://github.com/carolynvs/jsonschema/tree/local-keyword-registry
replace github.com/qri-io/jsonschema => github.com/carolynvs/jsonschema v0.2.1-0.20210602145235-283986347fba

require (
github.com/Masterminds/semver v1.5.0
github.com/agl/ed25519 v0.0.0-20170116200512-5312a6153412 // indirect
Expand Down Expand Up @@ -36,7 +41,8 @@ require (
github.com/opencontainers/go-digest v1.0.0
github.com/pivotal/image-relocation v0.0.0-20191111101224-e94aff6df06c
github.com/pkg/errors v0.9.1
github.com/qri-io/jsonschema v0.1.1
github.com/qri-io/jsonpointer v0.1.1
github.com/qri-io/jsonschema v0.2.1-0.20201028142641-08d62a2939dc
github.com/stretchr/testify v1.6.1
github.com/theupdateframework/notary v0.6.1 // indirect
github.com/xeipuuv/gojsonschema v1.2.0
Expand Down
8 changes: 4 additions & 4 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,8 @@ github.com/bugsnag/osext v0.0.0-20130617224835-0dd3f918b21b/go.mod h1:obH5gd0Bsq
github.com/bugsnag/panicwrap v0.0.0-20151223152923-e2c28503fcd0/go.mod h1:D/8v3kj0zr8ZAKg1AQ6crr+5VwKN5eIywRkfhyM/+dE=
github.com/bugsnag/panicwrap v1.2.0 h1:OzrKrRvXis8qEvOkfcxNcYbOd2O7xXS2nnKMEMABFQA=
github.com/bugsnag/panicwrap v1.2.0/go.mod h1:D/8v3kj0zr8ZAKg1AQ6crr+5VwKN5eIywRkfhyM/+dE=
github.com/carolynvs/jsonschema v0.2.1-0.20210602145235-283986347fba h1:mjN+V+g5m08sWxTlQ2UNj2kdO4FLH1AuDS0l/2aIw7U=
github.com/carolynvs/jsonschema v0.2.1-0.20210602145235-283986347fba/go.mod h1:g7DPkiOsK1xv6T/Ao5scXRkd+yTFygcANPBaaqW+VrI=
github.com/cenkalti/backoff v2.2.1+incompatible h1:tNowT99t7UNflLxfYYSlKYsBpXdEet03Pg2g16Swow4=
github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
Expand Down Expand Up @@ -664,10 +666,8 @@ github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4O
github.com/prometheus/procfs v0.2.0 h1:wH4vA7pcjKuZzjF7lM8awk4fnuJO6idemZXoKnULUx4=
github.com/prometheus/procfs v0.2.0/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=
github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU=
github.com/qri-io/jsonpointer v0.1.0 h1:OcTtTmorodUCRc2CZhj/ZwOET8zVj6uo0ArEmzoThZI=
github.com/qri-io/jsonpointer v0.1.0/go.mod h1:DnJPaYgiKu56EuDp8TU5wFLdZIcAnb/uH9v37ZaMV64=
github.com/qri-io/jsonschema v0.1.1 h1:t//Doa/gvMqJ0bDhG7PGIKfaWGGxRVaffp+bcvBGGEk=
github.com/qri-io/jsonschema v0.1.1/go.mod h1:QpzJ6gBQ0GYgGmh7mDQ1YsvvhSgE4rYj0k8t5MBOmUY=
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/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
Expand Down

0 comments on commit c9aabe2

Please sign in to comment.