diff --git a/api/client/v1alpha1/schema_types.go b/api/client/v1alpha1/schema_types.go index 776fbc2e..1d1e01f9 100644 --- a/api/client/v1alpha1/schema_types.go +++ b/api/client/v1alpha1/schema_types.go @@ -1,9 +1,5 @@ package v1alpha1 -import ( - "github.com/uor-framework/uor-client-go/schema" -) - // SchemaConfigurationKind object kind of SchemaConfiguration const SchemaConfigurationKind = "SchemaConfiguration" @@ -19,9 +15,6 @@ type SchemaConfigurationSpec struct { // the schema ID string `json:"id"` Description string `json:"description"` - // SchemaPath defines that path to a JSON schema. If set, the AttributeTypes fields - // will be ignored. - SchemaPath string - // AttributeTypes is a collection of attribute type definitions. - AttributeTypes schema.Types `json:"attributeTypes,omitempty"` + // SchemaPath defines that path to a JSON schema. + SchemaPath string `json:"schemaPath"` } diff --git a/cmd/client/commands/build_schema.go b/cmd/client/commands/build_schema.go index 1fd4593f..8d27fa28 100644 --- a/cmd/client/commands/build_schema.go +++ b/cmd/client/commands/build_schema.go @@ -99,21 +99,13 @@ func (o *BuildSchemaOptions) Run(ctx context.Context) error { } }() - var userSchema schema.Loader - if config.Schema.SchemaPath != "" { - schemaBytes, err := ioutil.ReadFile(config.Schema.SchemaPath) - if err != nil { - return err - } - userSchema, err = schema.FromBytes(schemaBytes) - if err != nil { - return err - } - } else { - userSchema, err = schema.FromTypes(config.Schema.AttributeTypes) - if err != nil { - return err - } + schemaBytes, err := ioutil.ReadFile(config.Schema.SchemaPath) + if err != nil { + return err + } + userSchema, err := schema.FromBytes(schemaBytes) + if err != nil { + return err } schemaAnnotations := map[string]string{} diff --git a/cmd/client/commands/build_schema_test.go b/cmd/client/commands/build_schema_test.go index 96815fb6..e74a357d 100644 --- a/cmd/client/commands/build_schema_test.go +++ b/cmd/client/commands/build_schema_test.go @@ -111,7 +111,7 @@ func TestBuildSchemaRun(t *testing.T) { cases := []spec{ { - name: "Success/FlatWorkspace", + name: "Success/ValidateSchemaConfig", opts: &BuildSchemaOptions{ BuildOptions: &BuildOptions{ Destination: fmt.Sprintf("%s/client-flat-test:latest", u.Host), @@ -127,6 +127,24 @@ func TestBuildSchemaRun(t *testing.T) { SchemaConfig: "testdata/configs/schema-config.yaml", }, }, + { + name: "Failure/NoSchemaPath", + opts: &BuildSchemaOptions{ + BuildOptions: &BuildOptions{ + Destination: fmt.Sprintf("%s/client-flat-test:latest", u.Host), + Common: &options.Common{ + IOStreams: genericclioptions.IOStreams{ + Out: os.Stdout, + In: os.Stdin, + ErrOut: os.Stderr, + }, + Logger: testlogr, + }, + }, + SchemaConfig: "testdata/configs/schema-config-nopath.yaml", + }, + expError: "no schema path provided in schema config", + }, } for _, c := range cases { diff --git a/cmd/client/commands/testdata/configs/schema-config-nopath.yaml b/cmd/client/commands/testdata/configs/schema-config-nopath.yaml new file mode 100644 index 00000000..e9c7a26d --- /dev/null +++ b/cmd/client/commands/testdata/configs/schema-config-nopath.yaml @@ -0,0 +1,4 @@ +kind: SchemaConfiguration +apiVersion: client.uor-framework.io/v1alpha1 +schema: + id: "myid" \ No newline at end of file diff --git a/cmd/client/commands/testdata/configs/schema-config.yaml b/cmd/client/commands/testdata/configs/schema-config.yaml index 2775d077..bfff4a79 100644 --- a/cmd/client/commands/testdata/configs/schema-config.yaml +++ b/cmd/client/commands/testdata/configs/schema-config.yaml @@ -1,5 +1,4 @@ kind: SchemaConfiguration apiVersion: client.uor-framework.io/v1alpha1 schema: - attributeTypes: - "test": "string" \ No newline at end of file + schemaPath: "testdata/configs/test.json" \ No newline at end of file diff --git a/cmd/client/commands/testdata/configs/test.json b/cmd/client/commands/testdata/configs/test.json new file mode 100644 index 00000000..fbbe902a --- /dev/null +++ b/cmd/client/commands/testdata/configs/test.json @@ -0,0 +1,12 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "type": "object", + "properties": { + "test": { + "type": "string" + } + }, + "required": [ + "test" + ] +} diff --git a/config/load.go b/config/load.go index 470f6341..d12fa755 100644 --- a/config/load.go +++ b/config/load.go @@ -3,6 +3,7 @@ package config import ( "bytes" "encoding/json" + "errors" "fmt" "io/ioutil" "path/filepath" @@ -65,6 +66,10 @@ func LoadSchemaConfig(data []byte) (configuration v1alpha1.SchemaConfiguration, if err = dec.Decode(&configuration); err != nil { return configuration, err } + + if configuration.Schema.SchemaPath == "" { + return configuration, errors.New("no schema path provided in schema config") + } return configuration, err } diff --git a/config/load_test.go b/config/load_test.go index 33a98d49..58208aca 100644 --- a/config/load_test.go +++ b/config/load_test.go @@ -7,7 +7,6 @@ import ( uorspec "github.com/uor-framework/collection-spec/specs-go/v1alpha1" "github.com/uor-framework/uor-client-go/api/client/v1alpha1" - "github.com/uor-framework/uor-client-go/schema" ) func TestReadAttributeQuery(t *testing.T) { @@ -122,14 +121,18 @@ func TestReadSchemaConfiguration(t *testing.T) { APIVersion: v1alpha1.GroupVersion, }, Schema: v1alpha1.SchemaConfigurationSpec{ - AttributeTypes: map[string]schema.Type{ - "test": schema.TypeString, - }, + ID: "myschema", + SchemaPath: "mypath", }, }, }, { - name: "Failure/InvalidConfig", + name: "Failure/InvalidConfigNoPath", + path: "testdata/invalid-schema.yaml", + expError: "no schema path provided in schema config", + }, + { + name: "Failure/InvalidConfigWrongType", path: "testdata/valid-attr.yaml", expError: "config kind AttributeQuery, does not match expected SchemaConfiguration", }, diff --git a/config/testdata/invalid-schema.yaml b/config/testdata/invalid-schema.yaml new file mode 100644 index 00000000..ac3c419c --- /dev/null +++ b/config/testdata/invalid-schema.yaml @@ -0,0 +1,4 @@ +kind: SchemaConfiguration +apiVersion: client.uor-framework.io/v1alpha1 +schema: + id: "myschema" \ No newline at end of file diff --git a/config/testdata/valid-schema.yaml b/config/testdata/valid-schema.yaml index 2775d077..a91d7823 100644 --- a/config/testdata/valid-schema.yaml +++ b/config/testdata/valid-schema.yaml @@ -1,5 +1,5 @@ kind: SchemaConfiguration apiVersion: client.uor-framework.io/v1alpha1 schema: - attributeTypes: - "test": "string" \ No newline at end of file + id: "myschema" + schemaPath: "mypath" \ No newline at end of file diff --git a/schema/loader.go b/schema/loader.go index a87db63f..8344178b 100644 --- a/schema/loader.go +++ b/schema/loader.go @@ -2,7 +2,6 @@ package schema import ( "encoding/json" - "sort" "github.com/xeipuuv/gojsonschema" ) @@ -17,47 +16,6 @@ func (l Loader) Export() json.RawMessage { return l.raw } -// FromTypes builds a JSON Schema from a key with an associated type. -// All keys provided will be considered required types in the schema when -// comparing sets of attributes. -func FromTypes(types Types) (Loader, error) { - if err := types.Validate(); err != nil { - return Loader{}, err - } - - // Build an object in json from the provided types - type jsonSchema struct { - Type string `json:"type"` - Properties map[string]map[string]string `json:"properties"` - Required []string `json:"required"` - } - - // Fill in properties and required keys. At this point - // we consider all keys as required. - properties := map[string]map[string]string{} - var required []string - for key, value := range types { - properties[key] = map[string]string{"type": value.String()} - required = append(required, key) - } - - // Make the required slice order deterministic - sort.Slice(required, func(i, j int) bool { - return required[i] < required[j] - }) - - tmp := jsonSchema{ - Type: "object", - Properties: properties, - Required: required, - } - b, err := json.Marshal(tmp) - if err != nil { - return Loader{}, err - } - return FromBytes(b) -} - // FromGo loads a Go struct into a JSON schema that // can be used for attribute validation. func FromGo(source interface{}) (Loader, error) { diff --git a/schema/loader_test.go b/schema/loader_test.go index 84d192df..55338864 100644 --- a/schema/loader_test.go +++ b/schema/loader_test.go @@ -7,55 +7,6 @@ import ( "github.com/stretchr/testify/require" ) -func TestFromTypes(t *testing.T) { - type spec struct { - name string - types map[string]Type - expSchema string - expError string - } - - cases := []spec{ - { - name: "Success/ValidConfiguration", - types: map[string]Type{ - "test": TypeString, - "size": TypeNumber, - }, - expSchema: "{\"type\":\"object\",\"properties\":" + "" + - "{\"size\":{\"type\":\"number\"},\"test\":{\"type\":\"string\"}},\"required\":[\"size\",\"test\"]}", - }, - { - name: "Failure/InvalidType", - types: map[string]Type{ - "test": TypeString, - "size": TypeInvalid, - }, - expError: "must set schema type", - }, - { - name: "Failure/UnknownType", - types: map[string]Type{ - "test": TypeString, - "size": 20, - }, - expError: "unknown schema type", - }, - } - - for _, c := range cases { - t.Run(c.name, func(t *testing.T) { - schema, err := FromTypes(c.types) - if c.expError != "" { - require.EqualError(t, err, c.expError) - } else { - require.NoError(t, err) - require.Equal(t, c.expSchema, string(schema.Export())) - } - }) - } -} - func TestFromBytes(t *testing.T) { type spec struct { name string diff --git a/schema/schema_test.go b/schema/schema_test.go index 010940f1..06b44aa5 100644 --- a/schema/schema_test.go +++ b/schema/schema_test.go @@ -1,6 +1,8 @@ package schema import ( + "encoding/json" + "sort" "testing" "github.com/stretchr/testify/require" @@ -11,18 +13,20 @@ import ( func TestSchema_Validate(t *testing.T) { type spec struct { - name string - schemaTypes Types - doc model.AttributeSet - expRes bool - expError string + name string + properties map[string]map[string]string + doc model.AttributeSet + expRes bool + expError string } cases := []spec{ { name: "Success/ValidAttributes", - schemaTypes: map[string]Type{ - "size": TypeNumber, + properties: map[string]map[string]string{ + "size": { + "type": "number", + }, }, doc: attributes.Attributes{ "size": attributes.NewFloat("size", 1.0), @@ -31,8 +35,10 @@ func TestSchema_Validate(t *testing.T) { }, { name: "Failure/IncompatibleType", - schemaTypes: map[string]Type{ - "size": TypeBool, + properties: map[string]map[string]string{ + "size": { + "type": "boolean", + }, }, doc: attributes.Attributes{ "size": attributes.NewFloat("size", 1.0), @@ -42,8 +48,10 @@ func TestSchema_Validate(t *testing.T) { }, { name: "Failure/MissingKey", - schemaTypes: map[string]Type{ - "size": TypeString, + properties: map[string]map[string]string{ + "size": { + "type": "number", + }, }, doc: attributes.Attributes{ "name": attributes.NewString("name", "test"), @@ -55,7 +63,7 @@ func TestSchema_Validate(t *testing.T) { for _, c := range cases { t.Run(c.name, func(t *testing.T) { - loader, err := FromTypes(c.schemaTypes) + loader, err := fromProperties(c.properties) require.NoError(t, err) schema, err := New(loader) @@ -71,3 +79,35 @@ func TestSchema_Validate(t *testing.T) { }) } } + +func fromProperties(properties map[string]map[string]string) (Loader, error) { + // Build an object in json from the provided types + type jsonSchema struct { + Type string `json:"type"` + Properties map[string]map[string]string `json:"properties"` + Required []string `json:"required"` + } + + // Fill in properties and required keys. At this point + // we consider all keys as required. + var required []string + for key := range properties { + required = append(required, key) + } + + // Make the required slice order deterministic + sort.Slice(required, func(i, j int) bool { + return required[i] < required[j] + }) + + tmp := jsonSchema{ + Type: "object", + Properties: properties, + Required: required, + } + b, err := json.Marshal(tmp) + if err != nil { + return Loader{}, err + } + return FromBytes(b) +} diff --git a/schema/types.go b/schema/types.go deleted file mode 100644 index 378960dd..00000000 --- a/schema/types.go +++ /dev/null @@ -1,118 +0,0 @@ -package schema - -import ( - "encoding/json" - "errors" - "fmt" - - "github.com/uor-framework/uor-client-go/model" -) - -// Type represent types for an attribute schema. -type Type int - -const ( - TypeInvalid Type = iota - TypeNull - TypeBool - TypeNumber - TypeInteger - TypeString -) - -// String prints a string representation of the attribute kind. -func (t Type) String() string { - return stringByType[t] -} - -// IsLike returns the model Kind that correlate to the schema Type. -func (t Type) IsLike() (model.Kind, error) { - // Use d to represent the default kind if one does not match - var d model.Kind - if err := t.validate(); err != nil { - return d, err - } - kind, found := modelKindByType[t] - if !found { - return d, fmt.Errorf("type %s is not linked to a model kind", t.String()) - } - return kind, nil -} - -// UnmarshalJSON unmarshal a JSON serialized type to the Schema Type -func (t *Type) UnmarshalJSON(b []byte) error { - var j string - if err := json.Unmarshal(b, &j); err != nil { - return err - } - *t = typeByString[j] - return t.validate() -} - -// MarshalJSON marshals the Schema Type into JSON format. -func (t Type) MarshalJSON() ([]byte, error) { - if err := t.validate(); err != nil { - return nil, err - } - return json.Marshal(t.String()) -} - -// validate performs basic validation -// on a Type. -func (t Type) validate() error { - if _, found := stringByType[t]; found { - return nil - } - switch t { - case TypeInvalid: - // TypeInvalid is the default value for the concrete type, which means the field was not set. - return errors.New("must set schema type") - default: - return fmt.Errorf("unknown schema type") - } -} - -// stringByType maps the schema Type to its string -// representation. -var stringByType = map[Type]string{ - TypeNumber: "number", - TypeInteger: "integer", - TypeBool: "boolean", - TypeString: "string", - TypeNull: "null", -} - -// typeByString maps the string representation of the schema Type -// to the schema Type. -var typeByString = map[string]Type{ - "number": TypeNumber, - "integer": TypeInteger, - "boolean": TypeBool, - "string": TypeString, - "null": TypeNull, -} - -// modelKindByType maps each schema type to a -// corresponding model Kind. -var modelKindByType = map[Type]model.Kind{ - TypeNumber: model.KindFloat, - TypeInteger: model.KindInt, - TypeBool: model.KindBool, - TypeString: model.KindString, - TypeNull: model.KindNull, - TypeInvalid: model.KindInvalid, -} - -// Types represent a schema Type mapped to a key of string type. -type Types map[string]Type - -// Validate performs basic validation -// on a set of schema types. -func (t Types) Validate() error { - for _, value := range t { - if err := value.validate(); err != nil { - return err - } - } - return nil -}