Skip to content

Commit 603fb18

Browse files
fix(concerto-core): add opt-out for reserved system type name clashes (#1215)
* fix(concerto-core): add opt-out for reserved system type name clashes Signed-off-by: muhammed-abdulkadir <muhammed.abdulkadir@docusign.com> * fix(concerto-core): detect reserved system-type clashes via resolved import metadata Signed-off-by: muhammed-abdulkadir <muhammed.abdulkadir@docusign.com> * refactor(*): rename dangerouslyAllowReservedSystemTypeNames to dangerouslyAllowReservedSystemTypeNamesInUserModels Signed-off-by: muhammed-abdulkadir <muhammed.abdulkadir@docusign.com> --------- Signed-off-by: muhammed-abdulkadir <muhammed.abdulkadir@docusign.com> Co-authored-by: muhammed-abdulkadir <muhammed.abdulkadir@docusign.com>
1 parent e89ae92 commit 603fb18

5 files changed

Lines changed: 80 additions & 3 deletions

File tree

packages/concerto-core/src/basemodelmanager.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -93,17 +93,18 @@ class BaseModelManager {
9393
options: any;
9494
decoratorValidation: any;
9595
metamodelModelFile: any;
96-
/**
96+
/**
9797
* Create the ModelManager.
9898
* @constructor
9999
* @param {object} [options] - ModelManager options, also passed to Serializer
100100
* @param {Object} [options.regExp] - An alternative regular expression engine.
101101
* @param {boolean} [options.metamodelValidation] - When true, modelfiles will be validated
102102
* @param {boolean} [options.addMetamodel] - When true, the Concerto metamodel is added to the model manager
103+
* @param {boolean} [options.dangerouslyAllowReservedSystemTypeNamesInUserModels] - Transitional escape hatch; when true, declarations may use reserved system type names
103104
* @param {object} [options.decoratorValidation] - the decorator validation configuration
104105
* @param {string} [options.decoratorValidation.missingDecorator] - the validation log level for missingDecorator decorators: off, warning, error
105106
* @param {string} [options.decoratorValidation.invalidDecorator] - the validation log level for invalidDecorator decorators: off, warning, error
106-
* @param {*} [processFile] - how to obtain a concerto AST from an input to the model manager
107+
* @param {*} [processFile] - how to obtain a concerto AST from an input to the model manager
107108
*/
108109
constructor(options?: ModelManagerOptions, processFile?: (fileName: string | null, modelInput: string | unknown) => ModelFileSource) {
109110
this.processFile = processFile ? processFile : defaultProcessFile;

packages/concerto-core/src/introspect/declaration.ts

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,13 +80,44 @@ class Declaration extends Decorated {
8080
*/
8181
validate(...args) {
8282
super.validate(...args);
83+
const modelFile = this.getModelFile();
8384

8485
// #648 - check for clashes against imported types
85-
if (this.getModelFile().isImportedType(this.getName())){
86+
if (modelFile.isImportedType(this.getName())) {
87+
const dangerouslyAllowReservedSystemTypeNamesInUserModels = Boolean(modelFile.getModelManager()?.options?.dangerouslyAllowReservedSystemTypeNamesInUserModels);
88+
if (dangerouslyAllowReservedSystemTypeNamesInUserModels && this.isReservedSystemTypeImport(modelFile, this.getName())) {
89+
return;
90+
}
91+
8692
throw new IllegalModelException(`Type '${this.getName()}' clashes with an imported type with the same name.`, this.modelFile, this.ast.location);
8793
}
8894
}
8995

96+
/**
97+
* Determines whether a type name resolves to a reserved type in the Concerto
98+
* system namespace.
99+
* @param {ModelFile} modelFile - the current model file
100+
* @param {string} typeName - local/imported type name
101+
* @returns {boolean} true if the resolved import is a reserved system type
102+
*/
103+
private isReservedSystemTypeImport(modelFile, typeName) {
104+
const importedType = modelFile.getType(typeName);
105+
if (!importedType || typeof importedType === 'string') {
106+
return false;
107+
}
108+
109+
const importedModelFile = importedType.getModelFile();
110+
if (!importedModelFile || !importedModelFile.isSystemModelFile()) {
111+
return false;
112+
}
113+
114+
return importedType.isConcept()
115+
|| importedType.isAsset()
116+
|| importedType.isTransaction()
117+
|| importedType.isParticipant()
118+
|| importedType.isEvent();
119+
}
120+
90121
/**
91122
* Returns the ModelFile that defines this class.
92123
*

packages/concerto-core/src/modelmanager.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ class ModelManager extends BaseModelManager {
6161
* @constructor
6262
* @param {object} [options] - ModelManager options, also passed to Serializer
6363
* @param {Object} [options.regExp] - An alternative regular expression engine.
64+
* @param {boolean} [options.dangerouslyAllowReservedSystemTypeNamesInUserModels] - Transitional escape hatch; when true, declarations may use reserved system type names
6465
*/
6566
constructor(options: ModelManagerOptions = {}) {
6667
super(options, ctoProcessFile(options));

packages/concerto-core/src/types.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,9 @@ export interface ModelManagerOptions {
1818
regExp?: RegExp;
1919
metamodelValidation?: boolean;
2020
addMetamodel?: boolean;
21+
// Transitional migration escape hatch for legacy models.
22+
// This option is temporary and will be removed in a future release.
23+
dangerouslyAllowReservedSystemTypeNamesInUserModels?: boolean;
2124
decoratorValidation?: {
2225
missingDecorator?: string;
2326
invalidDecorator?: string;

packages/concerto-core/test/introspect/modelfile.js

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -367,6 +367,47 @@ describe('ModelFile', () => {
367367
}).should.throw('Type \'Transaction\' clashes with an imported type with the same name.');
368368
});
369369

370+
it('should allow a system type name when dangerouslyAllowReservedSystemTypeNamesInUserModels is enabled', () => {
371+
const myModelManager = new ModelManager({
372+
dangerouslyAllowReservedSystemTypeNamesInUserModels: true,
373+
});
374+
375+
const model = `
376+
namespace A@1.0.0
377+
asset Asset identified by assetId {
378+
o String assetId
379+
}
380+
`;
381+
382+
const modelFile = ParserUtil.newModelFile(myModelManager, model);
383+
myModelManager.addModelFile(modelFile);
384+
myModelManager.getType('A@1.0.0.Asset').getName().should.equal('Asset');
385+
});
386+
387+
it('should still fail non-system clashes when dangerouslyAllowReservedSystemTypeNamesInUserModels is enabled', () => {
388+
const myModelManager = new ModelManager({
389+
dangerouslyAllowReservedSystemTypeNamesInUserModels: true,
390+
});
391+
392+
const imported = `namespace A@1.0.0
393+
concept B {
394+
o String name
395+
}`;
396+
397+
const clashing = `namespace B@1.0.0
398+
import A@1.0.0.{B}
399+
concept B {
400+
}`;
401+
402+
const importedModelFile = ParserUtil.newModelFile(myModelManager, imported);
403+
myModelManager.addModelFile(importedModelFile);
404+
405+
const clashingModelFile = ParserUtil.newModelFile(myModelManager, clashing);
406+
(() => {
407+
myModelManager.addModelFile(clashingModelFile);
408+
}).should.throw('Type \'B\' clashes with an imported type with the same name.');
409+
});
410+
370411
});
371412

372413
describe('#getDefinitions', () => {

0 commit comments

Comments
 (0)