From 905f89b0f55d0d059a2140dca19b1fe932185ada Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nathana=C3=ABl=20L=C3=A9caud=C3=A9?= Date: Sat, 30 Jun 2018 16:19:39 -0700 Subject: [PATCH] JSON node: finalize JSON Schema validation --- nodes/core/locales/en-US/messages.json | 4 +- nodes/core/parsers/70-JSON.html | 5 ++ nodes/core/parsers/70-JSON.js | 31 ++++------ test/nodes/core/parsers/70-JSON_spec.js | 81 +++++++++++++++++++++++++ 4 files changed, 102 insertions(+), 19 deletions(-) diff --git a/nodes/core/locales/en-US/messages.json b/nodes/core/locales/en-US/messages.json index 74ec0bcd4..c3586d412 100644 --- a/nodes/core/locales/en-US/messages.json +++ b/nodes/core/locales/en-US/messages.json @@ -697,7 +697,9 @@ "errors": { "dropped-object": "Ignored non-object payload", "dropped": "Ignored unsupported payload type", - "dropped-error": "Failed to convert payload" + "dropped-error": "Failed to convert payload", + "schema-error": "JSON Schema error", + "schema-error-compile": "JSON Schema error: failed to compile schema" }, "label": { "o2j": "Object to JSON options", diff --git a/nodes/core/parsers/70-JSON.html b/nodes/core/parsers/70-JSON.html index 565bae9a0..a440e5d45 100644 --- a/nodes/core/parsers/70-JSON.html +++ b/nodes/core/parsers/70-JSON.html @@ -31,6 +31,8 @@
payloadobject | string
A JavaScript object or JSON string.
+
schemaobject
+
An optional JSON Schema object to validate the payload against.

Outputs

@@ -41,6 +43,9 @@
  • If the input is a JavaScript object it creates a JSON string. The string can optionally be well-formatted.
  • +
    schemaErrorarray
    +
    If JSON schema validation fails, the catch node will have a schemaError property + containing an array of errors.

    Details

    By default, the node operates on msg.payload, but can be configured diff --git a/nodes/core/parsers/70-JSON.js b/nodes/core/parsers/70-JSON.js index ce547fe10..eb0bec63c 100644 --- a/nodes/core/parsers/70-JSON.js +++ b/nodes/core/parsers/70-JSON.js @@ -32,24 +32,19 @@ module.exports = function(RED) { this.on("input", function(msg) { var validate = false; if (msg.schema) { - if (typeof msg.schema === "object") { - // If input schema is different, re-compile it - if (JSON.stringify(this.schema) != JSON.stringify(msg.schema)) { - node.warn('Schema different, compiling'); - try { - this.compiledSchema = ajv.compile(msg.schema); - this.schema = msg.schema; - } catch(e) { - this.schema = null; - this.compiledSchema = null; - node.error("JSON Schema error: failed to compile schema", msg); - return; - } + // If input schema is different, re-compile it + if (JSON.stringify(this.schema) != JSON.stringify(msg.schema)) { + try { + this.compiledSchema = ajv.compile(msg.schema); + this.schema = msg.schema; + } catch(e) { + this.schema = null; + this.compiledSchema = null; + node.error(RED._("json.errors.schema-error-compile"), msg); + return; } - validate = true; - } else { - node.warn("Schema present but not an object, ignoring schema"); } + validate = true; } var value = RED.util.getMessageProperty(msg,node.property); if (value !== undefined) { @@ -62,7 +57,7 @@ module.exports = function(RED) { node.send(msg); } else { msg.schemaError = this.compiledSchema.errors; - node.error(`JSON Schema error: ${ajv.errorsText(this.compiledSchema.errors)}`, msg); + node.error(`${RED._("json.errors.schema-error")}: ${ajv.errorsText(this.compiledSchema.errors)}`, msg); } } else { node.send(msg); @@ -83,7 +78,7 @@ module.exports = function(RED) { node.send(msg); } else { msg.schemaError = this.compiledSchema.errors; - node.error(`JSON Schema error: ${ajv.errorsText(this.compiledSchema.errors)}`, msg); + node.error(`${RED._("json.errors.schema-error")}: ${ajv.errorsText(this.compiledSchema.errors)}`, msg); } } else { RED.util.setMessageProperty(msg,node.property,JSON.stringify(value,null,node.indent)); diff --git a/test/nodes/core/parsers/70-JSON_spec.js b/test/nodes/core/parsers/70-JSON_spec.js index 28fffde85..ba913b1e7 100644 --- a/test/nodes/core/parsers/70-JSON_spec.js +++ b/test/nodes/core/parsers/70-JSON_spec.js @@ -247,4 +247,85 @@ describe('JSON node', function() { }); }); }); + + it('should pass an object if provided a valid JSON string and schema', function(done) { + var flow = [{id:"jn1",type:"json",wires:[["jn2"]]}, + {id:"jn2", type:"helper"}]; + helper.load(jsonNode, flow, function() { + var jn1 = helper.getNode("jn1"); + var jn2 = helper.getNode("jn2"); + jn2.on("input", function(msg) { + should.equal(msg.payload.number, 3); + should.equal(msg.payload.string, "allo"); + done(); + }); + var jsonString = '{"number": 3, "string": "allo"}'; + var schema = {title: "testSchema", type: "object", properties: {number: {type: "number"}, string: {type: "string" }}}; + jn1.receive({payload:jsonString, schema:schema}); + }); + }); + + it('should pass a string if provided a valid object and schema', function(done) { + var flow = [{id:"jn1",type:"json",wires:[["jn2"]]}, + {id:"jn2", type:"helper"}]; + helper.load(jsonNode, flow, function() { + var jn1 = helper.getNode("jn1"); + var jn2 = helper.getNode("jn2"); + jn2.on("input", function(msg) { + should.equal(msg.payload, '{"number":3,"string":"allo"}'); + done(); + }); + var obj = {"number": 3, "string": "allo"}; + var schema = {title: "testSchema", type: "object", properties: {number: {type: "number"}, string: {type: "string" }}}; + jn1.receive({payload:obj, schema:schema}); + }); + }); + + it('should log an error if passed an invalid object and valid schema', function(done) { + var flow = [{id:"jn1",type:"json",wires:[["jn2"]]}, + {id:"jn2", type:"helper"}]; + helper.load(jsonNode, flow, function() { + try { + var jn1 = helper.getNode("jn1"); + var jn2 = helper.getNode("jn2"); + var schema = {title: "testSchema", type: "object", properties: {number: {type: "number"}, string: {type: "string" }}}; + var obj = {"number": "foo", "string": 3}; + jn1.receive({payload:obj, schema:schema}); + var logEvents = helper.log().args.filter(function(evt) { + return evt[0].type == "json"; + }); + logEvents.should.have.length(1); + logEvents[0][0].should.have.a.property('msg'); + logEvents[0][0].msg.should.equal("json.errors.schema-error: data.number should be number, data.string should be string"); + logEvents[0][0].should.have.a.property('level',helper.log().ERROR); + done(); + } catch(err) { + done(err); + } + }); + }); + + it('should log an error if passed a valid object and invalid schema', function(done) { + var flow = [{id:"jn1",type:"json",wires:[["jn2"]]}, + {id:"jn2", type:"helper"}]; + helper.load(jsonNode, flow, function() { + try { + var jn1 = helper.getNode("jn1"); + var jn2 = helper.getNode("jn2"); + var schema = "garbage"; + var obj = {"number": "foo", "string": 3}; + jn1.receive({payload:obj, schema:schema}); + var logEvents = helper.log().args.filter(function(evt) { + return evt[0].type == "json"; + }); + logEvents.should.have.length(1); + logEvents[0][0].should.have.a.property('msg'); + logEvents[0][0].msg.should.equal("json.errors.schema-error-compile"); + logEvents[0][0].should.have.a.property('level',helper.log().ERROR); + done(); + } catch(err) { + done(err); + } + }); + }); });