Skip to content
Closed
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
6 changes: 3 additions & 3 deletions schema/backwards_compatibility_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ func TestBackwardsCompatibilityImageIndex(t *testing.T) {

imageIndex := convertFormats(tt.imageIndex)
r := strings.NewReader(imageIndex)
err := schema.ValidatorMediaTypeImageIndex.Validate(r)
err := schema.ValidatorMediaTypeImageIndex.Validate(r, []schema.ValidateFunc{schema.ValidateSchema})

if got := err != nil; tt.fail != got {
t.Errorf("test %d: expected validation failure %t but got %t, err %v", i, tt.fail, got, err)
Expand Down Expand Up @@ -178,7 +178,7 @@ func TestBackwardsCompatibilityManifest(t *testing.T) {

manifest := convertFormats(tt.manifest)
r := strings.NewReader(manifest)
err := schema.ValidatorMediaTypeManifest.Validate(r)
err := schema.ValidatorMediaTypeManifest.Validate(r, []schema.ValidateFunc{schema.ValidateSchema})

if got := err != nil; tt.fail != got {
t.Errorf("test %d: expected validation failure %t but got %t, err %v", i, tt.fail, got, err)
Expand Down Expand Up @@ -217,7 +217,7 @@ func TestBackwardsCompatibilityConfig(t *testing.T) {

config := convertFormats(tt.config)
r := strings.NewReader(config)
err := schema.ValidatorMediaTypeImageConfig.Validate(r)
err := schema.ValidatorMediaTypeImageConfig.Validate(r, []schema.ValidateFunc{schema.ValidateSchema})

if got := err != nil; tt.fail != got {
t.Errorf("test %d: expected validation failure %t but got %t, err %v", i, tt.fail, got, err)
Expand Down
2 changes: 1 addition & 1 deletion schema/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -212,7 +212,7 @@ func TestConfig(t *testing.T) {
},
} {
r := strings.NewReader(tt.config)
err := schema.ValidatorMediaTypeImageConfig.Validate(r)
err := schema.ValidatorMediaTypeImageConfig.Validate(r, []schema.ValidateFunc{schema.ValidateSchema})

if got := err != nil; tt.fail != got {
t.Errorf("test %d: expected validation failure %t but got %t, err %v", i, tt.fail, got, err)
Expand Down
2 changes: 1 addition & 1 deletion schema/descriptor_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -204,7 +204,7 @@ func TestDescriptor(t *testing.T) {
},
} {
r := strings.NewReader(tt.descriptor)
err := schema.ValidatorMediaTypeDescriptor.Validate(r)
err := schema.ValidatorMediaTypeDescriptor.Validate(r, []schema.ValidateFunc{schema.ValidateSchema})

if got := err != nil; tt.fail != got {
t.Errorf("test %d: expected validation failure %t but got %t, err %v", i, tt.fail, got, err)
Expand Down
2 changes: 1 addition & 1 deletion schema/imageindex_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -239,7 +239,7 @@ func TestImageIndex(t *testing.T) {
},
} {
r := strings.NewReader(tt.imageIndex)
err := schema.ValidatorMediaTypeImageIndex.Validate(r)
err := schema.ValidatorMediaTypeImageIndex.Validate(r, []schema.ValidateFunc{schema.ValidateSchema})

if got := err != nil; tt.fail != got {
t.Errorf("test %d: expected validation failure %t but got %t, err %v", i, tt.fail, got, err)
Expand Down
2 changes: 1 addition & 1 deletion schema/manifest_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -193,7 +193,7 @@ func TestManifest(t *testing.T) {
},
} {
r := strings.NewReader(tt.manifest)
err := schema.ValidatorMediaTypeManifest.Validate(r)
err := schema.ValidatorMediaTypeManifest.Validate(r, []schema.ValidateFunc{schema.ValidateSchema})

if got := err != nil; tt.fail != got {
t.Errorf("test %d: expected validation failure %t but got %t, err %v", i, tt.fail, got, err)
Expand Down
3 changes: 2 additions & 1 deletion schema/spec_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,8 @@ func validate(t *testing.T, name string) {
continue
}

err = schema.Validator(example.Mediatype).Validate(strings.NewReader(example.Body))
err = schema.Validator(example.Mediatype).Validate(strings.NewReader(example.Body),
[]schema.ValidateFunc{schema.ValidateSchema})
if err == nil {
printFields(t, "ok", example.Mediatype, example.Title)
t.Log(example.Body, "---")
Expand Down
91 changes: 71 additions & 20 deletions schema/validator.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,14 +26,42 @@ import (
"github.com/xeipuuv/gojsonschema"
)

type validateRefMediaFunc func(r io.Reader) error

var validateRefMediaMap = map[Validator]validateRefMediaFunc{
ValidatorMediaTypeImageIndex: validateImageIndexRefObject,
ValidatorMediaTypeManifest: validateManifestRefObject,
}

// ValidateFunc provides the type of function to validate specific object.
type ValidateFunc func(r io.Reader, v Validator) error
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we expect to be applying a series of these to validate a single blob, using an io.Reader seems wasteful. Is there some Go type magic we can use to pass a parsed JSON object through to the validator to let us only deserialize the JSON once?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I didn't find out the better way yet. At the same time, I still think it had better to input io reader stream to. ValidateRefFuncs are public functions. Consumer should be allowed called them directly. Here we provide io reader to customer, who can use them freely, and it allows expend their job in future.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ValidateRefFuncs are public functions.

That doesn't really matter. We could always provide io.Reader and parsed-JSON flavored versions if we figure out how to write a function that takes an already-parsed-from-JSON object.


// Validator wraps a media type string identifier
// and implements validation against a JSON schema.
type Validator string

type validateDescendantsFunc func(r io.Reader) error
// Validate validates the given read r to memory and call funcList on the content.
// r: the given reader.
// funcList: the optional functions to validate the wrapped media type.
func (v Validator) Validate(r io.Reader, funcList []ValidateFunc) error {
buf, err := ioutil.ReadAll(r)
if err != nil {
return errors.Wrap(err, "unable to read the document file")
}

for _, f := range funcList {
err := f(bytes.NewReader(buf), v)
if err != nil {
return err
}
}
return nil
}

type unimplemented string

var mapValidateDescendants = map[Validator]validateDescendantsFunc{
ValidatorMediaTypeManifest: validateManifestDescendants,
func (v unimplemented) Validate(src io.Reader) error {
return fmt.Errorf("%s: unimplemented", v)
}

// ValidationError contains all the errors that happened during validation.
Expand All @@ -45,23 +73,15 @@ func (e ValidationError) Error() string {
return fmt.Sprintf("%v", e.Errs)
}

// Validate validates the given reader against the schema of the wrapped media type.
func (v Validator) Validate(src io.Reader) error {
// ValidateSchema validates the given reader against the schema of the wrapped media type.
// src: the given reader.
// v: the expected media type.
func ValidateSchema(src io.Reader, v Validator) error {
buf, err := ioutil.ReadAll(src)
if err != nil {
return errors.Wrap(err, "unable to read the document file")
}

if f, ok := mapValidateDescendants[v]; ok {
if f == nil {
return fmt.Errorf("internal error: mapValidateDescendents[%q] is nil", v)
}
err = f(bytes.NewReader(buf))
if err != nil {
return err
}
}

sl := gojsonschema.NewReferenceLoaderFileSystem("file:///"+specs[v], fs)
ml := gojsonschema.NewStringLoader(string(buf))

Expand All @@ -86,16 +106,26 @@ func (v Validator) Validate(src io.Reader) error {
}
}

type unimplemented string
// ValidateRefMedia validates the referenced objects against OCI media type defined in spec.
// src: the given reader.
// v: the expected media type.
func ValidateRefMedia(src io.Reader, v Validator) error {
f, ok := validateRefMediaMap[v]
if ok {
if f == nil {
return fmt.Errorf("referenced media type validation %q unimplemented", v)
}

func (v unimplemented) Validate(src io.Reader) error {
return fmt.Errorf("%s: unimplemented", v)
return f(src)
}

return nil
}

func validateManifestDescendants(r io.Reader) error {
func validateManifestRefObject(src io.Reader) error {
header := v1.Manifest{}

buf, err := ioutil.ReadAll(r)
buf, err := ioutil.ReadAll(src)
if err != nil {
return errors.Wrapf(err, "error reading the io stream")
}
Expand All @@ -119,3 +149,24 @@ func validateManifestDescendants(r io.Reader) error {
}
return nil
}

func validateImageIndexRefObject(src io.Reader) error {
header := v1.ImageIndex{}

buf, err := ioutil.ReadAll(src)
if err != nil {
return errors.Wrapf(err, "error reading the io stream")
}

err = json.Unmarshal(buf, &header)
if err != nil {
return errors.Wrap(err, "manifest list format mismatch")
}

for _, manifest := range header.Manifests {
if manifest.MediaType != string(v1.MediaTypeImageManifest) {
return fmt.Errorf("manifest %s has an unknown media type: %s", manifest.Digest, manifest.MediaType)
}
}
return nil
}