From 6b8a4f2e33e5d579b9b1b92b0e3b29ab848af90d Mon Sep 17 00:00:00 2001 From: Mathieu Debove Date: Mon, 4 Feb 2019 10:46:38 +0100 Subject: [PATCH] Generate JSON schema of flow action definition --- .gitignore | 1 + go.sum | 16 ++ internal/schema/assets.go | 235 ++++++++++++++++++ internal/schema/generate/schema_generator.go | 28 +++ internal/schema/schema.go | 47 ++++ internal/schema/schema.json | 236 +++++++++++++++++++ 6 files changed, 563 insertions(+) create mode 100644 internal/schema/assets.go create mode 100644 internal/schema/generate/schema_generator.go create mode 100644 internal/schema/schema.go create mode 100644 internal/schema/schema.json diff --git a/.gitignore b/.gitignore index 1343148f..efdcc340 100644 --- a/.gitignore +++ b/.gitignore @@ -44,6 +44,7 @@ _testmain.go tags .vscode/symbols.json +.idea .build-cache submodules/flogo-cicd/.build-cache ./Dockerfile diff --git a/go.sum b/go.sum index 91f69e2d..b5b33d92 100644 --- a/go.sum +++ b/go.sum @@ -8,12 +8,28 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/project-flogo/core v0.9.0-alpha.1 h1:c6BB8TPct70wvgyJ/FE1CCgZbn5WrLWZSZWJ6qR+Gq8= github.com/project-flogo/core v0.9.0-alpha.1/go.mod h1:2ahj+3BgitIAcO0P/3aW2YClPekCuVJzyDT6twLb2xM= +github.com/project-flogo/core v0.9.0-alpha.3 h1:G7ToVpdoRthE9BhefJWVL1yy+FxhLbA78kbP2EbWx2k= +github.com/project-flogo/core v0.9.0-alpha.3/go.mod h1:BHeB55AxPhvlNGd+it50rE977ag6xE3bD2RluSDeKBA= github.com/sirupsen/logrus v1.1.1 h1:VzGj7lhU7KEB9e9gMpAV/v5XT2NVSvLJhJLCWbnkgXg= github.com/sirupsen/logrus v1.1.1/go.mod h1:zrgwTnHtNr00buQ1vSptGe8m1f/BbgsPukg8qsT7A+A= +github.com/square-it/jsonschema v1.9.1 h1:0pYdNW+bvukTIBqcfal5XXHikgp/AbADeqcP7I0uV4M= +github.com/square-it/jsonschema v1.9.1/go.mod h1:80WJHSuy3YnokzfFopfx+MAt5lVVnVpS6w2Avv+svHk= github.com/stretchr/objx v0.1.1 h1:2vfRuCMp5sSVIDSqO8oNnWJq7mPa6KVP3iPIwFBuy8A= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +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= +github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ= +github.com/xeipuuv/gojsonschema v1.1.0 h1:ngVtJC9TY/lg0AA/1k48FYhBrhRoFlEmWzsehpNAaZg= +github.com/xeipuuv/gojsonschema v1.1.0/go.mod h1:5yf86TLmAcydyeJq5YvxkGPE2fm/u4myDekKRoLuqhs= +go.uber.org/atomic v1.3.2 h1:2Oa65PReHzfn29GpvgsYwloV9AVFHPDk8tYxt2c2tr4= +go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= +go.uber.org/multierr v1.1.0 h1:HoEmRHQPVSqub6w2z2d2EOVs2fjyFRGyofhKuyDq0QI= +go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= +go.uber.org/zap v1.9.1 h1:XCJQEf3W6eZaVwhRBof6ImoYGJSITeKWsyeh3HFu/5o= +go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793 h1:u+LnwYTOOW7Ukr/fppxEb1Nwz0AtPflrblfvUudpo+I= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33 h1:I6FyU15t786LL7oL/hn43zqTuEGr4PN7F4XJ1p4E3Y8= diff --git a/internal/schema/assets.go b/internal/schema/assets.go new file mode 100644 index 00000000..75bf384b --- /dev/null +++ b/internal/schema/assets.go @@ -0,0 +1,235 @@ +// Code generated by go-bindata. +// sources: +// schema.json +// DO NOT EDIT! + +package schema + +import ( + "bytes" + "compress/gzip" + "fmt" + "io" + "io/ioutil" + "os" + "path/filepath" + "strings" + "time" +) + +func bindataRead(data []byte, name string) ([]byte, error) { + gz, err := gzip.NewReader(bytes.NewBuffer(data)) + if err != nil { + return nil, fmt.Errorf("Read %q: %v", name, err) + } + + var buf bytes.Buffer + _, err = io.Copy(&buf, gz) + clErr := gz.Close() + + if err != nil { + return nil, fmt.Errorf("Read %q: %v", name, err) + } + if clErr != nil { + return nil, err + } + + return buf.Bytes(), nil +} + +type asset struct { + bytes []byte + info os.FileInfo +} + +type bindataFileInfo struct { + name string + size int64 + mode os.FileMode + modTime time.Time +} + +func (fi bindataFileInfo) Name() string { + return fi.name +} +func (fi bindataFileInfo) Size() int64 { + return fi.size +} +func (fi bindataFileInfo) Mode() os.FileMode { + return fi.mode +} +func (fi bindataFileInfo) ModTime() time.Time { + return fi.modTime +} +func (fi bindataFileInfo) IsDir() bool { + return false +} +func (fi bindataFileInfo) Sys() interface{} { + return nil +} + +var _schemaJson = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\xec\x58\x4b\x6b\xdb\x40\x10\xbe\xfb\x57\x08\xa5\xc7\xc6\xea\xad\xe0\x5b\x28\x85\x1a\x5a\x5c\x42\x6f\x25\x87\xb5\x35\x72\x36\x91\x77\xd5\xdd\x71\xa8\x09\xfe\xef\x45\xaf\xd5\x63\x1f\x8e\xb4\x6a\x50\x69\x74\x08\xc9\xcc\xee\x68\x1e\xdf\xcc\x37\xca\xf3\x22\x08\x82\x20\x7c\x27\x77\xf7\x70\x20\xe1\x2a\x08\xef\x11\xb3\x55\x14\x3d\x48\xce\xae\x4b\xe9\x92\x8b\x7d\x14\x0b\x92\xe0\xf5\x87\x8f\x51\x29\xbb\x0a\xdf\x97\x37\x05\xfc\x3a\x52\x01\x71\xb8\x0a\x7e\x16\x92\x42\x0a\xbf\xb3\x94\xee\x28\xde\x42\x96\x9e\xaa\xa3\x85\x82\x91\x03\xb4\xff\x3e\xf0\x18\xd2\x8e\x00\x90\xc4\x04\x49\x5b\x86\x44\x3e\xca\xb6\x20\xa5\xac\x2b\x00\x21\xb8\xf8\x42\x58\x9c\x82\x08\x0b\xf1\x5d\xe5\x5f\x26\x78\x06\x02\x29\xc8\x70\x15\x3c\x37\x37\x08\xa2\xa0\xdb\x23\xf6\xe4\x85\x8e\x22\x1c\x74\xb1\x5f\xa2\xba\x56\x04\x24\xb9\x89\xab\x28\x86\x84\x32\x8a\x94\x33\x19\xe5\x61\x2f\x6f\x6a\xbf\xc2\xce\xb5\x73\xd7\x4a\x88\xa7\x0c\x72\x0b\x44\x08\x72\x6a\x8e\x9e\x6d\x39\xd1\x62\xf4\x0e\xc4\x16\x84\xfa\x7d\xf9\xb9\xe5\xc0\x2d\x64\x16\x2f\x3b\x48\xd1\xdc\xac\xe3\xdc\x72\x9e\x02\x61\x66\x1b\x25\x1c\x66\x53\xc6\x26\x03\x5f\x29\x7b\xec\x44\x3e\xb2\x94\xaa\x29\x5e\xad\x8c\xf5\x1b\x97\xeb\xcd\xb7\xfa\xe5\x66\xd7\x8a\x06\xb6\xd6\x4d\xa2\xa0\x6c\x6f\xbe\x5a\xcc\x82\x51\x37\xcb\x81\x30\xc3\x82\xff\x20\x72\x7c\xc1\x17\xad\xc3\x21\x89\xe3\xc2\x24\x49\xbf\xb7\x27\x58\x42\x52\x09\xd5\x91\xda\x0e\xdf\x3e\xc0\x0e\xeb\x81\xdc\xf2\xac\x3b\xf1\x96\x7a\xc2\x8c\xc3\x5b\x69\x6d\x43\xbc\x5b\x40\x83\xbc\x3f\xd4\x1b\x85\x3e\xdc\x9b\xac\xf4\x86\xbc\x52\xf4\x87\x7d\xe3\x9f\x36\xf4\xeb\xe7\xae\x97\x71\x0b\x09\x28\xbd\x83\x0c\xd4\x19\x3b\xb8\xd4\x91\x49\x40\xd6\x58\x1b\x4c\x12\xf5\x73\x36\x5b\xb5\x01\xcf\x71\xcd\x4d\x22\xd3\x07\xee\x41\x2a\xce\x28\x9c\x24\xa3\xe5\x47\x23\x1b\x97\x6d\x33\xf9\x28\xf5\x5c\x60\x73\x81\x94\x1c\x11\x06\x23\xa1\x63\x25\xad\xe9\x03\x1f\x43\x62\x4e\xd7\x8d\xa4\xa6\x25\xa3\x4f\x51\x2e\x93\x46\xb2\xf3\xb2\x68\x26\x41\xa5\x9e\x1f\xf0\x8c\xe4\xe8\x88\x30\x78\x09\xf0\x9c\x4c\x7b\x99\x44\xb5\xf7\x54\x64\x6a\x5c\x3b\x7a\xb3\x57\xa3\xd3\xa9\x5f\xd7\xa4\xee\x66\x87\xf4\x89\xe2\xe9\x13\x67\x09\xdd\xe7\x49\x1c\xc8\xe5\x79\x6d\x4c\x18\xca\xdd\x30\xc8\x25\x20\x52\xb6\x97\x7e\xb4\x4a\x59\x76\xc4\xcb\xa0\xef\xe7\xc0\x90\x0b\x75\x87\x1f\x71\x72\x9b\x25\x6e\x27\xec\x4c\x95\xbd\x49\xdd\xac\xee\x8c\xf5\xf3\x55\x1b\xc5\xce\xd7\x03\x71\x7b\x61\x2b\xf4\x82\xe7\x1b\x73\xbf\x31\xcb\x5c\x98\x45\x2f\xf2\xd0\x46\xb1\x4c\x72\xdb\x37\x1a\x72\x93\x34\x11\xfc\x60\x92\x3f\x91\xf4\x08\x7e\xdd\x56\x98\x9e\xfb\x4a\xc5\x27\xb6\xe7\x37\xb2\xad\x85\xf8\xe7\x58\xa0\x6e\xd0\x81\xa0\xa6\xf1\x90\xa5\xc5\x06\x75\x45\xc7\x06\x1d\xa9\x16\x2b\xcf\xff\x1f\xd4\x56\xe6\xf0\x85\xac\xef\x8a\x2f\x42\x16\x8d\xe7\xde\x9d\xff\xfd\x5a\x65\xfa\x9e\x1d\xd8\x51\xeb\x62\x25\x37\xa4\x61\x53\x2e\xd6\x5e\x6d\xb0\x9e\x7e\xdf\xdf\xf8\xee\xfb\x7f\xbd\x3e\x8b\xf2\xe7\xf9\x4f\x00\x00\x00\xff\xff\xcd\x5c\xdd\x5c\x4a\x1b\x00\x00") + +func schemaJsonBytes() ([]byte, error) { + return bindataRead( + _schemaJson, + "schema.json", + ) +} + +func schemaJson() (*asset, error) { + bytes, err := schemaJsonBytes() + if err != nil { + return nil, err + } + + info := bindataFileInfo{name: "schema.json", size: 6986, mode: os.FileMode(438), modTime: time.Unix(1549274155, 0)} + a := &asset{bytes: bytes, info: info} + return a, nil +} + +// Asset loads and returns the asset for the given name. +// It returns an error if the asset could not be found or +// could not be loaded. +func Asset(name string) ([]byte, error) { + cannonicalName := strings.Replace(name, "\\", "/", -1) + if f, ok := _bindata[cannonicalName]; ok { + a, err := f() + if err != nil { + return nil, fmt.Errorf("Asset %s can't read by error: %v", name, err) + } + return a.bytes, nil + } + return nil, fmt.Errorf("Asset %s not found", name) +} + +// MustAsset is like Asset but panics when Asset would return an error. +// It simplifies safe initialization of global variables. +func MustAsset(name string) []byte { + a, err := Asset(name) + if err != nil { + panic("asset: Asset(" + name + "): " + err.Error()) + } + + return a +} + +// AssetInfo loads and returns the asset info for the given name. +// It returns an error if the asset could not be found or +// could not be loaded. +func AssetInfo(name string) (os.FileInfo, error) { + cannonicalName := strings.Replace(name, "\\", "/", -1) + if f, ok := _bindata[cannonicalName]; ok { + a, err := f() + if err != nil { + return nil, fmt.Errorf("AssetInfo %s can't read by error: %v", name, err) + } + return a.info, nil + } + return nil, fmt.Errorf("AssetInfo %s not found", name) +} + +// AssetNames returns the names of the assets. +func AssetNames() []string { + names := make([]string, 0, len(_bindata)) + for name := range _bindata { + names = append(names, name) + } + return names +} + +// _bindata is a table, holding each asset generator, mapped to its name. +var _bindata = map[string]func() (*asset, error){ + "schema.json": schemaJson, +} + +// AssetDir returns the file names below a certain +// directory embedded in the file by go-bindata. +// For example if you run go-bindata on data/... and data contains the +// following hierarchy: +// data/ +// foo.txt +// img/ +// a.png +// b.png +// then AssetDir("data") would return []string{"foo.txt", "img"} +// AssetDir("data/img") would return []string{"a.png", "b.png"} +// AssetDir("foo.txt") and AssetDir("notexist") would return an error +// AssetDir("") will return []string{"data"}. +func AssetDir(name string) ([]string, error) { + node := _bintree + if len(name) != 0 { + cannonicalName := strings.Replace(name, "\\", "/", -1) + pathList := strings.Split(cannonicalName, "/") + for _, p := range pathList { + node = node.Children[p] + if node == nil { + return nil, fmt.Errorf("Asset %s not found", name) + } + } + } + if node.Func != nil { + return nil, fmt.Errorf("Asset %s not found", name) + } + rv := make([]string, 0, len(node.Children)) + for childName := range node.Children { + rv = append(rv, childName) + } + return rv, nil +} + +type bintree struct { + Func func() (*asset, error) + Children map[string]*bintree +} + +var _bintree = &bintree{nil, map[string]*bintree{ + "schema.json": &bintree{schemaJson, map[string]*bintree{}}, +}} + +// RestoreAsset restores an asset under the given directory +func RestoreAsset(dir, name string) error { + data, err := Asset(name) + if err != nil { + return err + } + info, err := AssetInfo(name) + if err != nil { + return err + } + err = os.MkdirAll(_filePath(dir, filepath.Dir(name)), os.FileMode(0755)) + if err != nil { + return err + } + err = ioutil.WriteFile(_filePath(dir, name), data, info.Mode()) + if err != nil { + return err + } + err = os.Chtimes(_filePath(dir, name), info.ModTime(), info.ModTime()) + if err != nil { + return err + } + return nil +} + +// RestoreAssets restores an asset under the given directory recursively +func RestoreAssets(dir, name string) error { + children, err := AssetDir(name) + // File + if err != nil { + return RestoreAsset(dir, name) + } + // Dir + for _, child := range children { + err = RestoreAssets(dir, filepath.Join(name, child)) + if err != nil { + return err + } + } + return nil +} + +func _filePath(dir, name string) string { + cannonicalName := strings.Replace(name, "\\", "/", -1) + return filepath.Join(append([]string{dir}, strings.Split(cannonicalName, "/")...)...) +} diff --git a/internal/schema/generate/schema_generator.go b/internal/schema/generate/schema_generator.go new file mode 100644 index 00000000..fd932f1e --- /dev/null +++ b/internal/schema/generate/schema_generator.go @@ -0,0 +1,28 @@ +// +build ignore + +package main + +import ( + "encoding/json" + "fmt" + "io/ioutil" + "os" + + "github.com/project-flogo/flow/definition" + "github.com/square-it/jsonschema" +) + +func main() { + reflector := &jsonschema.Reflector{ExpandedStruct: true} + schema := reflector.Reflect(&definition.DefinitionRep{}) + schemaJSON, err := json.MarshalIndent(schema, "", " ") + if err != nil { + fmt.Fprintf(os.Stderr, "error: %v\n", err) + os.Exit(1) + } + err = ioutil.WriteFile("schema.json", schemaJSON, 0644) + if err != nil { + fmt.Fprintf(os.Stderr, "error: %v\n", err) + os.Exit(1) + } +} diff --git a/internal/schema/schema.go b/internal/schema/schema.go new file mode 100644 index 00000000..08743e19 --- /dev/null +++ b/internal/schema/schema.go @@ -0,0 +1,47 @@ +//go:generate go run generate/schema_generator.go +//go:generate go-bindata -pkg schema -o assets.go schema.json + +package schema + +import ( + "bytes" + "errors" + "fmt" + + "github.com/xeipuuv/gojsonschema" +) + +var schema *gojsonschema.Schema + +func init() { + jsonSchema, err := Asset("schema.json") + if err != nil { + panic(err) + } + schemaLoader := gojsonschema.NewStringLoader(string(jsonSchema)) + schema, err = gojsonschema.NewSchema(schemaLoader) + if err != nil { + panic(err) + } +} + +// Validate validates the provided JSON against the v2 JSON schema. +func Validate(JSON []byte) error { + JSONLoader := gojsonschema.NewStringLoader(string(JSON)) + result, err := schema.Validate(JSONLoader) + + if err != nil { + return err + } + + if result.Valid() { + return err + } + var msg bytes.Buffer + + msg.WriteString("The JSON is not valid. See errors:\n") + for _, desc := range result.Errors() { + msg.WriteString(fmt.Sprintf("- %s\n", desc)) + } + return errors.New(msg.String()) +} diff --git a/internal/schema/schema.json b/internal/schema/schema.json new file mode 100644 index 00000000..4a814c13 --- /dev/null +++ b/internal/schema/schema.json @@ -0,0 +1,236 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "required": [ + "explicitReply", + "name", + "model", + "metadata", + "tasks", + "links", + "errorHandler" + ], + "properties": { + "attributes": { + "items": { + "$schema": "http://json-schema.org/draft-07/schema#", + "$ref": "#/definitions/data.Attribute" + }, + "type": "array" + }, + "errorHandler": { + "$schema": "http://json-schema.org/draft-07/schema#", + "$ref": "#/definitions/definition.ErrorHandlerRep" + }, + "explicitReply": { + "type": "boolean" + }, + "links": { + "items": { + "$schema": "http://json-schema.org/draft-07/schema#", + "$ref": "#/definitions/definition.LinkRep" + }, + "type": "array" + }, + "metadata": { + "$schema": "http://json-schema.org/draft-07/schema#", + "$ref": "#/definitions/metadata.IOMetadata" + }, + "model": { + "type": "string" + }, + "name": { + "type": "string" + }, + "tasks": { + "items": { + "$schema": "http://json-schema.org/draft-07/schema#", + "$ref": "#/definitions/definition.TaskRep" + }, + "type": "array" + } + }, + "additionalProperties": false, + "type": "object", + "definitions": { + ".": { + "required": [ + "explicitReply", + "name", + "model", + "metadata", + "tasks", + "links", + "errorHandler" + ], + "properties": { + "attributes": { + "items": { + "$schema": "http://json-schema.org/draft-07/schema#", + "$ref": "#/definitions/data.Attribute" + }, + "type": "array" + }, + "errorHandler": { + "$schema": "http://json-schema.org/draft-07/schema#", + "$ref": "#/definitions/definition.ErrorHandlerRep" + }, + "explicitReply": { + "type": "boolean" + }, + "links": { + "items": { + "$schema": "http://json-schema.org/draft-07/schema#", + "$ref": "#/definitions/definition.LinkRep" + }, + "type": "array" + }, + "metadata": { + "$schema": "http://json-schema.org/draft-07/schema#", + "$ref": "#/definitions/metadata.IOMetadata" + }, + "model": { + "type": "string" + }, + "name": { + "type": "string" + }, + "tasks": { + "items": { + "$schema": "http://json-schema.org/draft-07/schema#", + "$ref": "#/definitions/definition.TaskRep" + }, + "type": "array" + } + }, + "additionalProperties": false, + "type": "object" + }, + "data.Attribute": { + "additionalProperties": false, + "type": "object" + }, + "definition.ActivityConfigRep": { + "required": [ + "ref", + "type", + "settings" + ], + "properties": { + "input": { + "type": "object" + }, + "output": { + "type": "object" + }, + "ref": { + "type": "string" + }, + "settings": { + "type": "object" + }, + "type": { + "type": "string" + } + }, + "additionalProperties": false, + "type": "object" + }, + "definition.ErrorHandlerRep": { + "required": [ + "tasks", + "links" + ], + "properties": { + "links": { + "items": { + "$schema": "http://json-schema.org/draft-07/schema#", + "$ref": "#/definitions/definition.LinkRep" + }, + "type": "array" + }, + "tasks": { + "items": { + "$schema": "http://json-schema.org/draft-07/schema#", + "$ref": "#/definitions/definition.TaskRep" + }, + "type": "array" + } + }, + "additionalProperties": false, + "type": "object" + }, + "definition.LinkRep": { + "required": [ + "type", + "name", + "to", + "from", + "value" + ], + "properties": { + "from": { + "type": "string" + }, + "name": { + "type": "string" + }, + "to": { + "type": "string" + }, + "type": { + "type": "string" + }, + "value": { + "type": "string" + } + }, + "additionalProperties": false, + "type": "object" + }, + "definition.TaskRep": { + "required": [ + "id", + "type", + "name", + "settings", + "activity" + ], + "properties": { + "activity": { + "$schema": "http://json-schema.org/draft-07/schema#", + "$ref": "#/definitions/definition.ActivityConfigRep" + }, + "id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "settings": { + "type": "object" + }, + "type": { + "type": "string" + } + }, + "additionalProperties": false, + "type": "object" + }, + "metadata.IOMetadata": { + "required": [ + "Input", + "Output" + ], + "properties": { + "Input": { + "type": "object" + }, + "Output": { + "type": "object" + } + }, + "additionalProperties": false, + "type": "object" + } + } +} \ No newline at end of file