-
-
Notifications
You must be signed in to change notification settings - Fork 111
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(dcs): add map type support for decorator command targets (#722)
* feat(dcs): add map type support Signed-off-by: Jonathan Casey <[email protected]> * feat(dcs): add test coverage Signed-off-by: Jonathan Casey <[email protected]> * add env var for test runner Signed-off-by: Jonathan Casey <[email protected]> * feat(dcs): fix bug Signed-off-by: Jonathan Casey <[email protected]> * feat(dcs): add more test cases Signed-off-by: Jonathan Casey <[email protected]> * feat(dcs): add more test cov Signed-off-by: Jonathan Casey <[email protected]> * feat(dcs): rename command property to mapElement Signed-off-by: Jonathan Casey <[email protected]> * feat(dcs): refactor apply decorator logic Signed-off-by: Jonathan Casey <[email protected]> * feat(dcs): fix typo Signed-off-by: Jonathan Casey <[email protected]> * feat(dcs): jsdoc Signed-off-by: Jonathan Casey <[email protected]> * restart GH check Signed-off-by: Jonathan Casey <[email protected]> * feat(dcs): refactor to use enum for target elements Signed-off-by: Jonathan Casey <[email protected]> * feat(dcs): remove unusued test fixture Signed-off-by: Jonathan Casey <[email protected]> * feat(dcs): reference MetaModelNamespace Signed-off-by: Jonathan Casey <[email protected]> * feat(dcs): set decoratorcommands at version 0.3.0 Signed-off-by: Jonathan Casey <[email protected]> * feat(dcs): fix merge Signed-off-by: Jonathan Casey <[email protected]> * feat(map): auto migrates minor version Signed-off-by: Jonathan Casey <[email protected]> * fix jsdoc format Signed-off-by: Jonathan Casey <[email protected]> * restart coveralls Signed-off-by: Jonathan Casey <[email protected]> * feat(map): function rename Signed-off-by: Jonathan Casey <[email protected]> * feat(map): function rename in test Signed-off-by: Jonathan Casey <[email protected]> * feat(map): drop regex in favour of util fun Signed-off-by: Jonathan Casey <[email protected]> * feat(map): drop regex in favour of util fun Signed-off-by: Jonathan Casey <[email protected]> * feat(map): add client option to run migrate Signed-off-by: Jonathan Casey <[email protected]> * feat(map): add typedefs Signed-off-by: Jonathan Casey <[email protected]> * feat(map): add changelog Signed-off-by: Jonathan Casey <[email protected]> * test(map): add option to test Signed-off-by: Jonathan Casey <[email protected]> * chore: fix changelog Signed-off-by: Jonathan Casey <[email protected]> * feat(map): checks major version before migration Signed-off-by: Jonathan Casey <[email protected]> * feat(map): refactor conditional logic Signed-off-by: Jonathan Casey <[email protected]> --------- Signed-off-by: Jonathan Casey <[email protected]>
- Loading branch information
1 parent
aca5c3e
commit 8d4fe1b
Showing
8 changed files
with
682 additions
and
8 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -18,6 +18,8 @@ const ModelManager = require('./modelmanager'); | |
const Serializer = require('./serializer'); | ||
const Factory = require('./factory'); | ||
const ModelUtil = require('./modelutil'); | ||
const { MetaModelNamespace } = require('@accordproject/concerto-metamodel'); | ||
const semver = require('semver'); | ||
|
||
// Types needed for TypeScript generation. | ||
/* eslint-disable no-unused-vars */ | ||
|
@@ -27,6 +29,7 @@ if (global === undefined) { | |
} | ||
/* eslint-enable no-unused-vars */ | ||
|
||
const DCS_VERSION = '0.3.0'; | ||
|
||
const DCS_MODEL = `concerto version "^3.0.0" | ||
namespace [email protected] | ||
|
@@ -59,6 +62,16 @@ concept CommandTarget { | |
o String property optional | ||
o String[] properties optional // property and properties are mutually exclusive | ||
o String type optional | ||
o MapElement mapElement optional | ||
} | ||
/** | ||
* Map Declaration elements which might be used as a target | ||
*/ | ||
enum MapElement { | ||
o KEY | ||
o VALUE | ||
o KEY_VALUE | ||
} | ||
/** | ||
|
@@ -147,6 +160,43 @@ class DecoratorManager { | |
return validationModelManager; | ||
} | ||
|
||
/** | ||
* Rewrites the $class property on decoratorCommandSet classes. | ||
* @private | ||
* @param {*} decoratorCommandSet the DecoratorCommandSet object | ||
* @param {string} version the DCS version upgrade target | ||
* @returns {object} the migrated DecoratorCommandSet object | ||
*/ | ||
static migrateTo(decoratorCommandSet, version) { | ||
if (decoratorCommandSet instanceof Object) { | ||
for (let key in decoratorCommandSet) { | ||
if (key === '$class' && decoratorCommandSet[key].includes('org.accordproject.decoratorcommands')) { | ||
const ns = ModelUtil.getNamespace(decoratorCommandSet.$class); | ||
decoratorCommandSet[key] = decoratorCommandSet[key].replace( | ||
ModelUtil.parseNamespace(ns).version, | ||
DCS_VERSION); | ||
} | ||
if (decoratorCommandSet[key] instanceof Object || decoratorCommandSet[key] instanceof Array) { | ||
this.migrateTo(decoratorCommandSet[key], version); | ||
} | ||
} | ||
} | ||
return decoratorCommandSet; | ||
} | ||
|
||
/** | ||
* Checks if the supplied decoratorCommandSet can be migrated. | ||
* Migrations should only take place across minor versions of the same major version. | ||
* @private | ||
* @param {*} decoratorCommandSet the DecoratorCommandSet object | ||
* @param {*} DCS_VERSION the DecoratorCommandSet version | ||
* @returns {boolean} returns true if major versions are equal | ||
*/ | ||
static canMigrate(decoratorCommandSet, DCS_VERSION) { | ||
const inputVersion = ModelUtil.parseNamespace(ModelUtil.getNamespace(decoratorCommandSet.$class)).version; | ||
return (semver.major(inputVersion) === semver.major(DCS_VERSION) && (semver.minor(inputVersion) < semver.minor(DCS_VERSION))); | ||
} | ||
|
||
/** | ||
* Applies all the decorator commands from the DecoratorCommandSet | ||
* to the ModelManager. | ||
|
@@ -157,11 +207,29 @@ class DecoratorManager { | |
* with respect to to decorator command set model | ||
* @param {boolean} [options.validateCommands] - validate the decorator command set targets. Note that | ||
* the validate option must also be true | ||
* @param {boolean} [options.migrate] - migrate the decoratorCommandSet $class to match the dcs model version | ||
* @returns {ModelManager} a new model manager with the decorations applied | ||
*/ | ||
static decorateModels(modelManager, decoratorCommandSet, options) { | ||
|
||
if (options?.migrate && this.canMigrate(decoratorCommandSet, DCS_VERSION)) { | ||
decoratorCommandSet = this.migrateTo(decoratorCommandSet, DCS_VERSION); | ||
} | ||
|
||
if (options?.validate) { | ||
const validationModelManager = DecoratorManager.validate(decoratorCommandSet, modelManager.getModelFiles()); | ||
const validationModelManager = new ModelManager({ | ||
strict: true, | ||
metamodelValidation: true, | ||
addMetamodel: true, | ||
}); | ||
validationModelManager.addModelFiles(modelManager.getModelFiles()); | ||
validationModelManager.addCTOModel( | ||
DCS_MODEL, | ||
'[email protected]' | ||
); | ||
const factory = new Factory(validationModelManager); | ||
const serializer = new Serializer(factory, validationModelManager); | ||
serializer.fromJSON(decoratorCommandSet); | ||
if (options?.validateCommands) { | ||
decoratorCommandSet.commands.forEach((command) => { | ||
DecoratorManager.validateCommand( | ||
|
@@ -267,6 +335,27 @@ class DecoratorManager { | |
} | ||
} | ||
|
||
|
||
/** | ||
* Applies a new decorator to the Map element | ||
* @private | ||
* @param {string} element the element to apply the decorator to | ||
* @param {string} target the command target | ||
* @param {*} declaration the map declaration | ||
* @param {string} type the command type | ||
* @param {*} newDecorator the decorator to add | ||
*/ | ||
static applyDecoratorForMapElement(element, target, declaration, type, newDecorator ) { | ||
const decl = element === 'KEY' ? declaration.key : declaration.value; | ||
if (target.type) { | ||
if (this.falsyOrEqual(target.type, decl.$class)) { | ||
this.applyDecorator(decl, type, newDecorator); | ||
} | ||
} else { | ||
this.applyDecorator(decl, type, newDecorator); | ||
} | ||
} | ||
|
||
/** | ||
* Compares two arrays. If the first argument is falsy | ||
* the function returns true. | ||
|
@@ -326,12 +415,31 @@ class DecoratorManager { | |
*/ | ||
static executeCommand(namespace, declaration, command) { | ||
const { target, decorator, type } = command; | ||
const { name } = ModelUtil.parseNamespace(namespace); | ||
if ( | ||
this.falsyOrEqual(target.namespace, [namespace, name]) && | ||
this.falsyOrEqual(target.declaration, [declaration.name]) | ||
) { | ||
if (!target.property && !target.type) { | ||
const { name } = ModelUtil.parseNamespace( namespace ); | ||
if (this.falsyOrEqual(target.namespace, [namespace,name]) && | ||
this.falsyOrEqual(target.declaration, [declaration.name])) { | ||
|
||
if (declaration.$class === `${MetaModelNamespace}.MapDeclaration`) { | ||
if (target.mapElement) { | ||
switch(target.mapElement) { | ||
case 'KEY': | ||
case 'VALUE': | ||
this.applyDecoratorForMapElement(target.mapElement, target, declaration, type, decorator); | ||
break; | ||
case 'KEY_VALUE': | ||
this.applyDecoratorForMapElement('KEY', target, declaration, type, decorator); | ||
this.applyDecoratorForMapElement('VALUE', target, declaration, type, decorator); | ||
break; | ||
} | ||
} else if (target.type) { | ||
if (this.falsyOrEqual(target.type, declaration.key.$class)) { | ||
this.applyDecorator(declaration.key, type, decorator); | ||
} | ||
if (this.falsyOrEqual(target.type, declaration.value.$class)) { | ||
this.applyDecorator(declaration.value, type, decorator); | ||
} | ||
} | ||
} else if (!target.property && !target.type) { | ||
this.applyDecorator(declaration, type, decorator); | ||
} else { | ||
// scalars are declarations but do not have properties | ||
|
189 changes: 189 additions & 0 deletions
189
packages/concerto-core/test/data/decoratorcommands/incompatible_version_dcs.json
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,189 @@ | ||
{ | ||
"$class" : "[email protected]", | ||
"name" : "web", | ||
"version": "1.0.0", | ||
"commands" : [ | ||
{ | ||
"$class" : "[email protected]", | ||
"type" : "APPEND", | ||
"target" : { | ||
"$class" : "[email protected]", | ||
"namespace" : "[email protected]", | ||
"type" : "[email protected]", | ||
"declaration" : "Dictionary", | ||
"mapElement": "KEY" | ||
}, | ||
"decorator" : { | ||
"$class" : "[email protected]", | ||
"name" : "TEST", | ||
"arguments" : [] | ||
} | ||
}, | ||
{ | ||
"$class" : "[email protected]", | ||
"type" : "UPSERT", | ||
"target" : { | ||
"$class" : "[email protected]", | ||
"namespace" : "[email protected]", | ||
"declaration" : "Dictionary", | ||
"type" : "[email protected]", | ||
"mapElement": "KEY" | ||
}, | ||
"decorator" : { | ||
"$class" : "[email protected]", | ||
"name" : "Foo", | ||
"arguments" : [] | ||
} | ||
}, | ||
{ | ||
"$class" : "[email protected]", | ||
"type" : "UPSERT", | ||
"target" : { | ||
"$class" : "[email protected]", | ||
"namespace" : "[email protected]", | ||
"declaration" : "Dictionary", | ||
"mapElement": "KEY" | ||
}, | ||
"decorator" : { | ||
"$class" : "[email protected]", | ||
"name" : "Qux", | ||
"arguments" : [] | ||
} | ||
}, | ||
{ | ||
"$class" : "[email protected]", | ||
"type" : "UPSERT", | ||
"target" : { | ||
"$class" : "[email protected]", | ||
"namespace" : "[email protected]", | ||
"declaration" : "Dictionary", | ||
"type" : "[email protected]", | ||
"mapElement": "VALUE" | ||
}, | ||
"decorator" : { | ||
"$class" : "[email protected]", | ||
"name" : "Bar", | ||
"arguments" : [] | ||
} | ||
}, | ||
{ | ||
"$class" : "[email protected]", | ||
"type" : "UPSERT", | ||
"target" : { | ||
"$class" : "[email protected]", | ||
"namespace" : "[email protected]", | ||
"declaration" : "Dictionary", | ||
"mapElement": "VALUE" | ||
}, | ||
"decorator" : { | ||
"$class" : "[email protected]", | ||
"name" : "Quux", | ||
"arguments" : [] | ||
} | ||
}, | ||
{ | ||
"$class" : "[email protected]", | ||
"type" : "UPSERT", | ||
"target" : { | ||
"$class" : "[email protected]", | ||
"namespace" : "[email protected]", | ||
"declaration" : "Dictionary", | ||
"mapElement": "KEY_VALUE" | ||
}, | ||
"decorator" : { | ||
"$class" : "[email protected]", | ||
"name" : "Baz", | ||
"arguments" : [] | ||
} | ||
}, | ||
|
||
{ | ||
"$class" : "[email protected]", | ||
"type" : "UPSERT", | ||
"target" : { | ||
"$class" : "[email protected]", | ||
"namespace" : "[email protected]", | ||
"type" : "[email protected]", | ||
"mapElement": "KEY_VALUE" | ||
}, | ||
"decorator" : { | ||
"$class" : "[email protected]", | ||
"name" : "Bazola", | ||
"arguments" : [] | ||
} | ||
}, | ||
{ | ||
"$class" : "[email protected]", | ||
"type" : "UPSERT", | ||
"target" : { | ||
"$class" : "[email protected]", | ||
"namespace" : "[email protected]", | ||
"type" : "[email protected]", | ||
"mapElement": "KEY_VALUE" | ||
}, | ||
"decorator" : { | ||
"$class" : "[email protected]", | ||
"name" : "Bongo", | ||
"arguments" : [] | ||
} | ||
}, | ||
{ | ||
"$class" : "[email protected]", | ||
"type" : "UPSERT", | ||
"target" : { | ||
"$class" : "[email protected]", | ||
"namespace" : "[email protected]", | ||
"declaration" : "Dictionary", | ||
"type" : "[email protected]" | ||
}, | ||
"decorator" : { | ||
"$class" : "[email protected]", | ||
"name" : "DecoratesKeyByType", | ||
"arguments" : [] | ||
} | ||
}, | ||
{ | ||
"$class" : "[email protected]", | ||
"type" : "UPSERT", | ||
"target" : { | ||
"$class" : "[email protected]", | ||
"namespace" : "[email protected]", | ||
"declaration" : "Dictionary", | ||
"type" : "[email protected]" | ||
}, | ||
"decorator" : { | ||
"$class" : "[email protected]", | ||
"name" : "DecoratesValueByType", | ||
"arguments" : [] | ||
} | ||
}, | ||
{ | ||
"$class" : "[email protected]", | ||
"type" : "UPSERT", | ||
"target" : { | ||
"$class" : "[email protected]", | ||
"namespace" : "[email protected]", | ||
"type" : "[email protected]" | ||
}, | ||
"decorator" : { | ||
"$class" : "[email protected]", | ||
"name" : "DecoratesAllMapKeys", | ||
"arguments" : [] | ||
} | ||
}, | ||
{ | ||
"$class" : "[email protected]", | ||
"type" : "UPSERT", | ||
"target" : { | ||
"$class" : "[email protected]", | ||
"namespace" : "[email protected]", | ||
"type" : "[email protected]" | ||
}, | ||
"decorator" : { | ||
"$class" : "[email protected]", | ||
"name" : "DecoratesAllMapValues", | ||
"arguments" : [] | ||
} | ||
} | ||
] | ||
} |
Oops, something went wrong.