From 4e0a6cba08acddeaacc2987dea040115c9a42938 Mon Sep 17 00:00:00 2001 From: Jerome Simeon Date: Fri, 13 Dec 2019 09:34:11 -0500 Subject: [PATCH] fix(validation) Serializing a not finite Double throws a validation error #158 Signed-off-by: Jerome Simeon --- .../lib/serializer/resourcevalidator.js | 12 ++++- packages/concerto-core/test/serializer.js | 50 +++++++++++++++++-- .../test/serializer/jsongenerator.js | 2 +- .../test/serializer/resourcevalidator.js | 17 +++++++ 4 files changed, 74 insertions(+), 7 deletions(-) diff --git a/packages/concerto-core/lib/serializer/resourcevalidator.js b/packages/concerto-core/lib/serializer/resourcevalidator.js index 85cff11fe6..5f8f925894 100644 --- a/packages/concerto-core/lib/serializer/resourcevalidator.js +++ b/packages/concerto-core/lib/serializer/resourcevalidator.js @@ -299,12 +299,16 @@ class ResourceValidator { invalid = true; } break; - case 'Double': case 'Long': case 'Integer': + case 'Double': { if(dataType !== 'number') { invalid = true; } + if (!isFinite(obj)) { + invalid = true; + } + } break; case 'Boolean': if(dataType !== 'boolean') { @@ -421,7 +425,11 @@ class ResourceValidator { else { if(value) { try { - value = JSON.stringify(value); + if (typeof value === 'number' && !isFinite(value)) { + value = value.toString(); + } else { + value = JSON.stringify(value); + } } catch(err) { value = value.toString(); diff --git a/packages/concerto-core/test/serializer.js b/packages/concerto-core/test/serializer.js index 5fdf5685f0..6368af3db2 100644 --- a/packages/concerto-core/test/serializer.js +++ b/packages/concerto-core/test/serializer.js @@ -45,6 +45,7 @@ describe('Serializer', () => { o String assetId --> SampleParticipant owner o String stringValue + o Double doubleValue } participant SampleParticipant identified by participantId { @@ -61,6 +62,7 @@ describe('Serializer', () => { concept Address { o String city o String country + o Double elevation } event SampleEvent{ @@ -113,6 +115,7 @@ describe('Serializer', () => { let resource = factory.newResource('org.acme.sample', 'SampleAsset', '1'); resource.owner = factory.newRelationship('org.acme.sample', 'SampleParticipant', 'alice@email.com'); resource.stringValue = 'the value'; + resource.doubleValue = 3.14; let json = serializer.toJSON(resource, { validate: true }); @@ -120,10 +123,41 @@ describe('Serializer', () => { $class: 'org.acme.sample.SampleAsset', assetId: '1', owner: 'resource:org.acme.sample.SampleParticipant#alice@email.com', - stringValue: 'the value' + stringValue: 'the value', + doubleValue: 3.14 }); }); + it('should throw validation errors during JSON object generation if Double is NaN', () => { + let resource = factory.newResource('org.acme.sample', 'SampleAsset', '1'); + resource.owner = factory.newRelationship('org.acme.sample', 'SampleParticipant', 'alice@email.com'); + resource.stringValue = 'the value'; + resource.doubleValue = NaN; + (() => { + serializer.toJSON(resource); + }).should.throw(/Model violation in instance org.acme.sample.SampleAsset#1 field doubleValue has value NaN/); + }); + + it('should throw validation errors during JSON object generation if Double is Infinity', () => { + let resource = factory.newResource('org.acme.sample', 'SampleAsset', '1'); + resource.owner = factory.newRelationship('org.acme.sample', 'SampleParticipant', 'alice@email.com'); + resource.stringValue = 'the value'; + resource.doubleValue = Infinity; + (() => { + serializer.toJSON(resource); + }).should.throw(/Model violation in instance org.acme.sample.SampleAsset#1 field doubleValue has value Infinity/); + }); + + it('should throw validation errors during JSON object generation if Double is -Infinity', () => { + let resource = factory.newResource('org.acme.sample', 'SampleAsset', '1'); + resource.owner = factory.newRelationship('org.acme.sample', 'SampleParticipant', 'alice@email.com'); + resource.stringValue = 'the value'; + resource.doubleValue = -Infinity; + (() => { + serializer.toJSON(resource); + }).should.throw(/Model violation in instance org.acme.sample.SampleAsset#1 field doubleValue has value -Infinity/); + }); + it('should throw validation errors during JSON object generation if the validate flag is not specified and errors are present', () => { let resource = factory.newResource('org.acme.sample', 'SampleAsset', '1'); (() => { @@ -175,10 +209,12 @@ describe('Serializer', () => { let address = factory.newConcept('org.acme.sample', 'Address'); address.city = 'Winchester'; address.country = 'UK'; + address.elevation = 3.14; const json = serializer.toJSON(address); json.should.deep.equal({ $class: 'org.acme.sample.Address', country: 'UK', + elevation: 3.14, city: 'Winchester' }); }); @@ -187,6 +223,7 @@ describe('Serializer', () => { let resource = factory.newResource('org.acme.sample', 'SampleAsset', '1'); resource.owner = factory.newRelationship('org.acme.sample', 'SampleParticipant', 'alice@email.com'); resource.stringValue = ''; + resource.doubleValue = 3.14; let json = serializer.toJSON(resource, { validate: true }); @@ -194,7 +231,8 @@ describe('Serializer', () => { $class: 'org.acme.sample.SampleAsset', assetId: '1', owner: 'resource:org.acme.sample.SampleParticipant#alice@email.com', - stringValue: '' + stringValue: '', + doubleValue: 3.14 }); }); }); @@ -222,13 +260,15 @@ describe('Serializer', () => { $class: 'org.acme.sample.SampleAsset', assetId: '1', owner: 'resource:org.acme.sample.SampleParticipant#alice@email.com', - stringValue: 'the value' + stringValue: 'the value', + doubleValue: 3.14 }; let resource = serializer.fromJSON(json); resource.should.be.an.instanceOf(Resource); resource.assetId.should.equal('1'); resource.owner.should.be.an.instanceOf(Relationship); resource.stringValue.should.equal('the value'); + resource.doubleValue.should.equal(3.14); }); it('should deserialize a valid transaction', () => { @@ -263,12 +303,14 @@ describe('Serializer', () => { let json = { $class: 'org.acme.sample.Address', city: 'Winchester', - country: 'UK' + country: 'UK', + elevation: 3.14 }; let resource = serializer.fromJSON(json); resource.should.be.an.instanceOf(Concept); resource.city.should.equal('Winchester'); resource.country.should.equal('UK'); + resource.elevation.should.equal(3.14); }); it('should throw validation errors if the validate flag is not specified', () => { diff --git a/packages/concerto-core/test/serializer/jsongenerator.js b/packages/concerto-core/test/serializer/jsongenerator.js index 84b8e569de..9630084c90 100644 --- a/packages/concerto-core/test/serializer/jsongenerator.js +++ b/packages/concerto-core/test/serializer/jsongenerator.js @@ -135,7 +135,7 @@ describe('JSONGenerator', () => { ergoJsonGenerator.convertToJSON({ getType: () => { return 'Integer'; } }, 123456).nat.should.equal(123456); }); - it('should pass through a double object', () => { + it('should pass through a finite double object', () => { jsonGenerator.convertToJSON({ getType: () => { return 'Double'; } }, 3.142).should.equal(3.142); }); diff --git a/packages/concerto-core/test/serializer/resourcevalidator.js b/packages/concerto-core/test/serializer/resourcevalidator.js index 1a5464fc75..6752f0b29c 100644 --- a/packages/concerto-core/test/serializer/resourcevalidator.js +++ b/packages/concerto-core/test/serializer/resourcevalidator.js @@ -74,6 +74,7 @@ describe('ResourceValidator', function () { import org.acme.l1.Person asset Vehicle extends Base { o Integer numberOfWheels + o Double milage } participant PrivateOwner identified by employeeId extends Person { o String employeeId @@ -414,6 +415,7 @@ describe('ResourceValidator', function () { vehicle.$identifier = ''; // empty the identifier vehicle.model = 'Ford'; vehicle.numberOfWheels = 4; + vehicle.milage = 3.14; const typedStack = new TypedStack(vehicle); const assetDeclaration = modelManager.getType('org.acme.l3.Car'); const parameters = { stack : typedStack, 'modelManager' : modelManager, rootResourceIdentifier : 'ABC' }; @@ -423,6 +425,21 @@ describe('ResourceValidator', function () { }).should.throw(/has an empty identifier/); }); + it('should reject a Double which is not finite', function () { + const vehicle = factory.newResource('org.acme.l3', 'Car', 'foo'); + vehicle.$identifier = '42'; + vehicle.model = 'Ford'; + vehicle.numberOfWheels = 4; + vehicle.milage = NaN; // NaN + const typedStack = new TypedStack(vehicle); + const assetDeclaration = modelManager.getType('org.acme.l3.Car'); + const parameters = { stack : typedStack, 'modelManager' : modelManager, rootResourceIdentifier : 'ABC' }; + + (() => { + assetDeclaration.accept(resourceValidator,parameters); + }).should.throw(/Model violation in instance org.acme.l3.Car#42 field milage has value NaN/); + }); + it('should report undeclared field if not identifiable', () => { const data = factory.newConcept('org.acme.l1', 'Data'); data.name = 'name';