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

fix(core): new pathway to apply namespace targeted decorators #877

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
4 changes: 2 additions & 2 deletions packages/concerto-core/api.txt
Original file line number Diff line number Diff line change
Expand Up @@ -59,12 +59,12 @@ class DecoratorExtractor {
}
class DecoratorManager {
+ ModelManager validate(decoratorCommandSet,ModelFile[]) throws Error
+ ModelManager decorateModels(ModelManager,decoratorCommandSet,object?,boolean?,boolean?,boolean?)
+ ModelManager decorateModels(ModelManager,decoratorCommandSet,object?,boolean?,boolean?,boolean?,boolean?)
+ ExtractDecoratorsResult extractDecorators(ModelManager,object,boolean,string)
+ void validateCommand(ModelManager,command)
+ Boolean falsyOrEqual(string||,string[])
+ void applyDecorator(decorated,string,newDecorator)
+ void executeCommand(string,declaration,command)
+ void executeCommand(string,declaration,command,boolean?)
+ void executePropertyCommand(property,command)
}
+ string[] intersect()
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 @@ -25,6 +25,9 @@
#


Version 3.16.8 {a23d37a4a92071314ff821f879943364} 2024-07-09
- Added a new pathway for applying dcs target at namespace

Version 3.16.7 {8f455df1e788c4994f423d6e236bee21} 2024-05-01
- Added missing `strictQualifiedDateTimes` option to Serializer.fromJSON

Expand Down
77 changes: 72 additions & 5 deletions packages/concerto-core/lib/decoratormanager.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ const ModelUtil = require('./modelutil');
const { MetaModelNamespace } = require('@accordproject/concerto-metamodel');
const semver = require('semver');
const DecoratorExtractor = require('./decoratorextractor');
const { Warning, ErrorCodes } = require('@accordproject/concerto-util');

