Skip to content

Commit

Permalink
feature(validation) add Ergo-style validation
Browse files Browse the repository at this point in the history
Signed-off-by: Jerome Simeon <[email protected]>
  • Loading branch information
jeromesimeon committed Oct 6, 2019
1 parent 0a6176d commit ad0ce5e
Show file tree
Hide file tree
Showing 8 changed files with 255 additions and 15 deletions.
2 changes: 1 addition & 1 deletion packages/concerto-core/api.txt
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,6 @@ class ModelManager {
class Serializer {
+ void constructor(Factory,ModelManager)
+ void setDefaultOptions(Object)
+ Object toJSON(Resource,Object,boolean,boolean,boolean,boolean) throws Error
+ Object toJSON(Resource,Object,boolean,boolean,boolean,boolean,boolean) throws Error
+ Resource fromJSON(Object,Object,boolean,boolean)
}
3 changes: 3 additions & 0 deletions packages/concerto-core/changelog.txt
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@
# Note that the latest public API is documented using JSDocs and is available in api.txt.
#

Version 0.80.3 {6f5a9ab45943cb76732c14b11f47d044} 2019-08-24
- Add Ergo option to serializer

Version 0.80.1 {297c88d29ce911ec6efc0f28ceeeb660} 2019-08-24
- Adds getModels and writeModelsToFileSystem functions to ModelManager
- Fixes API generation for hasSymbol function
Expand Down
11 changes: 8 additions & 3 deletions packages/concerto-core/lib/serializer.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,8 @@ const TransactionDeclaration = require('./introspect/transactiondeclaration');
const TypedStack = require('./serializer/typedstack');

const baseDefaultOptions = {
validate: true
validate: true,
ergo: false
};

/**
Expand Down Expand Up @@ -81,6 +82,8 @@ class Serializer {
* @param {boolean} [options.deduplicateResources] - Generate $id for resources and
* if a resources appears multiple times in the object graph only the first instance is
* serialized in full, subsequent instances are replaced with a reference to the $id
* @param {boolean} [options.convertResourcesToId] - Convert resources that
* are specified for relationship fields into their id, false by default.
* @return {Object} - The Javascript Object that represents the resource
* @throws {Error} - throws an exception if resource is not an instance of
* Resource or fails validation.
Expand Down Expand Up @@ -108,7 +111,9 @@ class Serializer {
const generator = new JSONGenerator(
options.convertResourcesToRelationships === true,
options.permitResourcesForRelationships === true,
options.deduplicateResources === true
options.deduplicateResources === true,
options.convertResourcesToId === true,
options.ergo === true
);

parameters.stack.clear();
Expand Down Expand Up @@ -173,7 +178,7 @@ class Serializer {
parameters.resourceStack = new TypedStack(resource);
parameters.modelManager = this.modelManager;
parameters.factory = this.factory;
const populator = new JSONPopulator(options.acceptResourcesForRelationships === true);
const populator = new JSONPopulator(options.acceptResourcesForRelationships === true, options.ergo === true);
classDeclaration.accept(populator, parameters);

// validate the resource against the model
Expand Down
48 changes: 43 additions & 5 deletions packages/concerto-core/lib/serializer/jsongenerator.js
Original file line number Diff line number Diff line change
Expand Up @@ -45,11 +45,16 @@ class JSONGenerator {
* @param {boolean} [deduplicateResources] If resources appear several times
* in the object graph only the first instance is serialized, with only the $id
* written for subsequent instances, false by default.
* @param {boolean} [convertResourcesToId] Convert resources that
* are specified for relationship fields into their id, false by default.
* @param {boolean} [ergo] target ergo.
*/
constructor(convertResourcesToRelationships, permitResourcesForRelationships, deduplicateResources) {
constructor(convertResourcesToRelationships, permitResourcesForRelationships, deduplicateResources, convertResourcesToId, ergo) {
this.convertResourcesToRelationships = convertResourcesToRelationships;
this.permitResourcesForRelationships = permitResourcesForRelationships;
this.deduplicateResources = deduplicateResources;
this.convertResourcesToId = convertResourcesToId;
this.ergo = ergo;
}

/**
Expand Down Expand Up @@ -141,8 +146,27 @@ class JSONGenerator {
}
}
result = array;
} else if (field.isPrimitive() || ModelUtil.isEnum(field)) {
} else if (field.isPrimitive()) {
result = this.convertToJSON(field, obj);
} else if (ModelUtil.isEnum(field)) {
if (this.ergo) {
// Boxes an enum value to the expected combination of sum types
const enumDeclaration = field.getParent().getModelFile().getType(field.getType());
const enumName = enumDeclaration.getFullyQualifiedName();
const properties = enumDeclaration.getProperties();
let either = { 'left' : obj };
for(let n=0; n < properties.length; n++) {
const property = properties[n];
if(property.getName() === obj) {
break;
} else {
either = { 'right' : either };
}
}
result = { 'type' : [enumName], 'data': either };
} else {
result = this.convertToJSON(field, obj);
}
} else {
parameters.stack.push(obj);
const classDeclaration = parameters.modelManager.getType(obj.getFullyQualifiedType());
Expand All @@ -163,10 +187,20 @@ class JSONGenerator {
switch (field.getType()) {
case 'DateTime':
{
return obj.isUtc() ? obj.format('YYYY-MM-DDTHH:mm:ss.SSS[Z]') : obj.format('YYYY-MM-DDTHH:mm:ss.SSSZ');
if (this.ergo) {
return obj;
} else {
return obj.isUtc() ? obj.format('YYYY-MM-DDTHH:mm:ss.SSS[Z]') : obj.format('YYYY-MM-DDTHH:mm:ss.SSSZ');
}
}
case 'Integer':
case 'Long':
case 'Long': {
if (this.ergo) {
return { nat: obj };
} else {
return obj;
}
}
case 'Double':
case 'Boolean':
default:
Expand Down Expand Up @@ -243,7 +277,11 @@ class JSONGenerator {
throw new Error('Did not find a relationship for ' + relationshipDeclaration.getFullyQualifiedTypeName() + ' found ' + relationshipOrResource);
}
}
return relationshipOrResource.toURI();
if (this.convertResourcesToId) {
return relationshipOrResource.getIdentifier();
} else {
return relationshipOrResource.toURI();
}
}
}

Expand Down
21 changes: 16 additions & 5 deletions packages/concerto-core/lib/serializer/jsonpopulator.js
Original file line number Diff line number Diff line change
Expand Up @@ -80,9 +80,11 @@ class JSONPopulator {
* Constructor.
* @param {boolean} [acceptResourcesForRelationships] Permit resources in the
* place of relationships, false by default.
* @param {boolean} [ergo] target ergo.
*/
constructor(acceptResourcesForRelationships) {
constructor(acceptResourcesForRelationships, ergo) {
this.acceptResourcesForRelationships = acceptResourcesForRelationships;
this.ergo = ergo;
}

/**
Expand Down Expand Up @@ -221,7 +223,7 @@ class JSONPopulator {
break;
case 'Integer':
case 'Long':
result = parseInt(json);
result = this.ergo ? parseInt(json.nat) : parseInt(json);
break;
case 'Double':
result = parseFloat(json);
Expand All @@ -232,11 +234,20 @@ class JSONPopulator {
case 'String':
result = json.toString();
break;
default:
default: {
// everything else should be an enumerated value...
result = json;
if (this.ergo) {
// unpack the enum
let current = json.data;
while (!current.left) {
current = current.right;
}
result = current.left;
} else {
result = json;
}
}
}

return result;
}

Expand Down
33 changes: 33 additions & 0 deletions packages/concerto-core/test/models/wildcards.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,12 +27,15 @@ describe('Wildcards Model', function () {
let factory;
let modelManager;
let serializer;
let ergoSerializer;

beforeEach(() => {
modelManager = new ModelManager();
Util.addComposerSystemModels(modelManager);
factory = new Factory(modelManager);
serializer = new Serializer(factory, modelManager);
ergoSerializer = new Serializer(factory, modelManager);
ergoSerializer.setDefaultOptions({ ergo: true });
const files = [
'./test/data/model/dependencies/base/base.cto',
'./test/data/model/wildcards.cto'
Expand Down Expand Up @@ -77,6 +80,36 @@ describe('Wildcards Model', function () {
resource.person.getFullyQualifiedIdentifier().should.equal('stdlib.base.Person#ALICE_1');
});

it('should parse a resource using types from a wildcard import (Ergo)', () => {
const json = {
$class: 'org.acme.wildcards.MyAsset',
assetId: '1',
concept: {
$class: 'org.acme.wildcards.MyConcept',
gender: { 'type': 'stdlib.base.Gender', 'data': { 'right' : { 'left': 'FEMALE' } } }
},
participant: {
$class: 'org.acme.wildcards.MyParticipant',
participantId: '1',
firstName: 'Alice',
lastName: 'A',
contactDetails: {
$class: 'stdlib.base.ContactDetails',
email: '[email protected]'
}
},
person: 'resource:stdlib.base.Person#ALICE_1'
};
const resource = ergoSerializer.fromJSON(json);
resource.assetId.should.equal('1');
resource.concept.gender.should.equal('FEMALE');
resource.participant.participantId.should.equal('1');
resource.participant.firstName.should.equal('Alice');
resource.participant.lastName.should.equal('A');
resource.participant.contactDetails.email.should.equal('[email protected]');
resource.person.getFullyQualifiedIdentifier().should.equal('stdlib.base.Person#ALICE_1');
});

it('should serialize a resource using types from a wildcard import', () => {
const resource = factory.newResource('org.acme.wildcards', 'MyAsset', '1');
resource.assetId = '1';
Expand Down
Loading

0 comments on commit ad0ce5e

Please sign in to comment.