diff --git a/README.md b/README.md index 070fddb..e6bb8b4 100644 --- a/README.md +++ b/README.md @@ -47,7 +47,7 @@ You may need to run this with `sudo`, or from within an Administrator command sh - node-red-nodegen http://petstore.swagger.io/v2/swagger.json - cd ~/.node-red -- npm install **/node-red-contrib-swagger-petstore +- npm install \/node-red-contrib-swagger-petstore - node-red -> You can use swagger-petstore node on Node-RED flow editor. @@ -57,7 +57,7 @@ You may need to run this with `sudo`, or from within an Administrator command sh - In Node-RED flow editor, edit the function node and to the right of the 'name' option, click on the book icon and select 'Save to library'. Then fill in the 'Export as' with the file name (lower-case.js). - node-red-nodegen ~/.node-red/lib/functions/lower-case.js - cd ~/.node-red -- npm install **/node-red-contrib-lower-case +- npm install \/node-red-contrib-lower-case - node-red -> You can use lower-case node on Node-RED flow editor. @@ -66,7 +66,7 @@ You may need to run this with `sudo`, or from within an Administrator command sh - node-red-nodegen example.jsonld - cd ~/.node-red -- npm install **/node-red-contrib-example-thing +- npm install \/node-red-contrib-example-thing - node-red -> You can use Example Thing node on Node-RED flow editor. @@ -75,7 +75,7 @@ You may need to run this with `sudo`, or from within an Administrator command sh - node-red-nodegen http://example.com/td.jsonld --wottd --lang "en-US,en;q=0.5" - cd ~/.node-red -- npm install **/node-red-contrib-example-thing +- npm install \/node-red-contrib-example-thing - node-red -> You can use Example Thing node on Node-RED flow editor. @@ -86,18 +86,19 @@ You may need to run this with `sudo`, or from within an Administrator command sh - Export JSON definition of the SUBFLOW template from Export menu. We assume it is saved to a file named qrcode.json. - node-red-nodegen qrcode.json - cd ~/.node-red -- npm install **/node-red-contrib-qrcode +- npm install \/node-red-contrib-qrcode - node-red -> You can use qrcode node on Node-RED flow editor. ## Documentation -- [Use cases](https://github.com/node-red/node-red-nodegen/blob/0.2.0/docs/index.md#use-cases) ([Japanese](https://github.com/node-red/node-red-nodegen/blob/0.2.0/docs/index_ja.md#use-cases)) -- [How to use Node generator](https://github.com/node-red/node-red-nodegen/blob/0.2.0/docs/index.md#how-to-use-node-generator) ([Japanese](https://github.com/node-red/node-red-nodegen/blob/0.2.0/docs/index_ja.md#how-to-use-node-generator)) -- [Generated files which node package contains](https://github.com/node-red/node-red-nodegen/blob/0.2.0/docs/index.md#generated-files-which-node-package-contains) ([Japanese](https://github.com/node-red/node-red-nodegen/blob/0.2.0/docs/index_ja.md#generated-files-which-node-package-contains)) -- [How to create a node from OpenAPI document](https://github.com/node-red/node-red-nodegen/blob/0.2.0/docs/index.md#how-to-create-a-node-from-openapi-document) ([Japanese](https://github.com/node-red/node-red-nodegen/blob/0.2.0/docs/index_ja.md#how-to-create-a-node-from-openapi-document)) -- [How to create a node from function node](https://github.com/node-red/node-red-nodegen/blob/0.2.0/docs/index.md#how-to-create-a-node-from-function-node) ([Japanese](https://github.com/node-red/node-red-nodegen/blob/0.2.0/docs/index_ja.md#how-to-create-a-node-from-function-node)) -- [How to create a node from subflow](https://github.com/node-red/node-red-nodegen/blob/0.2.0/docs/index.md#how-to-create-a-node-from-subflow) ([Japanese](https://github.com/node-red/node-red-nodegen/blob/0.2.0/docs/index_ja.md#how-to-create-a-node-from-subflow)) +- [Use cases](https://github.com/node-red/node-red-nodegen/blob/0.2.4/docs/index.md#use-cases) ([Japanese](https://github.com/node-red/node-red-nodegen/blob/0.2.4/docs/index_ja.md#use-cases)) +- [How to use Node generator](https://github.com/node-red/node-red-nodegen/blob/0.2.4/docs/index.md#how-to-use-node-generator) ([Japanese](https://github.com/node-red/node-red-nodegen/blob/0.2.4/docs/index_ja.md#how-to-use-node-generator)) +- [Generated files which node package contains](https://github.com/node-red/node-red-nodegen/blob/0.2.4/docs/index.md#generated-files-which-node-package-contains) ([Japanese](https://github.com/node-red/node-red-nodegen/blob/0.2.4/docs/index_ja.md#generated-files-which-node-package-contains)) +- [How to create a node from OpenAPI document](https://github.com/node-red/node-red-nodegen/blob/0.2.4/docs/index.md#how-to-create-a-node-from-openapi-document) ([Japanese](https://github.com/node-red/node-red-nodegen/blob/0.2.4/docs/index_ja.md#how-to-create-a-node-from-openapi-document)) +- [How to create a node from function node](https://github.com/node-red/node-red-nodegen/blob/0.2.4/docs/index.md#how-to-create-a-node-from-function-node) ([Japanese](https://github.com/node-red/node-red-nodegen/blob/0.2.4/docs/index_ja.md#how-to-create-a-node-from-function-node)) +- [How to create a node from subflow](https://github.com/node-red/node-red-nodegen/blob/0.2.4/docs/index.md#how-to-create-a-node-from-subflow) ([Japanese](https://github.com/node-red/node-red-nodegen/blob/0.2.4/docs/index_ja.md#how-to-create-a-node-from-subflow)) +- [How to create a node from WoT Thing Description](https://github.com/node-red/node-red-nodegen/blob/0.2.4/docs/index.md#how-to-create-a-node-from-wot-thing-description) ([Japanese](https://github.com/node-red/node-red-nodegen/blob/0.2.4/docs/index_ja.md#how-to-create-a-node-from-wot-thing-description)) Note: Currently node generator supports GET and POST methods using JSON format without authentication. diff --git a/bin/node-red-nodegen.js b/bin/node-red-nodegen.js index 2821e0c..824b129 100755 --- a/bin/node-red-nodegen.js +++ b/bin/node-red-nodegen.js @@ -110,8 +110,7 @@ function skipBom(body) { function isSubflowDefinition(data) { return data.find((item) => { - return ((item.type === "subflow") && - (item.hasOwnProperty("meta"))); + return item.type === "subflow"; }); } @@ -156,7 +155,6 @@ if (argv.help || argv.h) { promise.then(function (result) { console.log('Success: ' + result); }).catch(function (error) { - console.log('Error: ' + error); console.log(error.stack); }); } diff --git a/docs/index.md b/docs/index.md index 31bc870..5cbfb46 100644 --- a/docs/index.md +++ b/docs/index.md @@ -801,7 +801,7 @@ If you want to customize the generated node, the following procedures and comman Node generater can encrypt subflow definition included in generated code. Specify AES to --encoding option and encoding key to --encodekey option. When using the node with encrypted subflow definition, specify the encoding key to OS environment variable `NR_FLOW_DECODE_KEY` before starting Node-RED. - + ## How to create a node from WoT Thing Description diff --git a/docs/index_ja.md b/docs/index_ja.md index a1a270f..e18cd7c 100644 --- a/docs/index_ja.md +++ b/docs/index_ja.md @@ -804,7 +804,7 @@ Node-REDユーザは通常、以下の手順で生成したノードをNode-RED ノードジェネレータは生成したノードのサブフロー定義を暗号化することができます。暗号化を行うには、--encodingオプションにAESを、-encodekeyに暗号化キーを指定してください。暗号化指定したノードを利用する場合は、環境変数NR_FLOW_DECODE_KEYに暗号化キーを指定します。 - + ## Thing Descriptionからノードを生成する方法 diff --git a/lib/subflow/index.js b/lib/subflow/index.js index b53b8f1..c70ea80 100644 --- a/lib/subflow/index.js +++ b/lib/subflow/index.js @@ -7,7 +7,6 @@ const crypt = require("crypto-js"); const TEMPLATE_DIR = path.join(__dirname,'../../templates/subflow'); - // Extract Subflow definition from JSON data function getSubflowDef(flow) { const newFlow = []; @@ -24,10 +23,12 @@ function getSubflowDef(flow) { newFlow.push(item); } }); + if (sf == null) { + throw new Error("No module properties in subflow"); + } return [sf, newFlow]; } - // get flow encoding method function getEncoder(encoding) { if (encoding === "AES") { @@ -54,11 +55,10 @@ function createJSON(dstPath, flow, encoding, key) { else { sf.flow = newFlow; } - const data = JSON.stringify(sf, null, "\t"); + const data = JSON.stringify(sf, null, 4); fs.writeFileSync(dstPath, data); } - module.exports = async function(data, options) { "use strict"; @@ -79,6 +79,8 @@ module.exports = async function(data, options) { if (!data.license || (data.license === "")) { data.license = "unknown"; } + data.keywords = data.keywords || meta.keywords; + data.info = meta.info; var params = { nodeName: data.name, @@ -88,6 +90,8 @@ module.exports = async function(data, options) { category: data.category || "subflow", description: data.desc, licenseName: data.license, + nodeRead: sf.info || "", + encoding: options.encoding }; // Make directory diff --git a/lib/swagger/index.js b/lib/swagger/index.js index 2c6d0f5..360802f 100644 --- a/lib/swagger/index.js +++ b/lib/swagger/index.js @@ -24,7 +24,7 @@ async function getSpec(src, data) { let response = await axios.get(src, requestOptions); spec = response.data; } else if (/\.yaml$/.test(src)) { - spec = yamljs.load(util.skipBom(await fs.promises.readFile(src))); + spec = yamljs.load(src); } else if (/\.json/.test(src)) { spec = JSON.parse(util.skipBom(await fs.promises.readFile(src))); } else { diff --git a/lib/webofthings/index.js b/lib/webofthings/index.js index 66e79a2..2845637 100644 --- a/lib/webofthings/index.js +++ b/lib/webofthings/index.js @@ -37,6 +37,19 @@ module.exports = async function(data, options) { console.info(`Schema validation succeeded.`); } + // if there is no title but titles, put one of titles to title. + if (!td.title) { + if (td.titles) { + if (td.titles.en) { + td.title = td.titles.en + } else { + td.title = Object.values(td.titles)[0]; + } + } else { + td.title = "notitle"; + } + } + // if name is not specified, use td.title for module name. if (!data.name || data.name === '') { // filtering out special characters diff --git a/lib/webofthings/td-json-schema-validation.json b/lib/webofthings/td-json-schema-validation.json index 5751ca5..f02ddc0 100644 --- a/lib/webofthings/td-json-schema-validation.json +++ b/lib/webofthings/td-json-schema-validation.json @@ -1,1153 +1,1316 @@ { - "title": "WoT TD Schema - 16 October 2019", - "description": "JSON Schema for validating TD instances against the TD model. TD instances can be with or without terms that have default values", + "title": "Thing Description", + "version": "1.1-28-January-2022", + "description": "JSON Schema for validating TD instances against the TD information model. TD instances can be with or without terms that have default values", "$schema ": "http://json-schema.org/draft-07/schema#", + "$id":"https://raw.githubusercontent.com/w3c/wot-thing-description/main/validation/td-json-schema-validation.json", "definitions": { - "thing-context-w3c-uri": { - "type": "string", - "enum": [ - "https://www.w3.org/2019/wot/td/v1" - ] - }, - "thing-context": { - "oneOf": [{ - "type": "array", - "items": { - "anyOf": [{ - "$ref": "#/definitions/anyUri" - }, - { - "type": "object" - } - ] - }, - "contains": { - "$ref": "#/definitions/thing-context-w3c-uri" - } + "anyUri": { + "type": "string", + "format": "iri-reference" + }, + "description": { + "type": "string" + }, + "descriptions": { + "type": "object", + "additionalProperties": { + "type": "string" + } + }, + "title": { + "type": "string" + }, + "titles": { + "type": "object", + "additionalProperties": { + "type": "string" + } + }, + "security": { + "oneOf": [ + { + "type": "array", + "items": { + "type": "string" + } + }, + { + "type": "string" + } + ] + }, + "scopes": { + "oneOf": [ + { + "type": "array", + "items": { + "type": "string" + } + }, + { + "type": "string" + } + ] + }, + "subprotocol": { + "type": "string", + "examples": [ + "longpoll", + "websub", + "sse" + ] + }, + "thing-context-w3c-uri": { + "type": "string", + "enum": [ + "https://www.w3.org/2019/wot/td/v1", + "http://www.w3.org/ns/td", + "https://www.w3.org/2022/wot/td/v1.1" + ] + }, + "thing-context": { + "oneOf": [ + { + "type": "array", + "items": [ + { + "$ref": "#/definitions/thing-context-w3c-uri" + } + ], + "additionalItems": { + "anyOf": [ + { + "$ref": "#/definitions/anyUri" }, { - "$ref": "#/definitions/thing-context-w3c-uri" + "type": "object" + } + ] + } + }, + { + "$ref": "#/definitions/thing-context-w3c-uri" + } + ] + }, + "type_declaration": { + "oneOf": [ + { + "type": "string", + "not": { + "const": "tm:ThingModel" + } + }, + { + "type": "array", + "items": { + "type": "string", + "not": { + "const": "tm:ThingModel" + } + } + } + ] + }, + "dataSchema-type": { + "type": "string", + "enum": [ + "boolean", + "integer", + "number", + "string", + "object", + "array", + "null" + ] + }, + "dataSchema": { + "type": "object", + "properties": { + "@type": { + "$ref": "#/definitions/type_declaration" + }, + "description": { + "$ref": "#/definitions/description" + }, + "title": { + "$ref": "#/definitions/title" + }, + "descriptions": { + "$ref": "#/definitions/descriptions" + }, + "titles": { + "$ref": "#/definitions/titles" + }, + "writeOnly": { + "type": "boolean" + }, + "readOnly": { + "type": "boolean" + }, + "oneOf": { + "type": "array", + "items": { + "$ref": "#/definitions/dataSchema" + } + }, + "unit": { + "type": "string" + }, + "enum": { + "type": "array", + "minItems": 1, + "uniqueItems": true + }, + "format": { + "type": "string" + }, + "const": {}, + "default": {}, + "contentEncoding": { + "type": "string" + }, + "contentMediaType": { + "type": "string" + }, + "type": { + "$ref": "#/definitions/dataSchema-type" + }, + "items": { + "oneOf": [ + { + "$ref": "#/definitions/dataSchema" + }, + { + "type": "array", + "items": { + "$ref": "#/definitions/dataSchema" } + } ] - }, - "type_declaration": { - "oneOf": [{ - "type": "string" - }, - { - "type": "array", - "items": { - "type": "string" - } + }, + "maxItems": { + "type": "integer", + "minimum": 0 + }, + "minItems": { + "type": "integer", + "minimum": 0 + }, + "minimum": { + "type": "number" + }, + "maximum": { + "type": "number" + }, + "exclusiveMinimum": { + "type": "number" + }, + "exclusiveMaximum": { + "type": "number" + }, + "minLength": { + "type": "integer", + "minimum": 0 + }, + "maxLength": { + "type": "integer", + "minimum": 0 + }, + "multipleOf": { + "$ref": "#/definitions/multipleOfDefinition" + }, + "properties": { + "additionalProperties": { + "$ref": "#/definitions/dataSchema" + } + }, + "required": { + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "additionalResponsesDefinition": { + "type": "array", + "items": { + "type": "object", + "properties": { + "contentType": { + "type": "string" + }, + "schema": { + "type": "string" + }, + "success": { + "type": "boolean" + } + } + } + }, + "multipleOfDefinition": { + "type": [ + "integer", + "number" + ], + "exclusiveMinimum": 0 + }, + "expectedResponse": { + "type": "object", + "properties": { + "contentType": { + "type": "string" + } + } + }, + "form_element_base": { + "type": "object", + "properties": { + "op": { + "oneOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" } + } ] + }, + "href": { + "$ref": "#/definitions/anyUri" + }, + "contentType": { + "type": "string" + }, + "contentCoding": { + "type": "string" + }, + "subprotocol": { + "$ref": "#/definitions/subprotocol" + }, + "security": { + "$ref": "#/definitions/security" + }, + "scopes": { + "$ref": "#/definitions/scopes" + }, + "response": { + "$ref": "#/definitions/expectedResponse" + }, + "additionalResponses": { + "$ref": "#/definitions/additionalResponsesDefinition" + } }, - "property_element": { - "type": "object", - "properties": { - "@type": { - "$ref": "#/definitions/type_declaration" - }, - "description": { - "$ref": "#/definitions/description" - }, - "descriptions": { - "$ref": "#/definitions/descriptions" - }, - "title": { - "$ref": "#/definitions/title" - }, - "titles": { - "$ref": "#/definitions/titles" - }, - "uriVariables": { - "type": "object", - "additionalProperties": { - "$ref": "#/definitions/dataSchema" - } - }, - "forms": { - "type": "array", - "minItems": 1, - "items": { - "$ref": "#/definitions/form_element_property" - } - }, - "observable": { - "type": "boolean" - }, - "writeOnly": { - "type": "boolean" - }, - "readOnly": { - "type": "boolean" - }, - "oneOf": { - "type": "array", - "items": { - "$ref": "#/definitions/dataSchema" - } - }, - "unit": { - "type": "string" - }, - "enum": { - "type": "array", - "minItems": 1, - "uniqueItems": true - }, - "format": { - "type": "string" - }, - "const": {}, - "type": { - "type": "string", - "enum": [ - "boolean", - "integer", - "number", - "string", - "object", - "array", - "null" - ] - }, + "required": [ + "href" + ], + "additionalProperties": true + }, + "form_element_property": { + "allOf":[{"$ref":"#/definitions/form_element_base"}], + "type": "object", + "properties": { + "op": { + "oneOf": [ + { + "type": "string", + "enum": [ + "readproperty", + "writeproperty", + "observeproperty", + "unobserveproperty" + ] + }, + { + "type": "array", "items": { - "oneOf": [{ - "$ref": "#/definitions/dataSchema" - }, - { - "type": "array", - "items": { - "$ref": "#/definitions/dataSchema" - } - } - ] - }, - "maxItems": { - "type": "integer", - "minimum": 0 - }, - "minItems": { - "type": "integer", - "minimum": 0 - }, - "minimum": { - "type": "number" - }, - "maximum": { - "type": "number" - }, - "properties": { - "additionalProperties": { - "$ref": "#/definitions/dataSchema" - } - }, - "required": { - "type": "array", - "items": { - "type": "string" - } + "type": "string", + "enum": [ + "readproperty", + "writeproperty", + "observeproperty", + "unobserveproperty" + ] } - }, - "required": [ - "forms" - ], - "additionalProperties": true + } + ] + } }, - "action_element": { - "type": "object", - "properties": { - "description": { - "type": "string" - }, - "descriptions": { - "$ref": "#/definitions/descriptions" - }, - "title": { - "$ref": "#/definitions/title" - }, - "titles": { - "$ref": "#/definitions/titles" - }, - "uriVariables": { - "type": "object", - "additionalProperties": { - "$ref": "#/definitions/dataSchema" - } - }, - "@type": { - "$ref": "#/definitions/type_declaration" - }, - "forms": { - "type": "array", - "minItems": 1, - "items": { - "$ref": "#/definitions/form_element_action" - } - }, - "input": { - "$ref": "#/definitions/dataSchema" - }, - "output": { - "$ref": "#/definitions/dataSchema" - }, - "safe": { - "type": "boolean" - }, - "idempotent": { - "type": "boolean" + "additionalProperties": true + }, + "form_element_action": { + "allOf":[{"$ref":"#/definitions/form_element_base"}], + "type": "object", + "properties": { + "op": { + "oneOf": [ + { + "type": "string", + "enum": [ + "invokeaction", + "queryaction", + "cancelaction" + ] + }, + { + "type": "array", + "items": { + "type": "string", + "enum": [ + "invokeaction", + "queryaction", + "cancelaction" + ] } - }, - "required": [ - "forms" - ], - "additionalProperties": true + } + ] + } }, - "event_element": { - "type": "object", - "properties": { - "description": { - "type": "string" - }, - "descriptions": { - "$ref": "#/definitions/descriptions" - }, - "title": { - "$ref": "#/definitions/title" - }, - "titles": { - "$ref": "#/definitions/titles" - }, - "uriVariables": { - "type": "object", - "additionalProperties": { - "$ref": "#/definitions/dataSchema" - } - }, - "@type": { - "$ref": "#/definitions/type_declaration" - }, - "forms": { - "type": "array", - "minItems": 1, - "items": { - "$ref": "#/definitions/form_element_event" - } - }, - "subscription": { - "$ref": "#/definitions/dataSchema" - }, - "data": { - "$ref": "#/definitions/dataSchema" - }, - "cancellation": { - "$ref": "#/definitions/dataSchema" - }, - "type": { - "not": {} - }, - "enum": { - "not": {} - }, - "const": { - "not": {} + "additionalProperties": true + }, + "form_element_event": { + "allOf":[{"$ref":"#/definitions/form_element_base"}], + "type": "object", + "properties": { + "op": { + "oneOf": [ + { + "type": "string", + "enum": [ + "subscribeevent", + "unsubscribeevent" + ] + }, + { + "type": "array", + "items": { + "type": "string", + "enum": [ + "subscribeevent", + "unsubscribeevent" + ] } - }, - "required": [ - "forms" - ], - "additionalProperties": true + } + ] + } }, - "form_element_property": { - "type": "object", - "properties": { - "href": { - "$ref": "#/definitions/anyUri" - }, - "op": { - "oneOf": [{ - "type": "string", - "enum": [ - "readproperty", - "writeproperty", - "observeproperty", - "unobserveproperty" - ] - }, - { - "type": "array", - "items": { - "type": "string", - "enum": [ - "readproperty", - "writeproperty", - "observeproperty", - "unobserveproperty" - ] - } - } - ] - }, - "contentType": { - "type": "string" - }, - "security": { - "type": "array", - "items": { - "type": "string" - } - }, - "scopes": { - "type": "array", - "items": { - "type": "string" - } - }, - "subProtocol": { - "type": "string", - "enum": [ - "longpoll", - "websub", - "sse" - ] - }, - "response": { - "type": "object", - "properties": { - "contentType": { - "type": "string" - } - } + "additionalProperties": true + }, + "form_element_root": { + "allOf":[{"$ref":"#/definitions/form_element_base"}], + "type": "object", + "properties": { + "op": { + "oneOf": [ + { + "type": "string", + "enum": [ + "readallproperties", + "writeallproperties", + "readmultipleproperties", + "writemultipleproperties", + "observeallproperties", + "unobserveallproperties", + "queryallactions", + "subscribeallevents", + "unsubscribeallevents" + ] + }, + { + "type": "array", + "items": { + "type": "string", + "enum": [ + "readallproperties", + "writeallproperties", + "readmultipleproperties", + "writemultipleproperties", + "observeallproperties", + "unobserveallproperties", + "queryallactions", + "subscribeallevents", + "unsubscribeallevents" + ] } - }, - "required": [ - "href" - ], - "additionalProperties": true + } + ] + } }, - "form_element_action": { + "additionalProperties": true + }, + "form" : { + "$comment":"This is NOT for validation purposes but for automatic generation of TS types. For more info, please see: https://github.com/w3c/wot-thing-description/pull/1319#issuecomment-994950057", + "oneOf": [ + { "$ref" : "#/definitions/form_element_property" }, + { "$ref" : "#/definitions/form_element_action" }, + { "$ref" : "#/definitions/form_element_event" }, + { "$ref" : "#/definitions/form_element_root" } + ] + }, + "property_element": { + "type": "object", + "properties": { + "@type": { + "$ref": "#/definitions/type_declaration" + }, + "description": { + "$ref": "#/definitions/description" + }, + "descriptions": { + "$ref": "#/definitions/descriptions" + }, + "title": { + "$ref": "#/definitions/title" + }, + "titles": { + "$ref": "#/definitions/titles" + }, + "forms": { + "type": "array", + "minItems": 1, + "items": { + "$ref": "#/definitions/form_element_property" + } + }, + "uriVariables": { "type": "object", - "properties": { - "href": { - "$ref": "#/definitions/anyUri" - }, - "op": { - "oneOf": [{ - "type": "string", - "enum": [ - "invokeaction" - ] - }, - { - "type": "array", - "items": { - "type": "string", - "enum": [ - "invokeaction" - ] - } - } - ] - }, - "contentType": { - "type": "string" - }, - "security": { - "type": "array", - "items": { - "type": "string" - } - }, - "scopes": { - "type": "array", - "items": { - "type": "string" - } - }, - "subProtocol": { - "type": "string", - "enum": [ - "longpoll", - "websub", - "sse" - ] - }, - "response": { - "type": "object", - "properties": { - "contentType": { - "type": "string" - } - } + "additionalProperties": { + "$ref": "#/definitions/dataSchema" + } + }, + "observable": { + "type": "boolean" + }, + "writeOnly": { + "type": "boolean" + }, + "readOnly": { + "type": "boolean" + }, + "oneOf": { + "type": "array", + "items": { + "$ref": "#/definitions/dataSchema" + } + }, + "unit": { + "type": "string" + }, + "enum": { + "type": "array", + "minItems": 1, + "uniqueItems": true + }, + "format": { + "type": "string" + }, + "const": {}, + "default": {}, + "type": { + "$ref": "#/definitions/dataSchema-type" + }, + "items": { + "oneOf": [ + { + "$ref": "#/definitions/dataSchema" + }, + { + "type": "array", + "items": { + "$ref": "#/definitions/dataSchema" } - }, - "required": [ - "href" - ], - "additionalProperties": true + } + ] + }, + "maxItems": { + "type": "integer", + "minimum": 0 + }, + "minItems": { + "type": "integer", + "minimum": 0 + }, + "minimum": { + "type": "number" + }, + "maximum": { + "type": "number" + }, + "exclusiveMinimum": { + "type": "number" + }, + "exclusiveMaximum": { + "type": "number" + }, + "minLength": { + "type": "integer", + "minimum": 0 + }, + "maxLength": { + "type": "integer", + "minimum": 0 + }, + "multipleOf": { + "$ref": "#/definitions/multipleOfDefinition" + }, + "properties": { + "additionalProperties": { + "$ref": "#/definitions/dataSchema" + } + }, + "required": { + "type": "array", + "items": { + "type": "string" + } + } }, - "form_element_event": { + "required": [ + "forms" + ], + "additionalProperties": true + }, + "action_element": { + "type": "object", + "properties": { + "@type": { + "$ref": "#/definitions/type_declaration" + }, + "description": { + "$ref": "#/definitions/description" + }, + "descriptions": { + "$ref": "#/definitions/descriptions" + }, + "title": { + "$ref": "#/definitions/title" + }, + "titles": { + "$ref": "#/definitions/titles" + }, + "forms": { + "type": "array", + "minItems": 1, + "items": { + "$ref": "#/definitions/form_element_action" + } + }, + "uriVariables": { "type": "object", - "properties": { - "href": { - "$ref": "#/definitions/anyUri" - }, - "op": { - "oneOf": [{ - "type": "string", - "enum": [ - "subscribeevent", - "unsubscribeevent" - ] - }, - { - "type": "array", - "items": { - "type": "string", - "enum": [ - "subscribeevent", - "unsubscribeevent" - ] - } - } - ] - }, - "contentType": { - "type": "string" - }, - "security": { - "type": "array", - "items": { - "type": "string" - } - }, - "scopes": { - "type": "array", - "items": { - "type": "string" - } - }, - "subProtocol": { - "type": "string", - "enum": [ - "longpoll", - "websub", - "sse" - ] - }, - "response": { - "type": "object", - "properties": { - "contentType": { - "type": "string" - } - } - } - }, - "required": [ - "href" - ], - "additionalProperties": true + "additionalProperties": { + "$ref": "#/definitions/dataSchema" + } + }, + "input": { + "$ref": "#/definitions/dataSchema" + }, + "output": { + "$ref": "#/definitions/dataSchema" + }, + "safe": { + "type": "boolean" + }, + "idempotent": { + "type": "boolean" + } }, - "form_element_root": { + "required": [ + "forms" + ], + "additionalProperties": true + }, + "event_element": { + "type": "object", + "properties": { + "@type": { + "$ref": "#/definitions/type_declaration" + }, + "description": { + "$ref": "#/definitions/description" + }, + "descriptions": { + "$ref": "#/definitions/descriptions" + }, + "title": { + "$ref": "#/definitions/title" + }, + "titles": { + "$ref": "#/definitions/titles" + }, + "forms": { + "type": "array", + "minItems": 1, + "items": { + "$ref": "#/definitions/form_element_event" + } + }, + "uriVariables": { "type": "object", - "properties": { - "href": { - "$ref": "#/definitions/anyUri" - }, - "op": { - "oneOf": [{ - "type": "string", - "enum": [ - "readallproperties", - "writeallproperties", - "readmultipleproperties", - "writemultipleproperties" - ] - }, - { - "type": "array", - "items": { - "type": "string", - "enum": [ - "readallproperties", - "writeallproperties", - "readmultipleproperties", - "writemultipleproperties" - ] - } - } - ] - }, - "contentType": { - "type": "string" - }, - "security": { - "type": "array", - "items": { - "type": "string" - } - }, - "scopes": { - "type": "array", - "items": { - "type": "string" - } - }, - "subProtocol": { - "type": "string", - "enum": [ - "longpoll", - "websub", - "sse" - ] - }, - "response": { - "type": "object", - "properties": { - "contentType": { - "type": "string" - } - } - } - }, - "required": [ - "href" - ], - "additionalProperties": true + "additionalProperties": { + "$ref": "#/definitions/dataSchema" + } + }, + "subscription": { + "$ref": "#/definitions/dataSchema" + }, + "data": { + "$ref": "#/definitions/dataSchema" + }, + "dataResponse": { + "$ref": "#/definitions/dataSchema" + }, + "cancellation": { + "$ref": "#/definitions/dataSchema" + } }, - "description": { + "required": [ + "forms" + ], + "additionalProperties": true + }, + "base_link_element": { + "type": "object", + "properties": { + "href": { + "$ref": "#/definitions/anyUri" + }, + "type": { "type": "string" - }, - "title": { + }, + "rel": { "type": "string" + }, + "anchor": { + "$ref": "#/definitions/anyUri" + } }, - "descriptions": { - "type": "object" + "required": [ + "href" + ], + "additionalProperties": true + }, + "link_element": { + "allOf": [ + { + "$ref": "#/definitions/base_link_element" + }, + { + "not": { + "description": "A basic link element should not contain sizes", + "type": "object", + "properties": { + "sizes": {} + }, + "required": [ + "sizes" + ] + } + }, + { + "not": { + "description": "A basic link element should not contain icon or tm:extends", + "properties": { + "rel": { + "enum": [ + "icon", + "tm:extends" + ] + } + }, + "required": [ + "rel" + ] + } + } + ] + }, + "icon_link_element": { + "allOf": [ + { + "$ref": "#/definitions/base_link_element" + }, + { + "properties": { + "rel": { + "const": "icon" + }, + "sizes": { + "type": "string", + "pattern": "[0-9]*x[0-9]+" + } + }, + "required": [ + "rel" + ] + } + ] + }, + "noSecurityScheme": { + "type": "object", + "properties": { + "@type": { + "$ref": "#/definitions/type_declaration" + }, + "description": { + "$ref": "#/definitions/description" + }, + "descriptions": { + "$ref": "#/definitions/descriptions" + }, + "proxy": { + "$ref": "#/definitions/anyUri" + }, + "scheme": { + "type": "string", + "enum": [ + "nosec" + ] + } }, - "titles": { - "type": "object" + "required": [ + "scheme" + ] + }, + "autoSecurityScheme": { + "type": "object", + "properties": { + "@type": { + "$ref": "#/definitions/type_declaration" + }, + "description": { + "$ref": "#/definitions/description" + }, + "descriptions": { + "$ref": "#/definitions/descriptions" + }, + "proxy": { + "$ref": "#/definitions/anyUri" + }, + "scheme": { + "type": "string", + "enum": [ + "auto" + ] + } }, - "dataSchema": { + "required": [ + "scheme" + ] + }, + "comboSecurityScheme": { + "oneOf": [ + { "type": "object", "properties": { - "@type": { - "$ref": "#/definitions/type_declaration" - }, - "description": { - "$ref": "#/definitions/description" - }, - "title": { - "$ref": "#/definitions/title" - }, - "descriptions": { - "$ref": "#/definitions/descriptions" - }, - "titles": { - "$ref": "#/definitions/titles" - }, - "writeOnly": { - "type": "boolean" - }, - "readOnly": { - "type": "boolean" - }, - "oneOf": { - "type": "array", - "items": { - "$ref": "#/definitions/dataSchema" - } - }, - "unit": { - "type": "string" - }, - "enum": { - "type": "array", - "minItems": 1, - "uniqueItems": true - }, - "format": { - "type": "string" - }, - "const": {}, - "type": { - "type": "string", - "enum": [ - "boolean", - "integer", - "number", - "string", - "object", - "array", - "null" - ] - }, + "@type": { + "$ref": "#/definitions/type_declaration" + }, + "description": { + "$ref": "#/definitions/description" + }, + "descriptions": { + "$ref": "#/definitions/descriptions" + }, + "proxy": { + "$ref": "#/definitions/anyUri" + }, + "scheme": { + "type": "string", + "enum": [ + "combo" + ] + }, + "oneOf": { + "type": "array", + "minItems": 2, "items": { - "oneOf": [{ - "$ref": "#/definitions/dataSchema" - }, - { - "type": "array", - "items": { - "$ref": "#/definitions/dataSchema" - } - } - ] - }, - "maxItems": { - "type": "integer", - "minimum": 0 - }, - "minItems": { - "type": "integer", - "minimum": 0 - }, - "minimum": { - "type": "number" - }, - "maximum": { - "type": "number" - }, - "properties": { - "additionalProperties": { - "$ref": "#/definitions/dataSchema" - } - }, - "required": { - "type": "array", - "items": { - "type": "string" - } + "type": "string" } - } - }, - "link_element": { + } + }, + "required": [ + "scheme", + "oneOf" + ] + }, + { "type": "object", "properties": { - "anchor": { - "$ref": "#/definitions/anyUri" - }, - "href": { - "$ref": "#/definitions/anyUri" - }, - "rel": { - "type": "string" - }, - "type": { - "type": "string" + "@type": { + "$ref": "#/definitions/type_declaration" + }, + "description": { + "$ref": "#/definitions/description" + }, + "descriptions": { + "$ref": "#/definitions/descriptions" + }, + "proxy": { + "$ref": "#/definitions/anyUri" + }, + "scheme": { + "type": "string", + "enum": [ + "combo" + ] + }, + "allOf": { + "type": "array", + "minItems": 2, + "items": { + "type": "string" } + } }, "required": [ - "href" - ], - "additionalProperties": true - }, - "securityScheme": { - "oneOf": [{ - "type": "object", - "properties": { - "@type": { - "$ref": "#/definitions/type_declaration" - }, - "description": { - "$ref": "#/definitions/description" - }, - "descriptions": { - "$ref": "#/definitions/descriptions" - }, - "proxy": { - "$ref": "#/definitions/anyUri" - }, - "scheme": { - "type": "string", - "enum": [ - "nosec" - ] - } - }, - "required": [ - "scheme" - ] - }, - { - "type": "object", - "properties": { - "@type": { - "$ref": "#/definitions/type_declaration" - }, - "description": { - "$ref": "#/definitions/description" - }, - "descriptions": { - "$ref": "#/definitions/descriptions" - }, - "proxy": { - "$ref": "#/definitions/anyUri" - }, - "scheme": { - "type": "string", - "enum": [ - "basic" - ] - }, - "in": { - "type": "string", - "enum": [ - "header", - "query", - "body", - "cookie" - ] - }, - "name": { - "type": "string" - } - }, - "required": [ - "scheme" - ] - }, - { - "type": "object", - "properties": { - "@type": { - "$ref": "#/definitions/type_declaration" - }, - "description": { - "$ref": "#/definitions/description" - }, - "descriptions": { - "$ref": "#/definitions/descriptions" - }, - "proxy": { - "$ref": "#/definitions/anyUri" - }, - "scheme": { - "type": "string", - "enum": [ - "cert" - ] - }, - "identity": { - "type": "string" - } - }, - "required": [ - "scheme" - ] - }, - { - "type": "object", - "properties": { - "@type": { - "$ref": "#/definitions/type_declaration" - }, - "description": { - "$ref": "#/definitions/description" - }, - "descriptions": { - "$ref": "#/definitions/descriptions" - }, - "proxy": { - "$ref": "#/definitions/anyUri" - }, - "scheme": { - "type": "string", - "enum": [ - "digest" - ] - }, - "qop": { - "type": "string", - "enum": [ - "auth", - "auth-int" - ] - }, - "in": { - "type": "string", - "enum": [ - "header", - "query", - "body", - "cookie" - ] - }, - "name": { - "type": "string" - } - }, - "required": [ - "scheme" - ] - }, - { - "type": "object", - "properties": { - "@type": { - "$ref": "#/definitions/type_declaration" - }, - "description": { - "$ref": "#/definitions/description" - }, - "descriptions": { - "$ref": "#/definitions/descriptions" - }, - "proxy": { - "$ref": "#/definitions/anyUri" - }, - "scheme": { - "type": "string", - "enum": [ - "bearer" - ] - }, - "authorization": { - "$ref": "#/definitions/anyUri" - }, - "alg": { - "type": "string", - "enum": [ - "MD5", - "ES256", - "ES512-256" - ] - }, - "format": { - "type": "string", - "enum": [ - "jwt", - "jwe", - "jws" - ] - }, - "in": { - "type": "string", - "enum": [ - "header", - "query", - "body", - "cookie" - ] - }, - "name": { - "type": "string" - } - }, - "required": [ - "scheme" - ] - }, - { - "type": "object", - "properties": { - "@type": { - "$ref": "#/definitions/type_declaration" - }, - "description": { - "$ref": "#/definitions/description" - }, - "descriptions": { - "$ref": "#/definitions/descriptions" - }, - "proxy": { - "$ref": "#/definitions/anyUri" - }, - "scheme": { - "type": "string", - "enum": [ - "psk" - ] - }, - "identity": { - "type": "string" - } - }, - "required": [ - "scheme" - ] - }, - { - "type": "object", - "properties": { - "@type": { - "$ref": "#/definitions/type_declaration" - }, - "description": { - "$ref": "#/definitions/description" - }, - "descriptions": { - "$ref": "#/definitions/descriptions" - }, - "proxy": { - "$ref": "#/definitions/anyUri" - }, - "scheme": { - "type": "string", - "enum": [ - "public" - ] - }, - "identity": { - "type": "string" - } - }, - "required": [ - "scheme" - ] - }, - { - "type": "object", - "properties": { - "@type": { - "$ref": "#/definitions/type_declaration" - }, - "description": { - "$ref": "#/definitions/description" - }, - "descriptions": { - "$ref": "#/definitions/descriptions" - }, - "proxy": { - "$ref": "#/definitions/anyUri" - }, - "scheme": { - "type": "string", - "enum": [ - "oauth2" - ] - }, - "authorization": { - "$ref": "#/definitions/anyUri" - }, - "token": { - "$ref": "#/definitions/anyUri" - }, - "refresh": { - "$ref": "#/definitions/anyUri" - }, - "scopes": { - "type": "array", - "items": { - "type": "string" - } - }, - "flow": { - "type": "string", - "enum": [ - "implicit", - "password", - "client", - "code" - ] - } - }, - "required": [ - "scheme", - "flow" - ] - }, - { - "type": "object", - "properties": { - "@type": { - "$ref": "#/definitions/type_declaration" - }, - "description": { - "$ref": "#/definitions/description" - }, - "descriptions": { - "$ref": "#/definitions/descriptions" - }, - "proxy": { - "$ref": "#/definitions/anyUri" - }, - "scheme": { - "type": "string", - "enum": [ - "apikey" - ] - }, - "in": { - "type": "string", - "enum": [ - "header", - "query", - "body", - "cookie" - ] - }, - "name": { - "type": "string" - } - }, - "required": [ - "scheme" - ] - }, - { - "type": "object", - "properties": { - "@type": { - "$ref": "#/definitions/type_declaration" - }, - "description": { - "$ref": "#/definitions/description" - }, - "descriptions": { - "$ref": "#/definitions/descriptions" - }, - "proxy": { - "$ref": "#/definitions/anyUri" - }, - "scheme": { - "type": "string", - "enum": [ - "pop" - ] - }, - "authorization": { - "$ref": "#/definitions/anyUri" - }, - "format": { - "type": "string", - "enum": [ - "jwt", - "jwe", - "jws" - ] - }, - "alg": { - "type": "string", - "enum": [ - "MD5", - "ES256", - "ES512-256" - ] - }, - "in": { - "type": "string", - "enum": [ - "header", - "query", - "body", - "cookie" - ] - }, - "name": { - "type": "string" - } - }, - "required": [ - "scheme" - ] - } + "scheme", + "allOf" ] - }, - "anyUri": { + } + ] + }, + "basicSecurityScheme": { + "type": "object", + "properties": { + "@type": { + "$ref": "#/definitions/type_declaration" + }, + "description": { + "$ref": "#/definitions/description" + }, + "descriptions": { + "$ref": "#/definitions/descriptions" + }, + "proxy": { + "$ref": "#/definitions/anyUri" + }, + "scheme": { "type": "string", - "format": "uri-reference" - } - }, - "type": "object", - "properties": { - "id": { + "enum": [ + "basic" + ] + }, + "in": { "type": "string", - "format": "uri" - }, - "title": { - "$ref": "#/definitions/title" - }, - "titles": { - "$ref": "#/definitions/titles" + "enum": [ + "header", + "query", + "body", + "cookie", + "auto" + ] + }, + "name": { + "type": "string" + } }, + "required": [ + "scheme" + ] + }, + "digestSecurityScheme": { + "type": "object", "properties": { - "type": "object", - "additionalProperties": { - "$ref": "#/definitions/property_element" - } - }, - "actions": { - "type": "object", - "additionalProperties": { - "$ref": "#/definitions/action_element" - } + "@type": { + "$ref": "#/definitions/type_declaration" + }, + "description": { + "$ref": "#/definitions/description" + }, + "descriptions": { + "$ref": "#/definitions/descriptions" + }, + "proxy": { + "$ref": "#/definitions/anyUri" + }, + "scheme": { + "type": "string", + "enum": [ + "digest" + ] + }, + "qop": { + "type": "string", + "enum": [ + "auth", + "auth-int" + ] + }, + "in": { + "type": "string", + "enum": [ + "header", + "query", + "body", + "cookie", + "auto" + ] + }, + "name": { + "type": "string" + } }, - "events": { - "type": "object", - "additionalProperties": { - "$ref": "#/definitions/event_element" - } + "required": [ + "scheme" + ] + }, + "apiKeySecurityScheme": { + "type": "object", + "properties": { + "@type": { + "$ref": "#/definitions/type_declaration" + }, + "description": { + "$ref": "#/definitions/description" + }, + "descriptions": { + "$ref": "#/definitions/descriptions" + }, + "proxy": { + "$ref": "#/definitions/anyUri" + }, + "scheme": { + "type": "string", + "enum": [ + "apikey" + ] + }, + "in": { + "type": "string", + "enum": [ + "header", + "query", + "body", + "cookie" + ] + }, + "name": { + "type": "string" + } }, - "description": { + "required": [ + "scheme" + ] + }, + "bearerSecurityScheme": { + "type": "object", + "properties": { + "@type": { + "$ref": "#/definitions/type_declaration" + }, + "description": { "$ref": "#/definitions/description" + }, + "descriptions": { + "$ref": "#/definitions/descriptions" + }, + "proxy": { + "$ref": "#/definitions/anyUri" + }, + "scheme": { + "type": "string", + "enum": [ + "bearer" + ] + }, + "authorization": { + "$ref": "#/definitions/anyUri" + }, + "alg": { + "type": "string" + }, + "format": { + "type": "string" + }, + "in": { + "type": "string", + "enum": [ + "header", + "query", + "body", + "cookie", + "auto" + ] + }, + "name": { + "type": "string" + } }, - "descriptions": { + "required": [ + "scheme" + ] + }, + "pskSecurityScheme": { + "type": "object", + "properties": { + "@type": { + "$ref": "#/definitions/type_declaration" + }, + "description": { + "$ref": "#/definitions/description" + }, + "descriptions": { "$ref": "#/definitions/descriptions" + }, + "proxy": { + "$ref": "#/definitions/anyUri" + }, + "scheme": { + "type": "string", + "enum": [ + "psk" + ] + }, + "identity": { + "type": "string" + } }, - "version": { - "type": "object", - "properties": { - "instance": { - "type": "string" + "required": [ + "scheme" + ] + }, + "oAuth2SecurityScheme": { + "type": "object", + "properties": { + "@type": { + "$ref": "#/definitions/type_declaration" + }, + "description": { + "$ref": "#/definitions/description" + }, + "descriptions": { + "$ref": "#/definitions/descriptions" + }, + "proxy": { + "$ref": "#/definitions/anyUri" + }, + "scheme": { + "type": "string", + "enum": [ + "oauth2" + ] + }, + "authorization": { + "$ref": "#/definitions/anyUri" + }, + "token": { + "$ref": "#/definitions/anyUri" + }, + "refresh": { + "$ref": "#/definitions/anyUri" + }, + "scopes": { + "oneOf": [ + { + "type": "array", + "items": { + "type": "string" } - }, - "required": [ - "instance" + }, + { + "type": "string" + } ] + }, + "flow": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "string", + "enum": [ + "code", + "client", + "device" + ] + } + ] + } }, - "links": { - "type": "array", - "items": { - "$ref": "#/definitions/link_element" - } + "required": [ + "scheme" + ] + }, + "securityScheme": { + "oneOf": [ + { + "$ref": "#/definitions/noSecurityScheme" + }, + { + "$ref": "#/definitions/autoSecurityScheme" + }, + { + "$ref": "#/definitions/comboSecurityScheme" + }, + { + "$ref": "#/definitions/basicSecurityScheme" + }, + { + "$ref": "#/definitions/digestSecurityScheme" + }, + { + "$ref": "#/definitions/apiKeySecurityScheme" + }, + { + "$ref": "#/definitions/bearerSecurityScheme" + }, + { + "$ref": "#/definitions/pskSecurityScheme" + }, + { + "$ref": "#/definitions/oAuth2SecurityScheme" + } + ] + } + }, + "type": "object", + "properties": { + "id": { + "type": "string", + "format": "uri" + }, + "title": { + "$ref": "#/definitions/title" + }, + "titles": { + "$ref": "#/definitions/titles" + }, + "properties": { + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/property_element" + } + }, + "actions": { + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/action_element" + } + }, + "events": { + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/event_element" + } + }, + "description": { + "$ref": "#/definitions/description" + }, + "descriptions": { + "$ref": "#/definitions/descriptions" + }, + "version": { + "type": "object", + "properties": { + "instance": { + "type": "string" + } }, - "forms": { + "required": [ + "instance" + ] + }, + "links": { + "type": "array", + "items": { + "oneOf": [ + { + "$ref": "#/definitions/link_element" + }, + { + "$ref": "#/definitions/icon_link_element" + } + ] + } + }, + "forms": { "type": "array", "minItems": 1, "items": { - "$ref": "#/definitions/form_element_root" + "$ref": "#/definitions/form_element_root" } - }, - "base": { + }, + "base": { + "$ref": "#/definitions/anyUri" + }, + "securityDefinitions": { + "type": "object", + "minProperties": 1, + "additionalProperties": { + "$ref": "#/definitions/securityScheme" + } + }, + "schemaDefinitions": { + "type": "object", + "minProperties": 1, + "additionalProperties": { + "$ref": "#/definitions/dataSchema" + } + }, + "support": { + "$ref": "#/definitions/anyUri" + }, + "created": { + "type": "string", + "format": "date-time" + }, + "modified": { + "type": "string", + "format": "date-time" + }, + "profile": { + "oneOf": [ + { "$ref": "#/definitions/anyUri" - }, - "securityDefinitions": { - "type": "object", - "minProperties": 1, - "additionalProperties": { - "$ref": "#/definitions/securityScheme" + }, + { + "type": "array", + "minItems": 1, + "items": { + "$ref": "#/definitions/anyUri" } - }, - "support": { - "$ref": "#/definitions/anyUri" - }, - "created": { + } + ] + }, + "security": { + "oneOf": [ + { "type": "string" - }, - "modified": { - "type": "string" - }, - "security": { + }, + { "type": "array", "minItems": 1, "items": { - "type": "string" + "type": "string" } - }, - "@type": { - "$ref": "#/definitions/type_declaration" - }, - "@context": { - "$ref": "#/definitions/thing-context" + } + ] + }, + "uriVariables": { + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/dataSchema" } + }, + "@type": { + "$ref": "#/definitions/type_declaration" + }, + "@context": { + "$ref": "#/definitions/thing-context" + } }, "required": [ - "title", - "security", - "securityDefinitions", - "@context" + "title", + "security", + "securityDefinitions", + "@context" ], "additionalProperties": true -} + } diff --git a/lib/webofthings/wotutils.js b/lib/webofthings/wotutils.js index 0e95a4c..d9b35b8 100644 --- a/lib/webofthings/wotutils.js +++ b/lib/webofthings/wotutils.js @@ -15,6 +15,8 @@ **/ const Ajv = require('ajv'); +const addFormats = require('ajv-formats'); +const addFormatsDraft2019 = require('ajv-formats-draft2019'); const url = require('url'); const fs = require('fs'); const path = require('path'); @@ -22,7 +24,9 @@ const path = require('path'); function validateTd(td) { const TDSchema = JSON.parse(fs.readFileSync(path.join(__dirname, './td-json-schema-validation.json'), 'utf-8')); - const ajv = new Ajv({allErrors: true}); + const ajv = new Ajv({allErrors: true, strict: false, strictSchema: false}); + addFormats(ajv); + addFormatsDraft2019(ajv); const valid = ajv.validate(TDSchema, td); return { result: valid, errorText: valid?"":ajv.errorsText()}; @@ -237,7 +241,7 @@ function filterFormTd(td) { let forms = td.properties[p].forms; if (forms) { forms = forms.filter((f) => (f.hasOwnProperty("href") && - (f.href.match(/^https?:/) || f.href.match(/^wss?:/) || f.href.match(/^coaps?:/)))); + (f.href.match(/^https?:/) || f.href.match(/^wss?:/) || f.href.match(/^coaps?:/) || f.href.match(/^mqtt:/)))); } td.properties[p].forms = forms; } @@ -245,7 +249,7 @@ function filterFormTd(td) { let forms = td.actions[a].forms; if (forms) { forms = forms.filter((f) => (f.hasOwnProperty("href") && - (f.href.match(/^https?:/) || f.href.match(/^wss?:/) || f.href.match(/^coaps?:/)))); + (f.href.match(/^https?:/) || f.href.match(/^wss?:/) || f.href.match(/^coaps?:/) || f.href.match(/^mqtt:/)))); } td.actions[a].forms = forms; } @@ -253,7 +257,7 @@ function filterFormTd(td) { let forms = td.events[e].forms; if (forms) { forms = forms.filter((f) => (f.hasOwnProperty("href") && - (f.href.match(/^https?:/) || f.href.match(/^wss?:/) || f.href.match(/^coaps?:/)))); + (f.href.match(/^https?:/) || f.href.match(/^wss?:/) || f.href.match(/^coaps?:/) || f.href.match(/^mqtt:/)))); } td.events[e].forms = forms; } diff --git a/package.json b/package.json index 63ecf8a..9eaef0c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "node-red-nodegen", - "version": "0.2.0", + "version": "0.2.4", "description": "Node generator for Node-RED", "homepage": "http://nodered.org", "main": "./lib/nodegen.js", @@ -42,7 +42,9 @@ "web of things" ], "dependencies": { - "ajv": "6.12.6", + "ajv": "8.11.0", + "ajv-formats": "2.1.1", + "ajv-formats-draft2019": "1.6.1", "api-spec-converter": "2.12.0", "axios": "0.26.0", "colors": "1.4.0", diff --git a/samples/qrcode.json b/samples/qrcode.json index fde6ee2..44bdd89 100644 --- a/samples/qrcode.json +++ b/samples/qrcode.json @@ -1 +1,66 @@ -[{"id":"921a4b1d.07bb68","type":"subflow","name":"QRcode","info":"","category":"","in":[{"x":140,"y":120,"wires":[{"id":"e563f491.e443f8"}]}],"out":[{"x":380,"y":120,"wires":[{"id":"e563f491.e443f8","port":0}]}],"env":[],"meta":{"module":"node-red-contrib-qrcode","type":"qrcode","version":"0.1.0","author":"hiroyasu.nishiyama.uq@hitachi.com","desc":"Node-RED node for converting string to QRcode","keywords":"Node-RED, subflow, QRcode","license":"Apache-2.0"},"color":"#87A980","icon":"font-awesome/fa-qrcode"},{"id":"e563f491.e443f8","type":"function","z":"921a4b1d.07bb68","name":"","func":"qrcode.toString(msg.payload, (err,str) => {\n if (err) {\n node.error(err);\n return;\n }\n node.send({payload: str});\n});","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[{"var":"qrcode","module":"qrcode"}],"x":260,"y":120,"wires":[[]]}] \ No newline at end of file +[ + { + "id": "921a4b1d.07bb68", + "type": "subflow", + "name": "QRcode", + "info": "This node generates a QR code from the text data in the `msg.payload`. The QR code in the outputted `msg.payload` is text data (not image data) described by text which consists of black rectangles and white spaces. Therefore, you can use the debug node to see the QR code.\n\n### Inputs\n- `payload` - _string_\n\n Text data which is converted to QR code\n\n### Outputs\n- `payload` - _string_\n\n Text data which contains QR code\n\n**Note**: This node uses the [node-qrcode](https://www.npmjs.com/package/qrcode) module to generate QR codes.", + "category": "", + "in": [ + { + "x": 140, + "y": 120, + "wires": [ + { + "id": "e563f491.e443f8" + } + ] + } + ], + "out": [ + { + "x": 380, + "y": 120, + "wires": [ + { + "id": "e563f491.e443f8", + "port": 0 + } + ] + } + ], + "env": [], + "meta": { + "module": "node-red-contrib-qrcode", + "type": "qrcode", + "version": "0.1.0", + "author": "hiroyasu.nishiyama.uq@hitachi.com", + "desc": "Node-RED node for converting string to QRcode", + "keywords": "Node-RED, subflow, QRcode", + "license": "Apache-2.0" + }, + "color": "#87A980", + "icon": "font-awesome/fa-qrcode" + }, + { + "id": "e563f491.e443f8", + "type": "function", + "z": "921a4b1d.07bb68", + "name": "", + "func": "qrcode.toString(msg.payload, (err,str) => {\n if (err) {\n node.error(err);\n return;\n }\n node.send({payload: str});\n});", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [ + { + "var": "qrcode", + "module": "qrcode" + } + ], + "x": 260, + "y": 120, + "wires": [ + [] + ] + } +] \ No newline at end of file diff --git a/templates/function/package.json.mustache b/templates/function/package.json.mustache index 095a498..bf1f3cb 100644 --- a/templates/function/package.json.mustache +++ b/templates/function/package.json.mustache @@ -6,7 +6,11 @@ "scripts": { "test": "mocha \"test/**/*_spec.js\" --timeout 3000" }, + "engines": { + "node": ">=12.0.0" + }, "node-red": { + "version": ">=1.3.7", "nodes": { "{{&nodeName}}": "node.js" } diff --git a/templates/subflow/README.md.mustache b/templates/subflow/README.md.mustache index 0ee221f..a3b2239 100644 --- a/templates/subflow/README.md.mustache +++ b/templates/subflow/README.md.mustache @@ -3,10 +3,14 @@ {{&description}} -Install -------- +## Install Run the following command in your Node-RED user directory - typically `~/.node-red` npm install {{&projectName}} +{{#nodeRead}} +## Information + +{{&nodeRead}} +{{/nodeRead}} diff --git a/templates/subflow/package.json.mustache b/templates/subflow/package.json.mustache index 4c86ef6..a492866 100644 --- a/templates/subflow/package.json.mustache +++ b/templates/subflow/package.json.mustache @@ -1,18 +1,25 @@ { - "name": "{{&projectName}}", - "version": "{{&projectVersion}}", - "description": "Node-RED node for {{&nodeName}}", - "node-red": { - "nodes": { - "{{&nodeName}}": "subflow.js" - } - }, - "keywords": [ - {{#keywords}} - "{{name}}"{{^last}}, {{/last}} - {{/keywords}} - ], - "dependencies": { - "crypto-js": "4.1.1" + "name": "{{&projectName}}", + "version": "{{&projectVersion}}", + "description": "Node-RED node for {{&nodeName}}", + "keywords": [ + {{#keywords}} + "{{name}}"{{^last}}, {{/last}} + {{/keywords}} + ], + "engines": { + "node": ">=12.0.0" + }, + "node-red": { + "version": ">=1.3.7", + "nodes": { + "{{&nodeName}}": "subflow.js" } + }, + {{#encoding}} + "dependencies": { + "crypto-js": "4.1.1" + }, + {{/encoding}} + "license": "{{&licenseName}}" } diff --git a/templates/subflow/subflow.js.mustache b/templates/subflow/subflow.js.mustache index 669f264..4a6dd58 100644 --- a/templates/subflow/subflow.js.mustache +++ b/templates/subflow/subflow.js.mustache @@ -1,11 +1,11 @@ -module.exports = function(RED) { +module.exports = function (RED) { const fs = require('fs'); const path = require("path"); - const crypt = require("crypto-js"); - const file = path.join(__dirname, "subflow.json"); const text = fs.readFileSync(file); const flow = JSON.parse(text); + {{#encoding}} + const crypt = require("crypto-js"); function getDecoder(encoding) { var settings = RED.settings; @@ -63,4 +63,8 @@ module.exports = function(RED) { } RED.nodes.registerSubflow(convFlow(flow)); + {{/encoding}} + {{^encoding}} + RED.nodes.registerSubflow(flow); + {{/encoding}} } diff --git a/templates/swagger/package.json.mustache b/templates/swagger/package.json.mustache index 67049b0..5d7b4f3 100644 --- a/templates/swagger/package.json.mustache +++ b/templates/swagger/package.json.mustache @@ -6,7 +6,11 @@ "scripts": { "test": "mocha \"test/**/*_spec.js\" --timeout 3000" }, + "engines": { + "node": ">=12.0.0" + }, "node-red": { + "version": ">=1.3.7", "nodes": { "{{&nodeName}}": "node.js" } @@ -19,7 +23,7 @@ "dependencies": { "q": "1.5.1", "request": "2.88.2", - "file-type": "17.1.1" + "file-type": "16.5.3" }, "devDependencies": { "mocha": "9.2.1", diff --git a/templates/webofthings/node.js.mustache b/templates/webofthings/node.js.mustache index a388c71..66d8e78 100644 --- a/templates/webofthings/node.js.mustache +++ b/templates/webofthings/node.js.mustache @@ -7,6 +7,7 @@ module.exports = function (RED) { const urltemplate = require('url-template'); const Ajv = require('ajv').default; const nodecoap = require('coap'); + const mqtt = require('mqtt'); function extractTemplate(href, context={}) { return urltemplate.parse(href).expand(context); @@ -15,9 +16,9 @@ module.exports = function (RED) { function getResType(form) { if (form) { if (form.response && form.response.contentType) { - return form.response.contentType; + return form.response.contentType.split(';')[0]; } else if (form.contentType) { - return form.contentType; + return form.contentType.split(';')[0]; } } return "application/json"; @@ -25,7 +26,7 @@ module.exports = function (RED) { function isBinaryType(contentType) { let result; - switch (contentType) { + switch (contentType.split(';')[0]) { case "image/jpeg": case "application/octet-stream": result = true; @@ -37,6 +38,101 @@ module.exports = function (RED) { return result; } + function bindingMqtt(node, send, done, form, options={}) { // options.interaction, options.reqbody, options.msg + let jobqueue = []; // [{topic, op: "publish"/"subscribe"/"subscribeonce", msg, callback(recvmsg)}] + const mqttcli = options.mqttcli; + mqttcli.on('connect', () => { + node.trace('connected to broker'); + flushJobQueue(); + }); + function flushJobQueue() { + jobqueue.forEach((e) => { + processMqttOp(e); + }); + jobqueue = []; + } + function processMqttOp({ topic, op, msg, callback }) { + switch (op) { + case "publish": + mqttcli.publish(topic, msg); + break; + case "subscribe": + mqttcli.on('message', (t, m, _) => { + if (t !== topic) { + node.trace(`wrong topic: ${t}`); + } + callback(m); + }) + mqttcli.subscribe(topic); + break; + case "subscribeonce": + mqttcli.once('message', (t, m, _) => { + if (t !== topic) { + node.trace(`wrong topic: ${t}`); + } + mqttcli.unsubscribe(topic); + callback(m); + }) + mqttcli.subscribe(topic); + default: + // unknown op + break; + } + } + function mkmessage(form, origmessage) { + let retval; + if (getResType(form) == "application/json") { + try { + retval = {payload: JSON.parse(origmessage.toString())}; + } catch (_) { + retval = {payload: origmessage}; + } + } else { + retval = {payload: origmessage}; + } + return retval; + } + + const msg = options.msg || {}; + const topic = (new URL(extractTemplate(form.href, options.urivars))).pathname.slice(1); + node.trace(`interaction: ${options.interaction}`); + switch (options.interaction) { + case "property-read": + node.trace(`MQTT subscribe (property-read)`); + jobqueue.push({topic, op: "subscribeonce", callback: (m) => { + send(mkmessage(form, m)); + done(); + }}); + break; + case "property-write": + node.trace(`MQTT publish (property-write)`); + jobqueue.push({topic, op: "publish", msg: JSON.stringify(options.reqbody), callback: (_) => {done()}}); + break; + case "property-observe": + node.trace(`MQTT subscribe (property-observe)`); + jobqueue.push({topic, op: "subscribe", callback: (m) => { + node.send(mkmessage(form, m)); + }}); + break; + case "action": + node.trace(`MQTT publish (action)`); + jobqueue.push({topic, op: "publish", msg: JSON.stringify(options.reqbody), callback: (_) => {done()}}); + break; + case "event": + node.trace(`MQTT subscribe (event)`); + jobqueue.push({topic, op: "subscribe", callback: (m) => { + node.send(mkmessage(form, m)); + }}); + break; + default: + node.trace(`Unknown interaction`); + break; + } + if (mqttcli.connected) { + flushJobQueue(); + } + } + function coapMethodCodeToName(methodCode) { switch (methodCode) { case '1': @@ -451,6 +547,26 @@ module.exports = function (RED) { return auth; } + function setupMqttConnection(form, node) { + const u = new URL(form.href); + const mqtturi = `${u.protocol}//${u.host}`; + const cli = mqtt.connect(mqtturi); + cli.on('connect', ()=> { + node.status({fill:"green", shape:"dot", text:"connected"}); + }); + cli.on('reconnect', () => { + node.status({fill:"yellow", shape:"dot", text:"reconnecting..."}); + }); + cli.on('offline', () => { + node.status({fill:"red", shape:"ring", text:"disconnected"}); + }); + cli.on('error', () => { + node.status({fill:"red", shape:"dot", text:"error"}); + }); + return cli; + } + + function Node(config) { RED.nodes.createNode(this, config); const node = this; @@ -465,16 +581,20 @@ module.exports = function (RED) { const username = node.credentials.username; const password = node.credentials.password; const token = node.credentials.token; + let mqttcli = null; node.td = {{{tdstr}}}; const normTd = {{{normtd}}}; if (node.interactiontype === "property") { + const prop = normTd.properties[node.propname]; + const form = prop.forms[node.formindex];// formSelection("property-read", prop.forms); + const auth = makeauth(normTd, form, username, password, token); + if (form.href.match(/^mqtt:/)) { + mqttcli = setupMqttConnection(form, node); + } if (node.proptype === "read") { node.on("input", (msg, send, done) => { send = send || function() { node.send.apply(node,arguments) }; - const prop = normTd.properties[node.propname]; - const form = prop.forms[node.formindex];// formSelection("property-read", prop.forms); - const auth = makeauth(normTd, form, username, password, token); const urivars = prop.hasOwnProperty("uriVariables") ? msg.payload : {}; if (prop.uriVariables) { const ajv = new Ajv({allErrors: true, strict: false, validateFormats: false}); @@ -487,14 +607,13 @@ module.exports = function (RED) { bindingHttp(node, send, done, form, {interaction:"property-read", auth, msg, urivars, outschema: prop}); } else if (form.href.match(/^coaps?:/)) { bindingCoap(node, send, done, form, {interaction:"property-read", auth, msg, urivars, outschema: prop}); + } else if (form.href.match(/^mqtt:/)) { + bindingMqtt(node, send, done, form, {interaction:"property-read", auth, msg, urivars, outschema: prop, mqttcli}); } }); } else if (node.proptype === "write") { node.on("input", (msg, send, done) => { send = send || function() { node.send.apply(node,arguments) }; - const prop = normTd.properties[node.propname]; - const form = prop.forms[node.formindex];// formSelection("property-write", prop.forms); - const auth = makeauth(normTd, form, username, password, token); const ajv = new Ajv({allErrors: true, strict: false, validateFormats: false}); if (!ajv.validate(prop, msg.payload)) { node.warn(`input schema validation error: ${ajv.errorsText()}`, msg); @@ -504,34 +623,31 @@ module.exports = function (RED) { bindingHttp(node, send, done, form, {interaction: "property-write", auth, msg, reqbody: msg.payload}); } else if (form.href.match(/^coaps?:/)) { bindingCoap(node, send, done, form, {interaction: "property-write", auth, msg, reqbody: msg.payload}); + } else if (form.href.match(/^mqtt:/)) { + bindingMqtt(node, send, done, form, {interaction: "property-write", auth, msg, reqbody: msg.payload, mqttcli}); } }); } else if (node.proptype === "observe") { - const prop = normTd.properties[node.propname]; - const form = prop.forms[node.formindex];// formSelection("property-observe", prop.forms); - const auth = makeauth(normTd, form, username, password, token); - const urivars = prop.hasOwnProperty("uriVariables") ? msg.payload : {}; - if (prop.uriVariables) { - const ajv = new Ajv({allErrors: true, strict: false, validateFormats: false}); - const uvschema = { type: "object", properties: prop.uriVariables }; - if (!ajv.validate(uvschema, urivars)) { - node.warn(`input schema validation error: ${ajv.errorsText()}`, msg); - } - } + const urivars = {}; if (form.href.match(/^wss?:/)) { // websocket bindingWebSocket(node, form, {auth, urivars, outschema: prop}); } else if (form.href.match(/^coaps?:/)) { // CoAP observe bindingCoapObserve(node, form, {auth, urivars, outschema: prop}); - } else { // long polling + } else if (form.href.match(/^https?:/)) { // long polling bindingLongPoll(node, form, {auth, urivars, outschema: prop}); + } else if (form.href.match(/^mqtt:/)) { + bindingMqtt(node, undefined, undefined, form, {interaction: "property-observe", auth, urivars, outschema: prop, mqttcli}); } } } else if (node.interactiontype === "action") { + const act = normTd.actions[node.actname]; + const form = act.forms[node.formindex];// formSelection("action", act.forms); + const auth = makeauth(normTd, form, username, password, token); + if (form.href.match(/^mqtt:/)) { + mqttcli = setupMqttConnection(form, node); + } node.on("input", (msg, send, done) => { send = send || function() { node.send.apply(node,arguments) }; - const act = normTd.actions[node.actname]; - const form = act.forms[node.formindex];// formSelection("action", act.forms); - const auth = makeauth(normTd, form, username, password, token); const urivars = act.hasOwnProperty("uriVariables") ? msg.payload : {}; if (act.uriVariables) { const ajv = new Ajv({allErrors: true, strict: false, validateFormats: false}); @@ -550,28 +666,33 @@ module.exports = function (RED) { bindingHttp(node, send, done, form, {interaction: "action", auth, msg, urivars, reqbody:msg.payload}); } else if (form.href.match(/^coaps?/)) { bindingCoap(node, send, done, form, {interaction: "action", auth, msg, urivars, reqbody:msg.payload}); + } else if (form.href.match(/^mqtt:/)) { + bindingMqtt(node, send, done, form, {interaction: "action", auth, msg, urivars, reqbody:msg.payload, mqttcli}); } }); } else if (node.interactiontype === "event") { const ev = normTd.events[node.evname]; const form = ev.forms[node.formindex];// formSelection("event", ev.forms); const auth = makeauth(normTd, form, username, password, token); - const urivars = ev.hasOwnProperty("uriVariables") ? msg.payload : {}; - if (ev.uriVariables) { - const ajv = new Ajv({allErrors: true, strict: false, validateFormats: false}); - const uvschema = { type: "object", properties: ev.uriVariables }; - if (!ajv.validate(uvschema, urivars)) { - node.warn(`input schema validation error: ${ajv.errorsText()}`, msg); - } + if (form.href.match(/^mqtt:/)) { + mqttcli = setupMqttConnection(form, node); } + const urivars = {}; if (form.href.match(/^wss?:/)) { // websocket bindingWebSocket(node, form, {auth, urivars}); } else if (form.href.match(/^coaps?:/)) { bindingCoapObserve(node, form, {auth, urivars}); - } else { // long polling + } else if (form.href.match(/^https?:/)) { // long polling bindingLongPoll(node, form, {auth, urivars}); - } + } else if (form.href.match(/^mqtt:/)) { + bindingMqtt(node, undefined, undefined, form, {interaction: "event", auth, urivars, mqttcli }); + } } + node.on('close', () => { + if (mqttcli) { + mqttcli.end(true); + } + }); } RED.nodes.registerType("{{&nodeName}}", Node, { diff --git a/templates/webofthings/package.json.mustache b/templates/webofthings/package.json.mustache index a134e96..3d21096 100644 --- a/templates/webofthings/package.json.mustache +++ b/templates/webofthings/package.json.mustache @@ -1,37 +1,42 @@ { - "name": "{{&projectName}}", - "version": "{{&projectVersion}}", - "description": "Node-RED node for {{&nodeName}}", - "main": "node.js", - "scripts": { - "test": "mocha \"test/**/*_spec.js\"" - }, - "node-red": { - "nodes": { - "{{&nodeName}}": "node.js" - } - }, - "keywords": [ - {{#keywords}} - "{{name}}"{{^last}}, {{/last}} - {{/keywords}} - ], - "dependencies": { - "https-proxy-agent": "5.0.0", - "request": "2.88.2", - "ws": "8.5.0", - "url-template": "2.0.8", - "ajv": "8.10.0", - "coap": "1.0.3" - }, - "devDependencies": { - "node-red": "2.2.2", - "node-red-node-test-helper": "0.2.7" - }, - "license": "{{&licenseName}}", - "wot": { - {{#wotmeta}} - "{{name}}": "{{value}}"{{^last}}, {{/last}} - {{/wotmeta}} + "name": "{{&projectName}}", + "version": "{{&projectVersion}}", + "description": "Node-RED node for {{&nodeName}}", + "main": "node.js", + "scripts": { + "test": "mocha \"test/**/*_spec.js\"" + }, + "engines": { + "node": ">=12.0.0" + }, + "node-red": { + "version": ">=1.3.7", + "nodes": { + "{{&nodeName}}": "node.js" } + }, + "keywords": [ + {{#keywords}} + "{{name}}"{{^last}}, {{/last}} + {{/keywords}} + ], + "dependencies": { + "https-proxy-agent": "5.0.0", + "request": "2.88.2", + "ws": "8.5.0", + "url-template": "2.0.8", + "ajv": "8.11.0", + "coap": "1.0.3", + "mqtt": "4.3.7" + }, + "devDependencies": { + "node-red": "2.2.2", + "node-red-node-test-helper": "0.2.7" + }, + "license": "{{&licenseName}}", + "wot": { + {{#wotmeta}} + "{{name}}": "{{value}}"{{^last}}, {{/last}} + {{/wotmeta}} + } }