diff --git a/go.mod b/go.mod index 493b9898..20aa78f6 100644 --- a/go.mod +++ b/go.mod @@ -5,23 +5,18 @@ go 1.21 require ( github.com/Masterminds/semver/v3 v3.2.1 github.com/buger/jsonparser v1.1.1 - github.com/go-playground/validator/v10 v10.15.5 github.com/kennygrant/sanitize v1.2.4 github.com/spf13/cobra v1.7.0 github.com/spf13/viper v1.17.0 github.com/stretchr/testify v1.8.4 - github.com/web-of-things-open-source/go-jsonschema v0.0.0-20231122093147-d1a487982f5c + github.com/web-of-things-open-source/go-jsonschema v0.0.0-20231123170049-1c3cab186d29 ) require ( github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/fsnotify/fsnotify v1.6.0 // indirect - github.com/gabriel-vasile/mimetype v1.4.2 // indirect - github.com/go-playground/locales v0.14.1 // indirect - github.com/go-playground/universal-translator v0.18.1 // indirect github.com/hashicorp/hcl v1.0.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect - github.com/leodido/go-urn v1.2.4 // indirect github.com/magiconair/properties v1.8.7 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/pelletier/go-toml/v2 v2.1.0 // indirect @@ -35,7 +30,6 @@ require ( github.com/subosito/gotenv v1.6.0 // indirect go.uber.org/atomic v1.9.0 // indirect go.uber.org/multierr v1.9.0 // indirect - golang.org/x/crypto v0.15.0 // indirect golang.org/x/exp v0.0.0-20230905200255-921286631fa9 // indirect golang.org/x/net v0.18.0 // indirect golang.org/x/sys v0.14.0 // indirect diff --git a/go.sum b/go.sum index 085fb3ed..083fa779 100644 --- a/go.sum +++ b/go.sum @@ -65,19 +65,9 @@ github.com/frankban/quicktest v1.14.4 h1:g2rn0vABPOOXmZUj+vbmUp0lPoXEMuhTpIluN0X github.com/frankban/quicktest v1.14.4/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= -github.com/gabriel-vasile/mimetype v1.4.2 h1:w5qFW6JKBz9Y393Y4q372O9A7cUSequkh1Q7OhCmWKU= -github.com/gabriel-vasile/mimetype v1.4.2/go.mod h1:zApsH/mKG4w07erKIaJPFiX0Tsq9BFQgN3qGY5GnNgA= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= -github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= -github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= -github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= -github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= -github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= -github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= -github.com/go-playground/validator/v10 v10.15.5 h1:LEBecTWb/1j5TNY1YYG2RcOUN3R7NLylN+x8TTueE24= -github.com/go-playground/validator/v10 v10.15.5/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= @@ -155,8 +145,6 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= -github.com/leodido/go-urn v1.2.4 h1:XlAE/cm/ms7TE/VMVoduSpNBoyc2dOxHs5MZSwAN63Q= -github.com/leodido/go-urn v1.2.4/go.mod h1:7ZrI8mTSeBSHl/UaRyKQW1qZeMgak41ANeCNaVckg+4= github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= @@ -198,13 +186,12 @@ github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5 github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= -github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= -github.com/web-of-things-open-source/go-jsonschema v0.0.0-20231122093147-d1a487982f5c h1:iQpkPMPMSByM8PLnwbLHThjUYX9NEezJfksra6nB63U= -github.com/web-of-things-open-source/go-jsonschema v0.0.0-20231122093147-d1a487982f5c/go.mod h1:qjqU56vW5YwhHIY7u48KloTM0UnzBQKsCxpTbXgvYjs= +github.com/web-of-things-open-source/go-jsonschema v0.0.0-20231123170049-1c3cab186d29 h1:jIY9WgwunMAiNCzIDPLD6vkUKNqFYT7u70Ep26Xo4CA= +github.com/web-of-things-open-source/go-jsonschema v0.0.0-20231123170049-1c3cab186d29/go.mod h1:qjqU56vW5YwhHIY7u48KloTM0UnzBQKsCxpTbXgvYjs= github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f h1:J9EGpcZtP0E/raorCMxlFGSTBrsSlaDGf3jU/qvAE2c= github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHovont7NscjpAxXsDA8S8BMYve8Y5+7cuRE7R0= @@ -234,7 +221,6 @@ golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPh golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= -golang.org/x/crypto v0.15.0 h1:frVn1TEaCEaZcn3Tmd7Y2b5KKPaZ+I32Q2OA3kYp5TA= golang.org/x/crypto v0.15.0/go.mod h1:4ChreQoLWfG3xLDer1WdlH5NdlQ3+mwnQq1YTKY+72g= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= diff --git a/internal/commands/validate/tmc-mandatory.schema.json b/internal/commands/validate/tmc-mandatory.schema.json new file mode 100644 index 00000000..1c47560b --- /dev/null +++ b/internal/commands/validate/tmc-mandatory.schema.json @@ -0,0 +1,56 @@ +{ + "description": "JSON Schema for validating Thing Models for importing into Thing Model Catalog", + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "tm-mandatory.schema.json", + "definitions": { + "context": { + "type": "array", + "contains": { + "type": "object", + "properties": { + "schema": { + "const": "https://schema.org/" + } + }, + "required": [ + "schema" + ] + } + }, + "objWithSchemaName": { + "type": "object", + "properties": { + "schema:name": { + "type": "string", + "pattern": "\\w+" + } + }, + "required": [ + "schema:name" + ] + } + }, + "type": "object", + "properties": { + "@context": { + "$ref": "#/definitions/context" + }, + "schema:mpn": { + "type": "string", + "pattern": "\\w+" + }, + "schema:author": { + "$ref": "#/definitions/objWithSchemaName" + }, + "schema:manufacturer": { + "$ref": "#/definitions/objWithSchemaName" + } + }, + "additionalProperties": true, + "required": [ + "@context", + "schema:author", + "schema:manufacturer", + "schema:mpn" + ] +} \ No newline at end of file diff --git a/internal/commands/validate/validate.go b/internal/commands/validate/validate.go index 26d83de0..2f95f715 100644 --- a/internal/commands/validate/validate.go +++ b/internal/commands/validate/validate.go @@ -6,7 +6,6 @@ import ( "encoding/json" "log/slog" - "github.com/go-playground/validator/v10" "github.com/kennygrant/sanitize" "github.com/web-of-things-open-source/go-jsonschema" "github.com/web-of-things-open-source/tm-catalog-cli/internal/model" @@ -18,12 +17,20 @@ var tmValidationSchema []byte //go:embed modbus.schema.json var modbusValidationSchema []byte +//go:embed tmc-mandatory.schema.json +var tmcMandatorySchema []byte + +var tmcMandatoryValidator *jsonschema.Schema var tmValidator *jsonschema.Schema var modbusValidator *jsonschema.Schema -var structValidator *validator.Validate func init() { var err error + tmcMandatoryValidator, err = jsonschema.New(tmcMandatorySchema) + if err != nil { + panic(err) + } + tmValidator, err = jsonschema.New(tmValidationSchema) if err != nil { panic(err) @@ -37,7 +44,6 @@ func init() { panic(err) } - structValidator = validator.New(validator.WithRequiredStructEnabled()) } func ValidateAsTM(raw []byte) error { @@ -59,13 +65,13 @@ func shouldTryModbus(raw []byte) bool { return bytes.Index(raw, []byte("\"modbus:")) != -1 } -func ParseRequiredMetadata(raw []byte) (*model.ThingModel, error) { - tm := &model.ThingModel{} - err := json.Unmarshal(raw, tm) +func ValidateAsTmcImportable(raw []byte) (*model.ThingModel, error) { + _, err := tmcMandatoryValidator.Validate(raw) if err != nil { return nil, err } - err = structValidator.Struct(tm) + tm := &model.ThingModel{} + err = json.Unmarshal(raw, tm) if err != nil { return nil, err } @@ -80,7 +86,7 @@ func ParseRequiredMetadata(raw []byte) (*model.ThingModel, error) { func ValidateThingModel(raw []byte) (*model.ThingModel, error) { log := slog.Default() - tm, err := ParseRequiredMetadata(raw) + tm, err := ValidateAsTmcImportable(raw) if err != nil { return nil, err } diff --git a/internal/commands/validate/validate_test.go b/internal/commands/validate/validate_test.go index 2f435c98..74e60dca 100644 --- a/internal/commands/validate/validate_test.go +++ b/internal/commands/validate/validate_test.go @@ -43,17 +43,50 @@ func TestValidateAsModbus(t *testing.T) { } func TestParseRequiredMetadata(t *testing.T) { - tmFile := `{ "schema:manufacturer": { - "name": "omnicorp GmbH & Co. KG" + tmFile := `{ + "@context": [{ + "schema":"https://schema.org/" + }], + "schema:manufacturer": { + "schema:name": "omnicorp GmbH & Co. KG" }, "schema:mpn": "sense&all", "schema:author": { - "name": "omnicorp R&D/research" + "schema:name": "omnicorp R&D/research" } }` - tm, err := ParseRequiredMetadata([]byte(tmFile)) + tm, err := ValidateAsTmcImportable([]byte(tmFile)) assert.NoError(t, err) assert.Equal(t, "omnicorp-GmbH-Co-KG", tm.Manufacturer.Name) assert.Equal(t, "omnicorp-R-D-research", tm.Author.Name) assert.Equal(t, "sense-all", tm.Mpn) + + tmFile = `{ + "@context": [{ + "schema":"https://schema.org/" + }], + "schema:manufacturer": { + "schema:name": "omnicorp GmbH & Co. KG" + }, + "schema:mpn": "sense&all", + "schema:author": { + "schema:name": " .-" + } +}` + tm, err = ValidateAsTmcImportable([]byte(tmFile)) + assert.Error(t, err) + assert.ErrorContains(t, err, "$.schema:author.schema:name") + + tmFile = `{ + "@context": [{ + "schema":"https://schema.org/" + }], + "schema:mpn": "sense&all", + "schema:author": { + "schema:name": "omnicorp" + } +}` + _, err = ValidateAsTmcImportable([]byte(tmFile)) + assert.Error(t, err) + assert.ErrorContains(t, err, "schema:manufacturer at \"$\"") } diff --git a/internal/model/model.go b/internal/model/model.go index 30c22a19..1ed75129 100644 --- a/internal/model/model.go +++ b/internal/model/model.go @@ -11,10 +11,10 @@ type ThingModel struct { } type SchemaAuthor struct { - Name string `json:"name" validate:"required"` + Name string `json:"schema:name" validate:"required"` } type SchemaManufacturer struct { - Name string `json:"name" validate:"required"` + Name string `json:"schema:name" validate:"required"` } type Version struct { diff --git a/test/data/push/omnilamp-versioned.json b/test/data/push/omnilamp-versioned.json index dcfd6c3e..e1e6ee1f 100644 --- a/test/data/push/omnilamp-versioned.json +++ b/test/data/push/omnilamp-versioned.json @@ -1,16 +1,18 @@ { "@context": [ "https://www.w3.org/2022/wot/td/v1.1", - "https://schema.org/docs/jsonldcontext.json" + { + "schema": "https://schema.org/" + } ], "@type": "tm:ThingModel", "title": "Lamp Thing Model", "schema:manufacturer": { - "name": "omnicorp" + "schema:name": "omnicorp" }, "schema:mpn": "omnilamp", "schema:author": { - "name": "omnicorp TM department" + "schema:name": "omnicorp TM department" }, "properties": { "status": { diff --git a/test/data/push/omnilamp.json b/test/data/push/omnilamp.json index 19955dcc..7bdb549e 100644 --- a/test/data/push/omnilamp.json +++ b/test/data/push/omnilamp.json @@ -1,16 +1,18 @@ { "@context": [ "https://www.w3.org/2022/wot/td/v1.1", - "https://schema.org/docs/jsonldcontext.json" + { + "schema": "https://schema.org/" + } ], "@type": "tm:ThingModel", "title": "Lamp Thing Model", "schema:manufacturer": { - "name": "omnicorp" + "schema:name": "omnicorp" }, "schema:mpn": "omnilamp", "schema:author": { - "name": "omnicorp TM department" + "schema:name": "omnicorp TM department" }, "properties": { "status": { diff --git a/test/data/validate/modbus-senseall-broken.json b/test/data/validate/modbus-senseall-broken.json index 1577f4cd..80c6d535 100644 --- a/test/data/validate/modbus-senseall-broken.json +++ b/test/data/validate/modbus-senseall-broken.json @@ -2,6 +2,7 @@ "@context": [ "https://www.w3.org/2019/wot/td/v1", { + "schema": "https://schema.org/", "modbus": "https://www.w3.org/2019/wot/modbus" } ], @@ -16,10 +17,10 @@ }, "schema:mpn": "senseall", "schema:manufacturer": { - "name": "omnicorp" + "schema:name": "omnicorp" }, "schema:author": { - "name": "omnicorp" + "schema:name": "omnicorp" }, "security": "nosec_sc", "properties": { diff --git a/test/data/validate/modbus-senseall.json b/test/data/validate/modbus-senseall.json index c388cff0..3f4f7d1c 100644 --- a/test/data/validate/modbus-senseall.json +++ b/test/data/validate/modbus-senseall.json @@ -2,6 +2,7 @@ "@context": [ "https://www.w3.org/2019/wot/td/v1", { + "schema": "https://schema.org/", "modbus": "https://www.w3.org/2019/wot/modbus" } ], @@ -16,10 +17,10 @@ }, "schema:mpn": "senseall", "schema:manufacturer": { - "name": "omnicorp" + "schema:name": "omnicorp" }, "schema:author": { - "name": "omnicorp" + "schema:name": "omnicorp" }, "security": "nosec_sc", "properties": { diff --git a/test/data/validate/omnilamp-broken.json b/test/data/validate/omnilamp-broken.json index ae81aafd..839eff93 100644 --- a/test/data/validate/omnilamp-broken.json +++ b/test/data/validate/omnilamp-broken.json @@ -1,16 +1,18 @@ { "@context": [ "https://www.w3.org/2022/wot/td/v1.1", - "https://schema.org/docs/jsonldcontext.json" + { + "schema": "https://schema.org/" + } ], "@type": "tm:ThingModel", "title": "Lamp Thing Model", "schema:manufacturer": { - "name": "omnicorp" + "schema:name": "omnicorp" }, "schema:mpn": "omnilamp", "schema:author": { - "name": "omnicorp TM department" + "schema:name": "omnicorp TM department" }, "properties": { "status": { diff --git a/test/data/validate/omnilamp.json b/test/data/validate/omnilamp.json index 19955dcc..7bdb549e 100644 --- a/test/data/validate/omnilamp.json +++ b/test/data/validate/omnilamp.json @@ -1,16 +1,18 @@ { "@context": [ "https://www.w3.org/2022/wot/td/v1.1", - "https://schema.org/docs/jsonldcontext.json" + { + "schema": "https://schema.org/" + } ], "@type": "tm:ThingModel", "title": "Lamp Thing Model", "schema:manufacturer": { - "name": "omnicorp" + "schema:name": "omnicorp" }, "schema:mpn": "omnilamp", "schema:author": { - "name": "omnicorp TM department" + "schema:name": "omnicorp TM department" }, "properties": { "status": {