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(decoratormanager) add support for unversioned namespaces in decorator command set target #712

Merged
merged 1 commit into from
Sep 8, 2023
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
3 changes: 2 additions & 1 deletion packages/concerto-core/api.txt
Original file line number Diff line number Diff line change
Expand Up @@ -57,10 +57,11 @@ class Concerto {
class DecoratorManager {
+ ModelManager decorateModels(ModelManager,decoratorCommandSet,object?,boolean?,boolean?)
+ void validateCommand(ModelManager,command)
+ Boolean falsyOrEqual(string|,string)
+ Boolean falsyOrEqual(string|,string[])
+ void applyDecorator(decorated,string,newDecorator)
+ void executeCommand(string,declaration,command)
}
+ boolean isUnversionedNamespaceEqual()
class Factory {
+ string newId()
+ void constructor(ModelManager)
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.12.5 {ea967f943046b2792fe3863943a6b2ba} 2023-09-08
- Update DecoratorManager to support multiple value compare

Version 3.12.4 {7738d5490ea8438677e1d21d704bb5aa} 2023-08-31
- Adds validate and validateCommands options to DecoratorManager.decorateModels
- Adds addMetamodel option to BaseModelManager
Expand Down
50 changes: 38 additions & 12 deletions packages/concerto-core/lib/decoratormanager.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
const ModelManager = require('./modelmanager');
const Serializer = require('./serializer');
const Factory = require('./factory');
const ModelUtil = require('./modelutil');

const DCS_MODEL = `concerto version "^3.0.0"
namespace [email protected]
Expand Down Expand Up @@ -70,6 +71,20 @@ concept DecoratorCommandSet {
}
`;


/**
* Returns true if the unversioned namespace for a model
* file is equal to a target
* @param {ModelFile} modelFile the model file to test
* @param {string} unversionedNamespace the unversioned namespace to test against
* @returns {boolean} true is the unversioned namespace for the
* model file equals unversionedNamespace
*/
function isUnversionedNamespaceEqual(modelFile, unversionedNamespace) {
const { name } = ModelUtil.parseNamespace( modelFile.getNamespace() );
return name === unversionedNamespace;
}

/**
* Utility functions to work with
* [DecoratorCommandSet](https://models.accordproject.org/concerto/decorators.cto)
Expand Down Expand Up @@ -125,17 +140,27 @@ class DecoratorManager {
if(command.target.type) {
validationModelManager.resolveType( 'DecoratorCommand.type', command.target.type);
}
let modelFile = null;
if(command.target.namespace) {
const modelFile = validationModelManager.getModelFile(command.target.namespace);
modelFile = validationModelManager.getModelFile(command.target.namespace);
if(!modelFile) {
throw new Error(`Decorator Command references namespace "${command.target.namespace}" which does not exist.`);
const { name, version } = ModelUtil.parseNamespace(command.target.namespace);
if(!version) {
// does the model file exist with any version?
modelFile = validationModelManager.getModelFiles()
.find((m) => isUnversionedNamespaceEqual(m, name));
}
}
}
if(command.target.namespace && !modelFile) {
throw new Error(`Decorator Command references namespace "${command.target.namespace}" which does not exist: ${JSON.stringify(command, null, 2)}`);
}

if(command.target.namespace && command.target.declaration) {
validationModelManager.resolveType( 'DecoratorCommand.target.declaration', `${command.target.namespace}.${command.target.declaration}`);
validationModelManager.resolveType( 'DecoratorCommand.target.declaration', `${modelFile.getNamespace()}.${command.target.declaration}`);
}
if(command.target.namespace && command.target.declaration && command.target.property) {
const decl = validationModelManager.getType(`${command.target.namespace}.${command.target.declaration}`);
const decl = validationModelManager.getType(`${modelFile.getNamespace()}.${command.target.declaration}`);
const property = decl.getProperty(command.target.property);
if(!property) {
throw new Error(`Decorator Command references property "${command.target.namespace}.${command.target.declaration}.${command.target.property}" which does not exist.`);
Expand All @@ -147,11 +172,11 @@ class DecoratorManager {
* Compares two values. If the first argument is falsy
* the function returns true.
* @param {string | null} test the value to test (lhs)
* @param {string} value the value to compare (rhs)
* @returns {Boolean} true if the lhs is falsy or test === value
* @param {string[]} values the values to compare (rhs)
* @returns {Boolean} true if the lhs is falsy or values.includes(test)
*/
static falsyOrEqual(test, value) {
return test ? test === value : true;
static falsyOrEqual(test, values) {
return test ? values.includes(test) : true;
}

/**
Expand Down Expand Up @@ -197,17 +222,18 @@ class DecoratorManager {
*/
static executeCommand(namespace, declaration, command) {
const { target, decorator, type } = command;
if (this.falsyOrEqual(target.namespace, namespace) &&
this.falsyOrEqual(target.declaration, declaration.name)) {
const { name } = ModelUtil.parseNamespace( namespace );
if (this.falsyOrEqual(target.namespace, [namespace,name]) &&
this.falsyOrEqual(target.declaration, [declaration.name])) {
if (!target.property && !target.type) {
this.applyDecorator(declaration, type, decorator);
}
else {
// scalars are declarations but do not have properties
if(declaration.properties) {
declaration.properties.forEach(property => {
if (this.falsyOrEqual(target.property, property.name) &&
this.falsyOrEqual(target.type, property.$class)) {
if (this.falsyOrEqual(target.property, [property.name]) &&
this.falsyOrEqual(target.type, [property.$class])) {
this.applyDecorator(property, type, decorator);
}
});
Expand Down
15 changes: 15 additions & 0 deletions packages/concerto-core/test/data/decoratorcommands/web.json
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,21 @@
]
}
},
{
"$class" : "[email protected]",
"type" : "UPSERT",
"target" : {
"$class" : "[email protected]",
"namespace" : "test",
"declaration" : "Person",
"property" : "bio"
},
"decorator" : {
"$class" : "[email protected]",
"name" : "UnversionedNamespace",
"arguments" : []
}
},
{
"$class" : "[email protected]",
"type" : "UPSERT",
Expand Down
34 changes: 33 additions & 1 deletion packages/concerto-core/test/decoratormanager.js
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,35 @@ describe('DecoratorManager', () => {
});

describe('#decorateModels', function() {
it('should support no validation', async function() {
const testModelManager = new ModelManager({strict:true});
const modelText = fs.readFileSync('./test/data/decoratorcommands/test.cto', 'utf-8');
testModelManager.addCTOModel(modelText, 'test.cto');
const dcs = fs.readFileSync('./test/data/decoratorcommands/web.json', 'utf-8');
let decoratedModelManager = DecoratorManager.decorateModels( testModelManager, JSON.parse(dcs));
decoratedModelManager.should.not.be.null;
});

it('should support syntax validation', async function() {
const testModelManager = new ModelManager({strict:true});
const modelText = fs.readFileSync('./test/data/decoratorcommands/test.cto', 'utf-8');
testModelManager.addCTOModel(modelText, 'test.cto');
const dcs = fs.readFileSync('./test/data/decoratorcommands/web.json', 'utf-8');
let decoratedModelManager = DecoratorManager.decorateModels( testModelManager, JSON.parse(dcs),
{validate: true});
decoratedModelManager.should.not.be.null;
});

it('should support semantic validation', async function() {
const testModelManager = new ModelManager({strict:true});
const modelText = fs.readFileSync('./test/data/decoratorcommands/test.cto', 'utf-8');
testModelManager.addCTOModel(modelText, 'test.cto');
const dcs = fs.readFileSync('./test/data/decoratorcommands/web.json', 'utf-8');
let decoratedModelManager = DecoratorManager.decorateModels( testModelManager, JSON.parse(dcs),
{validate: true, validateCommands: true});
decoratedModelManager.should.not.be.null;
});

it('should add decorator', async function() {
// load a model to decorate
const testModelManager = new ModelManager({strict:true});
Expand All @@ -58,7 +87,7 @@ describe('DecoratorManager', () => {

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

const ssnDecl = decoratedModelManager.getType('[email protected]');
ssnDecl.should.not.be.null;
Expand All @@ -85,6 +114,9 @@ describe('DecoratorManager', () => {
decoratorBio.should.not.be.null;
decoratorBio.getArguments()[0].should.equal('inputType');
decoratorBio.getArguments()[1].should.equal('textArea');

const decoratorUnversionedNamespace = bioProperty.getDecorator('UnversionedNamespace');
decoratorUnversionedNamespace.should.not.be.null;
});

it('should fail with invalid command', async function() {
Expand Down