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: vocabulary support for namespace scoped decorators #983

Open
wants to merge 15 commits into
base: main
Choose a base branch
from
Open
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
1 change: 1 addition & 0 deletions packages/concerto-core/api.txt
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ class DecoratorManager {
+ void applyDecorator(decorated,string,newDecorator)
+ void executeCommand(string,declaration,command,property?,object?,boolean?)
+ void executePropertyCommand(property,command)
+ Boolean isNamespaceTargetEnabled(boolean?)
}
+ string[] intersect()
+ boolean isUnversionedNamespaceEqual()
Expand Down
2 changes: 2 additions & 0 deletions packages/concerto-core/changelog.txt
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@
# Note that the latest public API is documented using JSDocs and is available in api.txt.
#

Version 3.20.5 {811a8e795811c539a5f27fb4bed69826} 2025-02-03
- vocabulary support for namespace scoped decorators

Version 3.20.4 {335406876b7005f0a2e6f9ca3e0d0dbf} 2025-01-21
- expose canMigrate and MigrateTo methods from decorator manager
Expand Down
104 changes: 65 additions & 39 deletions packages/concerto-core/lib/decoratorextractor.js
Original file line number Diff line number Diff line change
Expand Up @@ -123,35 +123,49 @@ class DecoratorExtractor {
let strVoc = '';
strVoc = strVoc + `locale: ${this.locale}\n`;
strVoc = strVoc + `namespace: ${namespace}\n`;
strVoc = strVoc + 'declarations:\n';
Object.keys(vocabObject).forEach(decl =>{
if (vocabObject[decl].term){
strVoc += ` - ${decl}: ${vocabObject[decl].term}\n`;
if (vocabObject.namespace && Object.keys(vocabObject.namespace).length > 0 ){
if (vocabObject.namespace.term){
strVoc += `term: ${vocabObject.namespace.term}\n`;
}
const otherProps = Object.keys(vocabObject[decl]).filter((str)=>str !== 'term' && str !== 'propertyVocabs');
//If a declaration does not have any Term decorator, then add Term_ decorators to yaml
if(otherProps.length > 0){
if (!vocabObject[decl].term){
strVoc += ` - ${decl}: ${decl}\n`;
}
otherProps.forEach(key =>{
strVoc += ` ${key}: ${vocabObject[decl][key]}\n`;
});
}
if (vocabObject[decl].propertyVocabs && Object.keys(vocabObject[decl].propertyVocabs).length > 0){
if (!vocabObject[decl].term && otherProps.length === 0){
strVoc += ` - ${decl}: ${decl}\n`;
let otherProps = Object.keys(vocabObject.namespace).filter((str)=>str !== 'term');
otherProps.forEach(key =>{
strVoc += `${key}: ${vocabObject.namespace[key]}\n`;
});
}
if (vocabObject.declarations && Object.keys(vocabObject.declarations).length > 0 ){
strVoc = strVoc + 'declarations:\n';
Object.keys(vocabObject.declarations).forEach(decl =>{
if (vocabObject.declarations[decl].term){
strVoc += ` - ${decl}: ${vocabObject.declarations[decl].term}\n`;
}
strVoc += ' properties:\n';
Object.keys(vocabObject[decl].propertyVocabs).forEach(prop =>{
strVoc += ` - ${prop}: ${vocabObject[decl].propertyVocabs[prop].term || ''}\n`;
const otherProps = Object.keys(vocabObject[decl].propertyVocabs[prop]).filter((str)=>str !== 'term');
const otherProps = Object.keys(vocabObject.declarations[decl]).filter((str)=>str !== 'term' && str !== 'propertyVocabs');
//If a declaration does not have any Term decorator, then add Term_ decorators to yaml
if(otherProps.length > 0){
if (!vocabObject.declarations[decl].term){
strVoc += ` - ${decl}: ${decl}\n`;
}
otherProps.forEach(key =>{
strVoc += ` ${key}: ${vocabObject[decl].propertyVocabs[prop][key]}\n`;
strVoc += ` ${key}: ${vocabObject.declarations[decl][key]}\n`;
});
});
}
});
}
if (vocabObject.declarations[decl].propertyVocabs && Object.keys(vocabObject.declarations[decl].propertyVocabs).length > 0){
if (!vocabObject.declarations[decl].term && otherProps.length === 0){
strVoc += ` - ${decl}: ${decl}\n`;
}
strVoc += ' properties:\n';
Object.keys(vocabObject.declarations[decl].propertyVocabs).forEach(prop =>{
strVoc += ` - ${prop}: ${vocabObject.declarations[decl].propertyVocabs[prop].term || ''}\n`;
const otherProps = Object.keys(vocabObject.declarations[decl].propertyVocabs[prop]).filter((str)=>str !== 'term');
otherProps.forEach(key =>{
strVoc += ` ${key}: ${vocabObject.declarations[decl].propertyVocabs[prop][key]}\n`;
});
});
}
});
}
else{
strVoc = strVoc + 'declarations: []\n';
}
vocabData.push(strVoc);
}
return vocabData;
Expand Down Expand Up @@ -213,48 +227,60 @@ class DecoratorExtractor {
return dcsObjects;
}
/**
* @param {Object} dictVoc - the collection of collected vocabularies
* @param {Object} vocabObject - the collection of collected vocabularies
* @param {Object} decl - the declaration object
* @param {Object} dcs - the current dcs json to be parsed
* @returns {Object} - the collection of collected vocabularies with current dcs
* @private
*/
parseVocabularies(dictVoc, decl, dcs){
dictVoc[decl.declaration] = dictVoc[decl.declaration] || { propertyVocabs: {} };
parseVocabularies(vocabObject, decl, dcs){
Copy link
Member

@sanketshevkar sanketshevkar Feb 18, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is happening here? Its a bit unclear what this method is accomplishing, can you add some description?

The part that is confusing here is that what's relation between decl object and parsing namespace vocabulary?

if(decl.declaration === ''){
vocabObject.namespace = vocabObject.namespace || {};
if (dcs.name === 'Term'){
vocabObject.namespace.term = dcs.arguments[0].value;
}
else {
const extensionKey = dcs.name.split('Term_')[1];
vocabObject.namespace[extensionKey] = dcs.arguments[0].value;
}
return vocabObject;
}
vocabObject.declarations = vocabObject.declarations || {};
vocabObject.declarations[decl.declaration] = vocabObject.declarations[decl.declaration] || { propertyVocabs: {} };
if (decl.property !== ''){
if (!dictVoc[decl.declaration].propertyVocabs[decl.property]){
dictVoc[decl.declaration].propertyVocabs[decl.property] = {};
if (!vocabObject.declarations[decl.declaration].propertyVocabs[decl.property]){
vocabObject.declarations[decl.declaration].propertyVocabs[decl.property] = {};
}
if (dcs.name === 'Term'){
dictVoc[decl.declaration].propertyVocabs[decl.property].term = dcs.arguments[0].value;
vocabObject.declarations[decl.declaration].propertyVocabs[decl.property].term = dcs.arguments[0].value;
}
else {
const extensionKey = dcs.name.split('Term_')[1];
dictVoc[decl.declaration].propertyVocabs[decl.property][extensionKey] = dcs.arguments[0].value;
vocabObject.declarations[decl.declaration].propertyVocabs[decl.property][extensionKey] = dcs.arguments[0].value;
}
}
else if (decl.mapElement !== ''){
if (!dictVoc[decl.declaration].propertyVocabs[decl.mapElement]){
dictVoc[decl.declaration].propertyVocabs[decl.mapElement] = {};
if (!vocabObject.declarations[decl.declaration].propertyVocabs[decl.mapElement]){
vocabObject.declarations[decl.declaration].propertyVocabs[decl.mapElement] = {};
}
if (dcs.name === 'Term'){
dictVoc[decl.declaration].propertyVocabs[decl.mapElement].term = dcs.arguments[0].value;
vocabObject.declarations[decl.declaration].propertyVocabs[decl.mapElement].term = dcs.arguments[0].value;
}
else {
const extensionKey = dcs.name.split('Term_')[1];
dictVoc[decl.declaration].propertyVocabs[decl.mapElement][extensionKey] = dcs.arguments[0].value;
vocabObject.declarations[decl.declaration].propertyVocabs[decl.mapElement][extensionKey] = dcs.arguments[0].value;
}
}
else {
if (dcs.name === 'Term'){
dictVoc[decl.declaration].term = dcs.arguments[0].value;
vocabObject.declarations[decl.declaration].term = dcs.arguments[0].value;
}
else {
const extensionKey = dcs.name.split('Term_')[1];
dictVoc[decl.declaration][extensionKey] = dcs.arguments[0].value;
vocabObject.declarations[decl.declaration][extensionKey] = dcs.arguments[0].value;
}
}
return dictVoc;
return vocabObject;
}
/**
* parses the extracted decorators and generates arrays of decorator command set and vocabularies
Expand Down
1 change: 0 additions & 1 deletion packages/concerto-core/lib/decoratormanager.js
Original file line number Diff line number Diff line change
Expand Up @@ -791,7 +791,6 @@ class DecoratorManager {
/**
* 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
*/
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
["locale: en\nnamespace: [email protected]\nterm: my Namespace\nDesc: my Namespace desc\ndeclarations:\n - mapName: Map Name\n properties:\n - KEY: some key\n desc: some key description\n - Person: Person Class\n desc: Person Class Description\n properties:\n - firstName: HI\n - bio: some\n cus: con\n"]
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
["locale: en\nnamespace: [email protected]\nterm: my Namespace\nDesc: my Namespace desc\ndeclarations: []\n"]
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
@Term("my Namespace")
@Term_Desc("my Namespace desc")
@deco("testing namespace")
namespace [email protected]
@deco
enum Dummy {
@one
o One
}
@scc
scalar SSN extends String default="000-00-0000"

@M1
participant participantName identified by participantKey {
@M3
o String participantKey
}

@Dummy("term1",2)
asset assetName identified by assetKey {
o String assetKey
}

@Term("Map Name")
map mapName {
@Term("some key")
@Term_desc("some key description")
@deco(1)
o String
o String
}

@Term("Person Class")
@Term_desc("Person Class Description")
@Editable
concept Person {
@Term("HI")
@Custom
@Form("inputType", "text")
@New
o String firstName
@term("custom")
@term_desc("custom desc")
@Form("inputType", "text")
@New
o String lastName
@Term("some")
@Term_cus("con")
@Form("inputType", "textArea")
@New
o String bio
@Form("inputType", "text")
@New
o String ssn
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
@Term("my Namespace")
@Term_Desc("my Namespace desc")
namespace [email protected]
@deco
enum Dummy {
@one
o One
}
@scc
scalar SSN extends String default="000-00-0000"

@M1
participant participantName identified by participantKey {
@M3
o String participantKey
}

@Dummy("term1",2)
asset assetName identified by assetKey {
o String assetKey
}

map mapName {
@deco(1)
o String
o String
}

@Editable
concept Person {
@Custom
@Form("inputType", "text")
@New
o String firstName
@Form("inputType", "text")
@New
o String lastName
@Form("inputType", "textArea")
@New
o String bio
@Form("inputType", "text")
@New
o String ssn
}
76 changes: 76 additions & 0 deletions packages/concerto-core/test/decoratormanager.js
Original file line number Diff line number Diff line change
Expand Up @@ -708,6 +708,54 @@ describe('DecoratorManager', () => {
});
sourceCTO.should.be.deep.equal(updatedCTO);
});
it('should ensure that extraction and re-application of decorators and vocabs from a model is an identity operation including namespace terms', async function() {
process.env.ENABLE_DCS_NAMESPACE_TARGET = 'true';
const testModelManager = new ModelManager();
const sourceCTO = [];
const updatedCTO = [];
const modelTextWithoutNamespace = fs.readFileSync(path.join(__dirname,'/data/decoratorcommands/extract-test-with-namespace-term.cto'), 'utf-8');
testModelManager.addCTOModel(modelTextWithoutNamespace, 'test.cto');
const options = {
removeDecoratorsFromModel:true,
locale:'en'
};
const namespaceSource = testModelManager.getNamespaces();
namespaceSource.forEach(name=>{
let model = testModelManager.getModelFile(name);
let modelAst=model.getAst();
let data = Printer.toCTO(modelAst);
sourceCTO.push(data);
});
const resp = DecoratorManager.extractDecorators( testModelManager, options);
const dcs = resp.decoratorCommandSet;
const vocabs= resp.vocabularies;
let newModelManager=resp.modelManager;
const vocManager = new VocabularyManager();
vocabs.forEach(content => {
vocManager.addVocabulary(content);
});
const vocabKeySet=[];
const namespaceUpdated = newModelManager.getNamespaces();
namespaceUpdated.forEach(name=>{
let vocab = vocManager.getVocabulariesForNamespace(name);
vocab.forEach(voc=>vocabKeySet.push(voc.getLocale()));
});
vocabKeySet.map(voc=>{
let commandSet = vocManager.generateDecoratorCommands(newModelManager, voc);
newModelManager = DecoratorManager.decorateModels(newModelManager, commandSet);
});
dcs.forEach(content => {
newModelManager = DecoratorManager.decorateModels(newModelManager, (content));
});
namespaceUpdated.forEach(name=>{
let model = newModelManager.getModelFile(name);
let modelAst=model.getAst();
let data = Printer.toCTO(modelAst);
updatedCTO.push(data);
});
sourceCTO.should.be.deep.equal(updatedCTO);
process.env.ENABLE_DCS_NAMESPACE_TARGET = 'false';
});
it('should give proper response in there is no vocabulary on any model', async function() {
const testModelManager = new ModelManager({strict:true,});
const modelText = fs.readFileSync(path.join(__dirname,'/data/decoratorcommands/model-without-vocab.cto'), 'utf-8');
Expand All @@ -730,6 +778,34 @@ describe('DecoratorManager', () => {
vocab.should.be.deep.equal(JSON.parse(expectedVocabs));
vocab[0].should.not.include('custom');
});
it('should be able to extract vocabs from a model with terms for namespace', async function() {
const testModelManager = new ModelManager({strict:true,});
const modelText = fs.readFileSync(path.join(__dirname,'/data/decoratorcommands/extract-test-with-namespace-term.cto'), 'utf-8');
const expectedVocabs = fs.readFileSync(path.join(__dirname,'/data/decoratorcommands/extract-test-vocab-2.json'), 'utf-8');
testModelManager.addCTOModel(modelText, 'test.cto');
const options = {
removeDecoratorsFromModel:true,
locale:'en'
};
const resp = DecoratorManager.extractVocabularies( testModelManager, options);
const vocab = resp.vocabularies;
vocab.should.be.deep.equal(JSON.parse(expectedVocabs));
vocab[0].should.not.include('custom');
});
it('should be able to extract vocabs from a model with only terms for namespace', async function() {
const testModelManager = new ModelManager({strict:true,});
const modelText = fs.readFileSync(path.join(__dirname,'/data/decoratorcommands/extract-test-with-only-namespace-term.cto'), 'utf-8');
const expectedVocabs = fs.readFileSync(path.join(__dirname,'/data/decoratorcommands/extract-test-vocab-3.json'), 'utf-8');
testModelManager.addCTOModel(modelText, 'test.cto');
const options = {
removeDecoratorsFromModel:true,
locale:'en'
};
const resp = DecoratorManager.extractVocabularies( testModelManager, options);
const vocab = resp.vocabularies;
vocab.should.be.deep.equal(JSON.parse(expectedVocabs));
vocab[0].should.not.include('custom');
});
it('should be able to extract non-vocab decorators from a model', async function() {
const testModelManager = new ModelManager({strict:true,});
const modelText = fs.readFileSync(path.join(__dirname,'/data/decoratorcommands/extract-test.cto'), 'utf-8');
Expand Down
2 changes: 1 addition & 1 deletion packages/concerto-core/types/lib/decoratorextractor.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ declare class DecoratorExtractor {
*/
private parseNonVocabularyDecorators;
/**
* @param {Object} dictVoc - the collection of collected vocabularies
* @param {Object} vocabObject - the collection of collected vocabularies
* @param {Object} decl - the declaration object
* @param {Object} dcs - the current dcs json to be parsed
* @returns {Object} - the collection of collected vocabularies with current dcs
Expand Down
3 changes: 1 addition & 2 deletions packages/concerto-core/types/lib/decoratormanager.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -249,11 +249,10 @@ declare class DecoratorManager {
/**
* 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
*/
private static isNamespaceTargetEnabled;
static isNamespaceTargetEnabled(enableDcsNamespaceTarget?: boolean): boolean;
}
import ModelFile = require("./introspect/modelfile");
import ModelManager = require("./modelmanager");
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,6 @@ export interface IRange extends IConcept {

export interface ITypeIdentifier extends IConcept {
name: string;
resolvedName?: string;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you are not working on the latest main branch of concerto.

namespace?: string;
}

Expand Down
Loading
Loading