// Types needed for TypeScript generation.
/* eslint-disable no-unused-vars */
Expand Down Expand Up @@ -209,6 +210,7 @@ class DecoratorManager {
* @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
* @param {boolean} [options.enableDcsNamespaceTarget] - flag to control applying namespace targeted decorators on top of the namespace instead of all declarations in that namespace
* @returns {ModelManager} a new model manager with the decorations applied
*/
static decorateModels(modelManager, decoratorCommandSet, options) {
Expand Down Expand Up @@ -245,7 +247,10 @@ class DecoratorManager {
decoratedAst.models.forEach((model) => {
model.declarations.forEach((decl) => {
decoratorCommandSet.commands.forEach((command) => {
this.executeCommand(model.namespace, decl, command);
this.executeCommand(model.namespace, decl, command, options?.enableDcsNamespaceTarget);
if(this.isNamespaceTargetEnabled(options?.enableDcsNamespaceTarget)) {
this.executeNamespaceCommand(model, command);
}
});
});
});
Expand Down Expand Up @@ -440,15 +445,35 @@ class DecoratorManager {
}
}

/**
* Executes a Command against a Model Namespace, adding
* decorators to the Namespace.
* @private
* @param {*} model the model
* @param {*} command the Command object from the dcs
*/
static executeNamespaceCommand(model, command) {
mttrbrts marked this conversation as resolved.
Show resolved Hide resolved
const { target, decorator, type } = command;

if (Object.keys(target).length === 2 && target.namespace) {
const { name } = ModelUtil.parseNamespace( model.namespace );
// should we just compare with namespace??
if(this.falsyOrEqual(target.namespace, [model.namespace,name])) {
this.applyDecorator(model, type, decorator);
}
}
}

/**
* Executes a Command against a ClassDeclaration, adding
* decorators to the ClassDeclaration, or its properties, as required.
* @param {string} namespace the namespace for the declaration
* @param {*} declaration the class declaration
* @param {*} command the Command object from the
* @param {*} command the Command object from the dcs
* @param {boolean} [enableDcsNamespaceTarget] - flag to control applying namespace targeted decorators on top of the namespace instead of all declarations in that namespace
* org.accordproject.decoratorcommands model
*/
static executeCommand(namespace, declaration, command) {
static executeCommand(namespace, declaration, command, enableDcsNamespaceTarget) {
const { target, decorator, type } = command;
const { name } = ModelUtil.parseNamespace( namespace );
if (this.falsyOrEqual(target.namespace, [namespace,name]) &&
Expand All @@ -474,10 +499,10 @@ class DecoratorManager {
this.applyDecorator(declaration.value, type, decorator);
}
} else {
this.applyDecorator(declaration, type, decorator);
this.checkForNamespaceTargetAndApplyDecorator(declaration, type, decorator, target, enableDcsNamespaceTarget);
}
} else if (!(target.property || target.properties || target.type)) {
this.applyDecorator(declaration, type, decorator);
this.checkForNamespaceTargetAndApplyDecorator(declaration, type, decorator, target, enableDcsNamespaceTarget);
} else {
// scalars are declarations but do not have properties
if (declaration.properties) {
Expand Down Expand Up @@ -511,6 +536,48 @@ class DecoratorManager {
this.applyDecorator(property, type, decorator);
}
}

/**
* Checks if enableDcsNamespaceTarget or ENABLE_DCS_TARGET_NAMESPACE is enabled or not
* if enabled, applies the decorator on top of the namespace or else on all declarations
* within the namespace.
* @private
* @param {*} declaration the type to apply the decorator to
* @param {string} type the command type
* @param {*} decorator the decorator to add
* @param {*} target the target object for the decorator
* @param {boolean} [enableDcsNamespaceTarget] - flag to control applying namespace targeted decorators on top of the namespace instead of all declarations in that namespace
*/
static checkForNamespaceTargetAndApplyDecorator(declaration, type, decorator, target, enableDcsNamespaceTarget) {
if(this.isNamespaceTargetEnabled(enableDcsNamespaceTarget)) {
if (target.declaration) {
this.applyDecorator(declaration, type, decorator);
}
} else {
this.applyDecorator(declaration, type, decorator);
}
}

/**
* Checks if enableDcsNamespaceTarget or ENABLE_DCS_TARGET_NAMESPACE is enabled or not
* and print deprecation warning if not enabled and return boolean value as well
* @private
* @param {boolean} [enableDcsNamespaceTarget] - flag to control applying namespace targeted decorators on top of the namespace instead of all declarations in that namespace
* @returns {Boolean} true if either of the flags is enabled
*/
static isNamespaceTargetEnabled(enableDcsNamespaceTarget) {
if(enableDcsNamespaceTarget || process.env.ENABLE_DCS_NAMESPACE_TARGET === 'true') {
return true;
} else {
Warning.printDeprecationWarning(
'Functionality for namespace targeted Decorator Command Sets has beed changed. Using namespace targets to apply decorators on all declarations in a namespace will be deprecated soon.',
ErrorCodes.DEPRECATION_WARNING,
ErrorCodes.CONCERTO_DEPRECATION_001,
'Please refer to https://concerto.accordproject.org/deprecation/001'
);
return false;
}
}
}

module.exports = DecoratorManager;
8 changes: 7 additions & 1 deletion packages/concerto-core/lib/introspect/modelfile.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ const MapDeclaration = require('./mapdeclaration');
const ModelUtil = require('../modelutil');
const Globalize = require('../globalize');
const Decorated = require('./decorated');
const { Warning, ErrorCodes } = require('@accordproject/concerto-util');

// Types needed for TypeScript generation.
/* eslint-disable no-unused-vars */
Expand Down Expand Up @@ -746,7 +747,12 @@ class ModelFile extends Decorated {
if (this.getModelManager().isStrict()){
throw new Error('Wilcard Imports are not permitted in strict mode.');
}
console.warn('DEPRECATED: Wilcard Imports are deprecated in this version of Concerto and will be removed in a future version.');
Warning.printDeprecationWarning(
'Wilcard Imports are deprecated in this version of Concerto and will be removed in a future version.',
ErrorCodes.DEPRECATION_WARNING,
ErrorCodes.CONCERTO_DEPRECATION_002,
'Please refer to https://concerto.accordproject.org/deprecation/002'
);
this.importWildcardNamespaces.push(imp.namespace);
break;
case `${MetaModelNamespace}.ImportTypes`:
Expand Down
13 changes: 13 additions & 0 deletions packages/concerto-core/test/data/decoratorcommands/web.json
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,19 @@
"name" : "Address",
"arguments" : []
}
},
{
"$class" : "[email protected]",
"type" : "UPSERT",
"target" : {
"$class" : "[email protected]",
"namespace" : "test"
},
"decorator" : {
"$class" : "[email protected]",
"name" : "IsValid",
"arguments" : []
}
}
]
}
102 changes: 99 additions & 3 deletions packages/concerto-core/test/decoratormanager.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ const VocabularyManager= require('../../concerto-vocabulary/lib/vocabularymanage
const Printer= require('../../concerto-cto/lib/printer');

const chai = require('chai');
const { DEPRECATION_WARNING, CONCERTO_DEPRECATION_001 } = require('@accordproject/concerto-util/lib/errorcodes');
require('chai').should();
chai.use(require('chai-things'));
chai.use(require('chai-as-promised'));
Expand Down Expand Up @@ -121,7 +122,7 @@ describe('DecoratorManager', () => {
decoratedModelManager.should.not.be.null;
});

it('should add decorator', async function() {
it('should add decorators that target declarations', async function() {
// load a model to decorate
const testModelManager = new ModelManager({strict:true});
const modelText = fs.readFileSync(path.join(__dirname,'/data/decoratorcommands/test.cto'), 'utf-8');
Expand All @@ -138,10 +139,105 @@ describe('DecoratorManager', () => {
const decl = decoratedModelManager.getType('[email protected]');
decl.should.not.be.null;
decl.getDecorator('Editable').should.not.be.null;
});

/*
This test is target to the functionality wherein if there exists a namespace targeted decorator, it applies the decorator to
all the declarations within the namespace, which has been identified as bug and will be deprecated.
*/
it('should add decorators that target namespace and catch warning - behaviour to be deprecated', async function() {
// event listner to catch the warning
process.once('warning', (warning) => {
chai.expect(warning.message).to.be.equals('DEPRECATED: Functionality for namespace targeted Decorator Command Sets has beed changed. Using namespace targets to apply decorators on all declarations in a namespace will be deprecated soon.');
chai.expect(warning.name).to.be.equals(DEPRECATION_WARNING);
chai.expect(warning.code).to.be.equals(CONCERTO_DEPRECATION_001);
chai.expect(warning.detail).to.be.equals('Please refer to https://concerto.accordproject.org/deprecation/001');
});
// load a model to decorate
const testModelManager = new ModelManager({strict:true});
const modelText = fs.readFileSync(path.join(__dirname,'/data/decoratorcommands/test.cto'), 'utf-8');
testModelManager.addCTOModel(modelText, 'test.cto');

const dcs = fs.readFileSync(path.join(__dirname,'/data/decoratorcommands/web.json'), 'utf-8');
const decoratedModelManager = DecoratorManager.decorateModels( testModelManager, JSON.parse(dcs),
{validate: true, validateCommands: true});

const modelFile = decoratedModelManager.getModelFile('[email protected]');
modelFile.should.not.be.null;
chai.expect(modelFile.getDecorator('IsValid')).to.be.null;

const ssnDecl = decoratedModelManager.getType('[email protected]');
ssnDecl.should.not.be.null;
ssnDecl.getDecorator('IsValid').should.not.be.null;

const decl = decoratedModelManager.getType('[email protected]');
decl.should.not.be.null;
decl.getDecorator('IsValid').should.not.be.null;
});

/*
This test is target to the functionality wherein if there exists a namespace targeted decorator, it applies the decorator to the
namespace, which is the new feature added can be accessed using the feature flag: ENABLE_DCS_NAMESPACE_TARGET.
*/
it('should add decorators that target namespace - updated behaviour using environment variable', async function() {
process.env.ENABLE_DCS_NAMESPACE_TARGET = 'true';
// load a model to decorate
const testModelManager = new ModelManager({strict:true});
const modelText = fs.readFileSync(path.join(__dirname,'/data/decoratorcommands/test.cto'), 'utf-8');
testModelManager.addCTOModel(modelText, 'test.cto');

const dcs = fs.readFileSync(path.join(__dirname,'/data/decoratorcommands/web.json'), 'utf-8');
const decoratedModelManager = DecoratorManager.decorateModels( testModelManager, JSON.parse(dcs),
{validate: true, validateCommands: true});

const modelFile = decoratedModelManager.getModelFile('[email protected]');
modelFile.should.not.be.null;
modelFile.getDecorator('IsValid').should.not.be.null;

const ssnDecl = decoratedModelManager.getType('[email protected]');
ssnDecl.should.not.be.null;
chai.expect(ssnDecl.getDecorator('IsValid')).to.be.null;
process.env.ENABLE_DCS_NAMESPACE_TARGET = 'false';
});

/*
This test is target to the functionality wherein if there exists a namespace targeted decorator, it applies the decorator to the
namespace, which is the new feature added can be accessed using the option parameter: enableDcsNamespaceTarget.
*/
it('should add decorators that target namespace - updated behaviour using options parameter', async function() {
// load a model to decorate
const testModelManager = new ModelManager({strict:true});
const modelText = fs.readFileSync(path.join(__dirname,'/data/decoratorcommands/test.cto'), 'utf-8');
testModelManager.addCTOModel(modelText, 'test.cto');

const dcs = fs.readFileSync(path.join(__dirname,'/data/decoratorcommands/web.json'), 'utf-8');
const decoratedModelManager = DecoratorManager.decorateModels( testModelManager, JSON.parse(dcs),
{validate: true, validateCommands: true, enableDcsNamespaceTarget: true});

const modelFile = decoratedModelManager.getModelFile('[email protected]');
modelFile.should.not.be.null;
modelFile.getDecorator('IsValid').should.not.be.null;

const ssnDecl = decoratedModelManager.getType('[email protected]');
ssnDecl.should.not.be.null;
chai.expect(ssnDecl.getDecorator('IsValid')).to.be.null;
});

it('should add decorators that target properties', async function() {
// load a model to decorate
const testModelManager = new ModelManager({strict:true});
const modelText = fs.readFileSync(path.join(__dirname,'/data/decoratorcommands/test.cto'), 'utf-8');
testModelManager.addCTOModel(modelText, 'test.cto');

const dcs = fs.readFileSync(path.join(__dirname,'/data/decoratorcommands/web.json'), 'utf-8');
const decoratedModelManager = DecoratorManager.decorateModels( testModelManager, JSON.parse(dcs),
{validate: true, validateCommands: true});

const decl = decoratedModelManager.getType('[email protected]');
decl.should.not.be.null;

const firstNameProperty = decl.getProperty('firstName');
firstNameProperty.should.not.be.null;

const decoratorFormFirstName = firstNameProperty.getDecorator('Form');
decoratorFormFirstName.should.not.be.null;
decoratorFormFirstName.getArguments()[0].should.equal('inputType');
Expand Down Expand Up @@ -567,4 +663,4 @@ describe('DecoratorManager', () => {
});
});

});
});
Loading
Loading