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

feat(dcs): add map type support for decorator command targets #722

Merged
merged 32 commits into from
Oct 19, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
9c690b3
feat(dcs): add map type support
jonathan-casey Oct 2, 2023
a074e5c
feat(dcs): add test coverage
jonathan-casey Oct 2, 2023
134c20b
add env var for test runner
jonathan-casey Oct 2, 2023
da4fdcf
feat(dcs): fix bug
jonathan-casey Oct 2, 2023
02de497
feat(dcs): add more test cases
jonathan-casey Oct 2, 2023
33c17ba
feat(dcs): add more test cov
jonathan-casey Oct 3, 2023
c57c1da
feat(dcs): rename command property to mapElement
jonathan-casey Oct 3, 2023
3c0a0c1
feat(dcs): refactor apply decorator logic
jonathan-casey Oct 3, 2023
76baa65
feat(dcs): fix typo
jonathan-casey Oct 3, 2023
ea63a5f
feat(dcs): jsdoc
jonathan-casey Oct 3, 2023
d51e5ef
restart GH check
jonathan-casey Oct 4, 2023
90ebf47
feat(dcs): refactor to use enum for target elements
jonathan-casey Oct 4, 2023
ec2a2d3
feat(dcs): remove unusued test fixture
jonathan-casey Oct 4, 2023
00b54e1
feat(dcs): reference MetaModelNamespace
jonathan-casey Oct 4, 2023
8aab804
feat(dcs): set decoratorcommands at version 0.3.0
jonathan-casey Oct 10, 2023
19efa27
Merge branch 'main' into jonathan/decorator_command_sets_map
jonathan-casey Oct 10, 2023
296ca11
feat(dcs): fix merge
jonathan-casey Oct 10, 2023
8fe1717
feat(map): auto migrates minor version
jonathan-casey Oct 16, 2023
13a4242
fix jsdoc format
jonathan-casey Oct 16, 2023
2d09e53
restart coveralls
jonathan-casey Oct 17, 2023
3bb1c25
feat(map): function rename
jonathan-casey Oct 18, 2023
3cbe91b
feat(map): function rename in test
jonathan-casey Oct 18, 2023
cf48362
feat(map): drop regex in favour of util fun
jonathan-casey Oct 18, 2023
e2cf402
feat(map): drop regex in favour of util fun
jonathan-casey Oct 18, 2023
8b799d5
feat(map): add client option to run migrate
jonathan-casey Oct 18, 2023
5f8729e
feat(map): add typedefs
jonathan-casey Oct 18, 2023
07aed29
feat(map): add changelog
jonathan-casey Oct 18, 2023
c961db4
test(map): add option to test
jonathan-casey Oct 18, 2023
3fc4aec
Merge branch 'main' into jonathan/decorator_command_sets_map
jonathan-casey Oct 18, 2023
83f01a4
chore: fix changelog
jonathan-casey Oct 18, 2023
1cb7055
feat(map): checks major version before migration
jonathan-casey Oct 19, 2023
3e856ce
feat(map): refactor conditional logic
jonathan-casey Oct 19, 2023
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 @@ -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 org.accordproject.decoratorcommands@0.3.0
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')) {
jonathan-casey marked this conversation as resolved.
Show resolved Hide resolved
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,
'decoratorcommands@0.3.0.cto'
);
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 );
jonathan-casey marked this conversation as resolved.
Show resolved Hide resolved
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" : "org.accordproject.decoratorcommands@0.2.0.DecoratorCommandSet",
"name" : "web",
"version": "1.0.0",
"commands" : [
{
"$class" : "org.accordproject.decoratorcommands@0.2.0.Command",
"type" : "APPEND",
"target" : {
"$class" : "org.accordproject.decoratorcommands@0.2.0.CommandTarget",
"namespace" : "test@1.0.0",
"type" : "concerto.metamodel@1.0.0.StringMapKeyType",
"declaration" : "Dictionary",
"mapElement": "KEY"
},
"decorator" : {
"$class" : "concerto.metamodel@1.0.0.Decorator",
"name" : "TEST",
"arguments" : []
}
},
{
"$class" : "org.accordproject.decoratorcommands@0.2.0.Command",
"type" : "UPSERT",
"target" : {
"$class" : "org.accordproject.decoratorcommands@0.2.0.CommandTarget",
"namespace" : "test@1.0.0",
"declaration" : "Dictionary",
"type" : "concerto.metamodel@1.0.0.StringMapKeyType",
"mapElement": "KEY"
},
"decorator" : {
"$class" : "concerto.metamodel@1.0.0.Decorator",
"name" : "Foo",
"arguments" : []
}
},
{
"$class" : "org.accordproject.decoratorcommands@0.2.0.Command",
"type" : "UPSERT",
"target" : {
"$class" : "org.accordproject.decoratorcommands@0.2.0.CommandTarget",
"namespace" : "test@1.0.0",
"declaration" : "Dictionary",
"mapElement": "KEY"
},
"decorator" : {
"$class" : "concerto.metamodel@1.0.0.Decorator",
"name" : "Qux",
"arguments" : []
}
},
{
"$class" : "org.accordproject.decoratorcommands@0.2.0.Command",
"type" : "UPSERT",
"target" : {
"$class" : "org.accordproject.decoratorcommands@0.2.0.CommandTarget",
"namespace" : "test@1.0.0",
"declaration" : "Dictionary",
"type" : "concerto.metamodel@1.0.0.StringMapValueType",
"mapElement": "VALUE"
},
"decorator" : {
"$class" : "concerto.metamodel@1.0.0.Decorator",
"name" : "Bar",
"arguments" : []
}
},
{
"$class" : "org.accordproject.decoratorcommands@0.2.0.Command",
"type" : "UPSERT",
"target" : {
"$class" : "org.accordproject.decoratorcommands@0.2.0.CommandTarget",
"namespace" : "test@1.0.0",
"declaration" : "Dictionary",
"mapElement": "VALUE"
},
"decorator" : {
"$class" : "concerto.metamodel@1.0.0.Decorator",
"name" : "Quux",
"arguments" : []
}
},
{
"$class" : "org.accordproject.decoratorcommands@0.2.0.Command",
"type" : "UPSERT",
"target" : {
"$class" : "org.accordproject.decoratorcommands@0.2.0.CommandTarget",
"namespace" : "test@1.0.0",
"declaration" : "Dictionary",
"mapElement": "KEY_VALUE"
},
"decorator" : {
"$class" : "concerto.metamodel@1.0.0.Decorator",
"name" : "Baz",
"arguments" : []
}
},

{
"$class" : "org.accordproject.decoratorcommands@0.2.0.Command",
"type" : "UPSERT",
"target" : {
"$class" : "org.accordproject.decoratorcommands@0.2.0.CommandTarget",
"namespace" : "test@1.0.0",
"type" : "concerto.metamodel@1.0.0.StringMapKeyType",
"mapElement": "KEY_VALUE"
},
"decorator" : {
"$class" : "concerto.metamodel@1.0.0.Decorator",
"name" : "Bazola",
"arguments" : []
}
},
{
"$class" : "org.accordproject.decoratorcommands@0.2.0.Command",
"type" : "UPSERT",
"target" : {
"$class" : "org.accordproject.decoratorcommands@0.2.0.CommandTarget",
"namespace" : "test@1.0.0",
"type" : "concerto.metamodel@1.0.0.StringMapValueType",
"mapElement": "KEY_VALUE"
},
"decorator" : {
"$class" : "concerto.metamodel@1.0.0.Decorator",
"name" : "Bongo",
"arguments" : []
}
},
{
"$class" : "org.accordproject.decoratorcommands@0.2.0.Command",
"type" : "UPSERT",
"target" : {
"$class" : "org.accordproject.decoratorcommands@0.2.0.CommandTarget",
"namespace" : "test@1.0.0",
"declaration" : "Dictionary",
"type" : "concerto.metamodel@1.0.0.StringMapKeyType"
},
"decorator" : {
"$class" : "concerto.metamodel@1.0.0.Decorator",
"name" : "DecoratesKeyByType",
"arguments" : []
}
},
{
"$class" : "org.accordproject.decoratorcommands@0.2.0.Command",
"type" : "UPSERT",
"target" : {
"$class" : "org.accordproject.decoratorcommands@0.2.0.CommandTarget",
"namespace" : "test@1.0.0",
"declaration" : "Dictionary",
"type" : "concerto.metamodel@1.0.0.StringMapValueType"
},
"decorator" : {
"$class" : "concerto.metamodel@1.0.0.Decorator",
"name" : "DecoratesValueByType",
"arguments" : []
}
},
{
"$class" : "org.accordproject.decoratorcommands@0.2.0.Command",
"type" : "UPSERT",
"target" : {
"$class" : "org.accordproject.decoratorcommands@0.2.0.CommandTarget",
"namespace" : "test@1.0.0",
"type" : "concerto.metamodel@1.0.0.StringMapKeyType"
},
"decorator" : {
"$class" : "concerto.metamodel@1.0.0.Decorator",
"name" : "DecoratesAllMapKeys",
"arguments" : []
}
},
{
"$class" : "org.accordproject.decoratorcommands@0.2.0.Command",
"type" : "UPSERT",
"target" : {
"$class" : "org.accordproject.decoratorcommands@0.2.0.CommandTarget",
"namespace" : "test@1.0.0",
"type" : "concerto.metamodel@1.0.0.StringMapValueType"
},
"decorator" : {
"$class" : "concerto.metamodel@1.0.0.Decorator",
"name" : "DecoratesAllMapValues",
"arguments" : []
}
}
]
}
Loading
Loading