Skip to content

Commit

Permalink
feat(dcs): add map type support for decorator command targets (#722)
Browse files Browse the repository at this point in the history
* 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
jonathan-casey authored Oct 19, 2023
1 parent aca5c3e commit 8d4fe1b
Show file tree
Hide file tree
Showing 8 changed files with 682 additions and 8 deletions.
2 changes: 1 addition & 1 deletion packages/concerto-core/api.txt
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ class Concerto {
+ object setCurrentTime()
class DecoratorManager {
+ ModelManager validate(decoratorCommandSet,ModelFile[]) throws Error
+ ModelManager decorateModels(ModelManager,decoratorCommandSet,object?,boolean?,boolean?)
+ ModelManager decorateModels(ModelManager,decoratorCommandSet,object?,boolean?,boolean?,boolean?)
+ void validateCommand(ModelManager,command)
+ Boolean falsyOrEqual(string||,string[])
+ void applyDecorator(decorated,string,newDecorator)
Expand Down
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 3.13.1 {f5a9a1ea6a64865843a3abb77798cbb0} 2023-10-18
- Add migrate option to DecoratorManager options

Version 3.13.1 {f435a20a00712e49c5cd32bc73ecb06a} 2023-10-03
- Add JSDoc for enableMapType option on ModelManager

Expand Down
122 changes: 115 additions & 7 deletions packages/concerto-core/lib/decoratormanager.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 */
Expand All @@ -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]
Expand Down Expand Up @@ -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
}
/**
Expand Down Expand Up @@ -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.
Expand All @@ -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(
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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
Expand Down
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" : []
}
}
]
}
Loading

0 comments on commit 8d4fe1b

Please sign in to comment.