Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feature(validation) add Ergo-style validation #111

Merged
merged 1 commit into from
Oct 6, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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