Skip to content

Commit

Permalink
Merge pull request #523 from MUzairS15/MUzairS15/schema-validation
Browse files Browse the repository at this point in the history
Support for validating resources.
  • Loading branch information
Mohd Uzair authored Jul 1, 2024
2 parents c92f2c9 + 3ac6cf9 commit 58d7ff2
Show file tree
Hide file tree
Showing 6 changed files with 208 additions and 21 deletions.
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ require (
github.com/google/uuid v1.5.0
github.com/kubernetes/kompose v1.31.1
github.com/layer5io/meshery-operator v0.7.0
github.com/meshery/schemas v0.7.6
github.com/meshery/schemas v0.7.8
github.com/nats-io/nats.go v1.31.0
github.com/open-policy-agent/opa v0.57.1
github.com/opencontainers/image-spec v1.1.0-rc6
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -607,8 +607,8 @@ github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0 h1:jWpvCLoY8Z/e3VKvls
github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0/go.mod h1:QUyp042oQthUoa9bqDv0ER0wrtXnBruoNd7aNjkbP+k=
github.com/meshery/kompose v1.0.1 h1:lg8B/pkLh6762jeFsQATD8UJZZwXZf/aviC3/dzw78A=
github.com/meshery/kompose v1.0.1/go.mod h1:TWhWTEMbJBUzENf4JTEtBmZRFm/r8n0nS6v4/nSD2vA=
github.com/meshery/schemas v0.7.6 h1:BOV5+LN0hjNWNqpFhuczaHpzerZQihjMv3uM5bx1ReE=
github.com/meshery/schemas v0.7.6/go.mod h1:ZsfoE5HvlqJvUvBlqS2rHoNQoDJ+eYuly5w5m+qIQSM=
github.com/meshery/schemas v0.7.8 h1:OZwGhIWsKr+L7C6ZelZoOFx2Rz+Cu7swF1VVy/Dx4ks=
github.com/meshery/schemas v0.7.8/go.mod h1:ZsfoE5HvlqJvUvBlqS2rHoNQoDJ+eYuly5w5m+qIQSM=
github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
github.com/miekg/dns v1.1.43 h1:JKfpVSCB84vrAmHzyrsxB5NAr5kLoMXZArPSw7Qlgyg=
github.com/miekg/dns v1.1.43/go.mod h1:+evo5L0630/F6ca/Z9+GAqzhjGyn8/c+TBaOyfEl0V4=
Expand Down
8 changes: 0 additions & 8 deletions models/catalog/v1alpha1/catalog.go
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,6 @@ type ContentClassObj struct {
const (
Official ContentClass = "official"
Verified ContentClass = "verified"
Project ContentClass = "project"
Community ContentClass = "community"
)

Expand All @@ -87,8 +86,6 @@ func (c ContentClass) String() string {
return "official"
case Verified:
return "verified"
// case Project:
// return "project"
case Community:
fallthrough
default:
Expand All @@ -107,11 +104,6 @@ func GetCatalogClasses() []ContentClassObj {
Class: Verified,
Description: "Content produced by partners and verified by Meshery maintainers. While not directly maintained by Meshery, it has undergone a verification process to ensure quality and compatibility.",
},
// Uncomment if needed
// {
// Class: ProjectClass,
// Description: "Content produced and supported by the respective project or organization responsible for the specific technology. This class offers a level of support from the project maintainers themselves.",
// },
{
Class: Community,
Description: "Content produced and shared by Meshery users. This includes a wide range of content, such as performance profiles, test results, filters, patterns, and applications. Community content may have varying levels of support and reliability.",
Expand Down
27 changes: 17 additions & 10 deletions utils/cue.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package utils

import (
"fmt"
"io"

"cuelang.org/go/cue"
"cuelang.org/go/cue/cuecontext"
Expand All @@ -15,20 +16,12 @@ import (
func Validate(schema cue.Value, value cue.Value) (bool, []errors.Error) {
var errs []errors.Error
uval := value.Unify(schema)
err := uval.Validate()
err := uval.Validate(cue.Final())
if err != nil {
cueErr := errors.Errors(err)
errs = append(errs, cueErr...)
}
// check for required fields
schema.Walk(func(v cue.Value) bool {
val := value.LookupPath(v.Path())
if !(val.Err() == nil && val.IsConcrete()) {
cueErr := errors.Errors(errors.New(fmt.Sprintf("%v is a required field", v.Path().String())))
errs = append(errs, cueErr...)
}
return true
}, nil)

if len(errs) != 0 {
return false, errs
}
Expand Down Expand Up @@ -113,3 +106,17 @@ func Lookup(rootVal cue.Value, path string) (cue.Value, error) {

return res.Value(), nil
}

func ConvertoCue(reader io.Reader) (cue.Value, error) {
var result cue.Value
cuectx := cuecontext.New()
expr, err := yaml.Extract("", reader)
if err != nil {
return result, ErrYamlToCue(err)
}
result = cuectx.BuildFile(expr)
if result.Err() != nil {
return result, ErrYamlToCue(result.Err())
}
return result, nil
}
105 changes: 105 additions & 0 deletions validator/validate.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
package validator

import (
"encoding/json"
"fmt"
"sync"

"cuelang.org/go/cue"
cueerrors "cuelang.org/go/cue/errors"
"github.com/layer5io/meshkit/errors"
"github.com/layer5io/meshkit/utils"
"github.com/meshery/schemas"
)

var (
ErrValidateCode = ""
schemaPath = "components.schemas"
cueschema cue.Value
mx sync.Mutex
isSchemaLoaded bool
)

func loadSchema() error {
mx.Lock()
defer func() {
mx.Unlock()
}()

if isSchemaLoaded {
return nil
}

file, err := schemas.Schemas.Open("schemas/openapi.yml")
if err != nil {
return utils.ErrReadFile(err, "schemas/openapi.yml")
}

cueschema, err = utils.ConvertoCue(file)
if err == nil {
isSchemaLoaded = true
}
return err
}

func GetSchemaFor(resourceName string) (cue.Value, error) {
var schema cue.Value
schemaPathForResource := fmt.Sprintf("%s.%s", schemaPath, resourceName)

err := loadSchema()
if err != nil {
return schema, err
}

schema, err = utils.Lookup(cueschema, schemaPathForResource)
if err != nil {
return schema, err
}

byt, err := schema.MarshalJSON()
if err != nil {
return schema, utils.ErrMarshal(err)
}

schema, err = utils.JsonSchemaToCue(string(byt))
if err != nil {
return schema, err
}

return schema, nil
}

func Validate(schema cue.Value, resourceValue interface{}) error {

byt, err := json.Marshal(resourceValue)
if err != nil {
return utils.ErrMarshal(err)
}

cv, err := utils.JsonToCue(byt)
if err != nil {
return err
}

valid, errs := utils.Validate(schema, cv)
if !valid {
return errors.New(ErrValidateCode,
errors.Alert,
[]string{"validation for the resource failed"},
convertCueErrorsToStrings(errs),
[]string{}, []string{},
)
}
return nil
}

func convertCueErrorsToStrings(errs []cueerrors.Error) []string {
var res []string
for _, err := range errs {
_ = cueerrors.Sanitize(err)
}
for _, err2 := range errs {
res = append(res, err2.Error())
}
return res
}
83 changes: 83 additions & 0 deletions validator/validate_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
package validator

import (
"fmt"
"testing"

"github.com/layer5io/meshkit/models/catalog/v1alpha1"
"github.com/layer5io/meshkit/models/meshmodel/core/v1beta1"
"github.com/meshery/schemas/models/patterns"
)

type ValidationCases struct {
Path string
Resource interface{}

ShouldPass bool
}

func TestValidator(t *testing.T) {
tests := []ValidationCases{
{
Path: "design",
Resource: patterns.PatternFile{
Name: "test-design",
Services: make(map[string]*patterns.Service),
},
ShouldPass: true,
},
{
Path: "catalog_data",
Resource: v1alpha1.CatalogData{
PublishedVersion: "v.10.9",
ContentClass: "sdsds",
Compatibility: []v1alpha1.CatalogDataCompatibility{
"kubernetes",
},
PatternCaveats: "NA",
PatternInfo: "NA",
Type: v1alpha1.CatalogDataType("Dployment"),
},
ShouldPass: false,
},
{
Path: "models",
Resource: v1beta1.Model{
VersionMeta: v1beta1.VersionMeta{
SchemaVersion: "v1beta1",
Version: "1.0.0",
},
Category: v1beta1.Category{
Name: "test",
},
Model: v1beta1.ModelEntity{
Version: "1.0.0",
},
Status: "",
DisplayName: "",
Description: "",
},
ShouldPass: false,
},
}

for _, test := range tests {
t.Run("validaion", func(_t *testing.T) {
schema, err := GetSchemaFor(test.Path)
if err != nil {
t.Errorf("%v", err)

}

err = Validate(schema, test.Resource)
fmt.Println(err)
if test.ShouldPass && err != nil {
t.Errorf("test failed for %s, got %s, want %t, error: %v", test.Path, "false", test.ShouldPass, err)

} else if !test.ShouldPass && err == nil {
t.Errorf("test failed for %s, got %s, want %t error: %v", test.Path, "true", !test.ShouldPass, err)
}

})
}
}

0 comments on commit 58d7ff2

Please sign in to comment.