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

Validation for Decorator Command Sets #702

Merged
merged 10 commits into from
Sep 6, 2023
5 changes: 3 additions & 2 deletions packages/concerto-core/api.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ class AstModelManager extends BaseModelManager {
+ void constructor(object?)
}
class BaseModelManager {
+ void constructor(object?,boolean?,Object?,boolean?,processFile?)
+ void constructor(object?,boolean?,Object?,boolean?,boolean?,processFile?)
+ boolean isModelManager()
+ boolean isStrict()
+ Object accept(Object,Object)
Expand Down Expand Up @@ -55,7 +55,8 @@ class Concerto {
}
+ object setCurrentTime()
class DecoratorManager {
+ ModelManager decorateModels(ModelManager,decoratorCommandSet)
+ ModelManager decorateModels(ModelManager,decoratorCommandSet,object?,boolean?,boolean?)
+ void validateCommand(ModelManager,command)
+ Boolean falsyOrEqual(string|,string)
+ void applyDecorator(decorated,string,newDecorator)
+ void executeCommand(string,declaration,command)
Expand Down
4 changes: 4 additions & 0 deletions packages/concerto-core/changelog.txt
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,10 @@
# Note that the latest public API is documented using JSDocs and is available in api.txt.
#

Version 3.12.4 {7738d5490ea8438677e1d21d704bb5aa} 2023-08-31
- Adds validate and validateCommands options to DecoratorManager.decorateModels
- Adds addMetamodel option to BaseModelManager

Version 3.12.2 {bb8951ea059e312cd347b715bc9fc8d7} 2023-08-28
- Adds ModelFile :: getClassDeclarations

Expand Down
5 changes: 4 additions & 1 deletion packages/concerto-core/lib/basemodelmanager.js
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ class BaseModelManager {
* @param {boolean} [options.strict] - require versioned namespaces and imports
* @param {Object} [options.regExp] - An alternative regular expression engine.
* @param {boolean} [options.metamodelValidation] - When true, modelfiles will be validated
* against the metamodel when they are added to a BaseModelManager
* @param {boolean} [options.addMetamodel] - When true, the Concerto metamodel is added to the model manager
* @param {*} [processFile] - how to obtain a concerto AST from an input to the model manager
*/
constructor(options, processFile) {
Expand All @@ -96,6 +96,9 @@ class BaseModelManager {
// Cache a copy of the Metamodel ModelFile for use when validating the structure of ModelFiles later.
this.metamodelModelFile = new ModelFile(this, MetaModelUtil.metaModelAst, undefined, MetaModelNamespace);

if(options?.addMetamodel) {
this.addModelFile(this.metamodelModelFile);
}
}

/**
Expand Down
101 changes: 100 additions & 1 deletion packages/concerto-core/lib/decoratormanager.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,60 @@
'use strict';

const ModelManager = require('./modelmanager');
const Serializer = require('./serializer');
const Factory = require('./factory');

const DCS_MODEL = `concerto version "^3.0.0"
namespace [email protected]

import [email protected]

/**
* A reference to an existing named & versioned DecoratorCommandSet
*/
concept DecoratorCommandSetReference {
o String name
o String version
}

/**
* Whether to upsert or append the decorator
*/
enum CommandType {
o UPSERT
o APPEND
}

/**
* Which models elements to add the decorator to. Any null
* elements are 'wildcards'.
*/
concept CommandTarget {
o String namespace optional
o String declaration optional
o String property optional
o String type optional
}

/**
* Applies a decorator to a given target
*/
concept Command {
o CommandTarget target
o Decorator decorator
o CommandType type
}

/**
* A named and versioned set of commands. Includes are supported for modularity/reuse.
*/
concept DecoratorCommandSet {
o String name
o String version
o DecoratorCommandSetReference[] includes optional
o Command[] commands
}
`;

/**
* Utility functions to work with
Expand All @@ -27,9 +81,27 @@ class DecoratorManager {
* to the ModelManager.
* @param {ModelManager} modelManager the input model manager
* @param {*} decoratorCommandSet the DecoratorCommandSet object
* @param {object} [options] - decorator models options
* @param {boolean} [options.validate] - validate that decorator command set is valid
* 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
* @returns {ModelManager} a new model manager with the decorations applied
*/
static decorateModels(modelManager, decoratorCommandSet) {
static decorateModels(modelManager, decoratorCommandSet, options) {
if(options?.validate) {
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(validationModelManager, command);
});
}
}
const ast = modelManager.getAst(true);
const decoratedAst = JSON.parse(JSON.stringify(ast));
decoratedAst.models.forEach(model => {
Expand All @@ -44,6 +116,33 @@ class DecoratorManager {
return newModelManager;
}

/**
* Throws an error if the decoractor command is invalid
* @param {ModelManager} validationModelManager the validation model manager
* @param {*} command the decorator command
*/
static validateCommand(validationModelManager, command) {
if(command.target.type) {
validationModelManager.resolveType( 'DecoratorCommand.type', command.target.type);
}
if(command.target.namespace) {
const modelFile = validationModelManager.getModelFile(command.target.namespace);
if(!modelFile) {
throw new Error(`Decorator Command references namespace "${command.target.namespace}" which does not exist.`);
}
}
if(command.target.namespace && command.target.declaration) {
validationModelManager.resolveType( 'DecoratorCommand.target.declaration', `${command.target.namespace}.${command.target.declaration}`);
}
if(command.target.namespace && command.target.declaration && command.target.property) {
const decl = validationModelManager.getType(`${command.target.namespace}.${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.`);
}
}
}

/**
* Compares two values. If the first argument is falsy
* the function returns true.
Expand Down
2 changes: 2 additions & 0 deletions packages/concerto-core/lib/introspect/field.js
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,8 @@ class Field extends Property {
if (this.isPrimitive()) {
return false;
} else {
this.getParent()
.getModelFile().resolveType( 'property ' + this.getFullyQualifiedName(), this.getType());
const type = this.getParent()
.getModelFile()
.getType(this.getType());
Expand Down
54 changes: 0 additions & 54 deletions packages/concerto-core/test/data/decoratorcommands/decorators.cto

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
{
"$class" : "[email protected]",
"name" : "invalid-command",
"version": "1.0.0",
"commands" : [
{
"$class" : "[email protected]",
"type" : "INVALID",
"target" : {
"$class" : "[email protected]",
"type" : "[email protected]"
},
"decorator" : {
"$class" : "[email protected]",
"name" : "Test"
}
}
]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
{
"$class" : "[email protected]",
"name" : "invalid-type",
"version": "1.0.0",
"commands" : [
{
"$class" : "[email protected]",
"type" : "UPSERT",
"target" : {
"$class" : "[email protected]",
"type" : "[email protected]"
},
"decorator" : {
"$class" : "[email protected]",
"name" : "Test"
}
}
]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
{
"$class" : "[email protected]",
"name" : "invalid-target-namespace",
"version": "1.0.0",
"commands" : [
{
"$class" : "[email protected]",
"type" : "UPSERT",
"target" : {
"$class" : "[email protected]",
"namespace" : "[email protected]",
"declaration" : "Missing"
},
"decorator" : {
"$class" : "[email protected]",
"name" : "Test"
}
}
]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
{
"$class" : "[email protected]",
"name" : "invalid-target-namespace",
"version": "1.0.0",
"commands" : [
{
"$class" : "[email protected]",
"type" : "UPSERT",
"target" : {
"$class" : "[email protected]",
"namespace" : "[email protected]"
},
"decorator" : {
"$class" : "[email protected]",
"name" : "Test"
}
}
]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
{
"$class" : "[email protected]",
"name" : "invalid-target-property",
"version": "1.0.0",
"commands" : [
{
"$class" : "[email protected]",
"type" : "UPSERT",
"target" : {
"$class" : "[email protected]",
"namespace" : "[email protected]",
"declaration" : "Person",
"property" : "missing"
},
"decorator" : {
"$class" : "[email protected]",
"name" : "Test"
}
}
]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
{
"$class" : "[email protected]",
"name" : "invalid-type",
"version": "1.0.0",
"commands" : [
{
"$class" : "[email protected]",
"type" : "UPSERT",
"target" : {
"$class" : "[email protected]",
"type" : "[email protected]"
},
"decorator" : {
"$class" : "[email protected]",
"name" : "Test"
}
}
]
}
Loading