Skip to content

Commit

Permalink
feat(vocabulary): vocaulary for map types (#755)
Browse files Browse the repository at this point in the history
* feat(vocabulary): vocaulary for map types

Signed-off-by: Sanket Shevkar <Sanket.Shevkar@docusign.com>

* feat(vocabulary): enable map types for tests

Signed-off-by: Sanket Shevkar <Sanket.Shevkar@docusign.com>

* feat(vocabulary): resolved review comments

Signed-off-by: Sanket Shevkar <sanket.shevkar@docusign.com>

* feat(vocabulary): addressed comments

Signed-off-by: Sanket Shevkar <sanket.shevkar@docusign.com>

* feat(vocabulary): addressed comments

Signed-off-by: Sanket Shevkar <Sanket.Shevkar@docusign.com>

* feat(vocabulary): addressed comments

Signed-off-by: Sanket Shevkar <Sanket.Shevkar@docusign.com>

---------

Signed-off-by: Sanket Shevkar <Sanket.Shevkar@docusign.com>
Signed-off-by: Sanket Shevkar <sanket.shevkar@docusign.com>
Co-authored-by: Sanket Shevkar <Sanket.Shevkar@docusign.com>
  • Loading branch information
sanketshevkar and Sanket Shevkar committed Nov 14, 2023
1 parent 4758d64 commit 53abb6d
Show file tree
Hide file tree
Showing 18 changed files with 269 additions and 23 deletions.
3 changes: 3 additions & 0 deletions packages/concerto-core/api.txt
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,7 @@ class Declaration extends Decorated {
+ boolean isEnum()
+ boolean isClassDeclaration()
+ boolean isScalarDeclaration()
+ boolean isMapDeclaration()
}
class Decorator {
+ void constructor(ClassDeclaration|Property,Object) throws IllegalModelException
Expand Down Expand Up @@ -180,6 +181,7 @@ class MapKeyType extends Decorated {
+ String toString()
+ boolean isKey()
+ boolean isValue()
+ string getNamespace()
}
class MapValueType extends Decorated {
+ void constructor(MapDeclaration,Object) throws IllegalModelException
Expand All @@ -190,6 +192,7 @@ class MapValueType extends Decorated {
+ String toString()
+ boolean isKey()
+ boolean isValue()
+ string getNamespace()
}
+ ModelManager newMetaModelManager()
+ object validateMetaModel()
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.2 {dccc690753912cf87e7ceec56d949058} 2023-10-18
- Add getNamespace method to key type and value type of maps

Version 3.13.1 {f5a9a1ea6a64865843a3abb77798cbb0} 2023-10-18
- Add migrate option to DecoratorManager options

Expand Down
6 changes: 4 additions & 2 deletions packages/concerto-core/lib/decoratormanager.js
Original file line number Diff line number Diff line change
Expand Up @@ -54,14 +54,14 @@ enum CommandType {
/**
* Which models elements to add the decorator to. Any null
* elements are 'wildcards'.
* elements are 'wildcards'.
*/
concept CommandTarget {
o String namespace optional
o String declaration optional
o String property optional
o String[] properties optional // property and properties are mutually exclusive
o String type optional
o String type optional
o MapElement mapElement optional
}
Expand Down Expand Up @@ -438,6 +438,8 @@ class DecoratorManager {
if (this.falsyOrEqual(target.type, declaration.value.$class)) {
this.applyDecorator(declaration.value, type, decorator);
}
} else {
this.applyDecorator(declaration, type, decorator);
}
} else if (!(target.property || target.properties || target.type)) {
this.applyDecorator(declaration, type, decorator);
Expand Down
9 changes: 9 additions & 0 deletions packages/concerto-core/lib/introspect/declaration.js
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,15 @@ class Declaration extends Decorated {
isScalarDeclaration() {
return false;
}

/**
* Returns true if this class is the definition of a map-declaration.
*
* @return {boolean} true if the class is a map-declaration
*/
isMapDeclaration() {
return false;
}
}

module.exports = Declaration;
8 changes: 8 additions & 0 deletions packages/concerto-core/lib/introspect/mapkeytype.js
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,14 @@ class MapKeyType extends Decorated {
isValue() {
return false;
}

/**
* Return the namespace of this map key.
* @return {string} namespace - a namespace.
*/
getNamespace() {
return this.modelFile.getNamespace();
}
}

module.exports = MapKeyType;
8 changes: 8 additions & 0 deletions packages/concerto-core/lib/introspect/mapvaluetype.js
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,14 @@ class MapValueType extends Decorated {
isValue() {
return true;
}

/**
* Return the namespace of this map value.
* @return {string} namespace - a namespace.
*/
getNamespace() {
return this.modelFile.getNamespace();
}
}

module.exports = MapValueType;
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,20 @@
"name" : "web",
"version": "1.0.0",
"commands" : [
{
"$class" : "org.accordproject.decoratorcommands@0.3.0.Command",
"type" : "UPSERT",
"target" : {
"$class" : "org.accordproject.decoratorcommands@0.3.0.CommandTarget",
"namespace" : "test@1.0.0",
"declaration" : "Dictionary"
},
"decorator" : {
"$class" : "concerto.metamodel@1.0.0.Decorator",
"name" : "MapDeclarationDecorator",
"arguments" : []
}
},
{
"$class" : "org.accordproject.decoratorcommands@0.3.0.Command",
"type" : "APPEND",
Expand Down
18 changes: 17 additions & 1 deletion packages/concerto-core/test/decoratormanager.js
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,21 @@ describe('DecoratorManager', () => {
decoratorCity2Property.should.not.be.null;
});

it('should decorate the specified MapDeclaration', async function() {
// load a model to decorate
const testModelManager = new ModelManager({strict:true, skipLocationNodes: true});
const modelText = fs.readFileSync('./test/data/decoratorcommands/test.cto', 'utf-8');
testModelManager.addCTOModel(modelText, 'test.cto');

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

const dictionary = decoratedModelManager.getType('test@1.0.0.Dictionary');
dictionary.should.not.be.null;
dictionary.getDecorator('MapDeclarationDecorator').should.not.be.null;
});

it('should decorate the specified element on the specified Map Declaration (Map Key)', async function() {
// load a model to decorate
const testModelManager = new ModelManager({strict:true, skipLocationNodes: true});
Expand Down Expand Up @@ -284,7 +299,7 @@ describe('DecoratorManager', () => {
dictionary.value.getDecorator('DecoratesValueByType').should.not.be.null;
});

it('should decorate both Key and Value elements on the specified Map Declaration', async function() {
it('should decorate Declaration, Key and Value elements on the specified Map Declaration', async function() {
// load a model to decorate
const testModelManager = new ModelManager({strict:true, skipLocationNodes: true});
const modelText = fs.readFileSync('./test/data/decoratorcommands/test.cto', 'utf-8');
Expand All @@ -297,6 +312,7 @@ describe('DecoratorManager', () => {
const dictionary = decoratedModelManager.getType('test@1.0.0.Dictionary');

dictionary.should.not.be.null;
dictionary.getDecorator('MapDeclarationDecorator').should.not.be.null;
dictionary.key.getDecorator('Baz').should.not.be.null;
dictionary.value.getDecorator('Baz').should.not.be.null;
});
Expand Down
6 changes: 6 additions & 0 deletions packages/concerto-core/test/introspect/declaration.js
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,12 @@ describe('Declaration', () => {
});
});

describe('#isMapDeclaration', () => {
it('should be false', () => {
declaration.isMapDeclaration().should.equal(false);
});
});

describe('#isSystemIdentified', () => {
it('should be false', () => {
declaration.isSystemIdentified().should.equal(false);
Expand Down
8 changes: 8 additions & 0 deletions packages/concerto-core/test/introspect/mapdeclaration.js
Original file line number Diff line number Diff line change
Expand Up @@ -757,4 +757,12 @@ describe('MapDeclaration', () => {
declaration.getValue().getParent().should.equal(declaration);
});
});

describe('#getNamespace', () => {
it('should return the correct namespace for a Map Declaration Key and Value', () => {
let declaration = introspectUtils.loadLastDeclaration('test/data/parser/mapdeclaration/mapdeclaration.goodkey.primitive.string.cto', MapDeclaration);
declaration.getKey().getNamespace().should.equal('com.acme@1.0.0');
declaration.getValue().getNamespace().should.equal('com.acme@1.0.0');
});
});
});
41 changes: 36 additions & 5 deletions packages/concerto-vocabulary/lib/vocabulary.js
Original file line number Diff line number Diff line change
Expand Up @@ -144,16 +144,47 @@ class Vocabulary {
* @returns {*} an object with missingTerms and additionalTerms properties
*/
validate(modelFile) {
const getOwnProperties = (d) => {
// ensures we have a valid return, even for scalars
return d.getOwnProperties?.() ? d.getOwnProperties?.() : [];
const getOwnProperties = (declaration) => {
// ensures we have a valid return, even for scalars and map-declarations
if(declaration.isMapDeclaration()) {
return [declaration.getKey(), declaration.getValue()];
} else {
return declaration.getOwnProperties?.() ? declaration.getOwnProperties?.() : [];
}
};

const getPropertyName = (property) => {
if(property.isKey?.()) {
return 'KEY';
} else if(property.isValue?.()) {
return 'VALUE';
} else {
return property.getName();
}
};

const checkPropertyExists = (k, p) => {
const declaration = modelFile.getLocalType(Object.keys(k)[0]);
const property = Object.keys(p)[0];
if(declaration.isMapDeclaration()) {
if (property === 'KEY') {
return true;
} else if(property === 'VALUE') {
return true;
} else {
return false;
}
} else {
return declaration.getOwnProperty(Object.keys(p)[0]);
}
};

const result = {
missingTerms: modelFile.getAllDeclarations().flatMap( d => this.getTerm(d.getName())
? getOwnProperties(d).flatMap( p => this.getTerm(d.getName(), p.getName()) ? null : `${d.getName()}.${p.getName()}`)
? getOwnProperties(d).flatMap( p => this.getTerm(d.getName(), getPropertyName(p)) ? null : `${d.getName()}.${getPropertyName(p)}`)
: d.getName() ).filter( i => i !== null),
additionalTerms: this.content.declarations.flatMap( k => modelFile.getLocalType(Object.keys(k)[0])
? Array.isArray(k.properties) ? k.properties.flatMap( p => modelFile.getLocalType(Object.keys(k)[0]).getOwnProperty(Object.keys(p)[0]) ? null : `${Object.keys(k)[0]}.${Object.keys(p)[0]}`) : null
? Array.isArray(k.properties) ? k.properties.flatMap( p => checkPropertyExists(k, p) ? null : `${Object.keys(k)[0]}.${Object.keys(p)[0]}`) : null
: k ).filter( i => i !== null)
};

Expand Down
35 changes: 29 additions & 6 deletions packages/concerto-vocabulary/lib/vocabularymanager.js
Original file line number Diff line number Diff line change
Expand Up @@ -205,7 +205,18 @@ class VocabularyManager {
resolveTerms(modelManager, namespace, locale, declarationName, propertyName) {
const modelFile = modelManager.getModelFile(namespace);
const classDecl = modelFile ? modelFile.getType(declarationName) : null;
const property = propertyName ? classDecl ? classDecl.getProperty(propertyName) : null : null;
let property;
if(classDecl && !classDecl.isScalarDeclaration()) {
if(classDecl.isMapDeclaration()) {
if(propertyName === 'KEY') {
property = classDecl.getKey();
} else if(propertyName === 'VALUE') {
property = classDecl.getValue();
}
} else {
property = propertyName ? classDecl ? classDecl.getProperty(propertyName) : null : null;
}
}
return this.getTerms(property ? property.getNamespace() : namespace, locale, property ? property.getParent().getName() : declarationName, propertyName);
}

Expand Down Expand Up @@ -286,6 +297,16 @@ class VocabularyManager {
'commands': []
};

const getPropertyNames = (declaration) => {
if (declaration.getProperties) {
return declaration.getProperties().map(property => property.getName());
} else if(declaration.isMapDeclaration?.()) {
return ['KEY', 'VALUE'];
} else {
return [];
}
};

modelManager.getModelFiles().forEach(model => {
model.getAllDeclarations().forEach(decl => {
const terms = this.resolveTerms(modelManager, model.getNamespace(), locale, decl.getName());
Expand Down Expand Up @@ -336,19 +357,21 @@ class VocabularyManager {
});
}

decl.getProperties?.().forEach(property => {
const propertyTerms = this.resolveTerms(modelManager, model.getNamespace(), locale, decl.getName(), property.getName());
const propertyNames = getPropertyNames(decl);
propertyNames.forEach(propertyName => {
const propertyTerms = this.resolveTerms(modelManager, model.getNamespace(), locale, decl.getName(), propertyName);
if (propertyTerms) {
Object.keys(propertyTerms).forEach( term => {
if(term === property.getName()) {
const propertyType = propertyName === 'KEY' || propertyName === 'VALUE' ? 'mapElement' : 'property';
if(term === propertyName) {
decoratorCommandSet.commands.push({
'$class': `${DC_NAMESPACE}.Command`,
'type': 'UPSERT',
'target': {
'$class': `${DC_NAMESPACE}.CommandTarget`,
'namespace': model.getNamespace(),
'declaration': decl.getName(),
'property': property.getName()
[propertyType]: propertyName
},
'decorator': {
'$class': `${MetaModelNamespace}.Decorator`,
Expand All @@ -370,7 +393,7 @@ class VocabularyManager {
'$class': `${DC_NAMESPACE}.CommandTarget`,
'namespace': model.getNamespace(),
'declaration': decl.getName(),
'property': property.getName()
[propertyType]: propertyName
},
'decorator': {
'$class': `${MetaModelNamespace}.Decorator`,
Expand Down

0 comments on commit 53abb6d

Please sign in to comment.