diff --git a/package-lock.json b/package-lock.json index 60c9bde904..fbe7da3fe4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -17983,6 +17983,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/safe-stable-stringify": { + "version": "2.4.3", + "resolved": "https://registry.npmjs.org/safe-stable-stringify/-/safe-stable-stringify-2.4.3.tgz", + "integrity": "sha512-e2bDA2WJT0wxseVd4lsDP4+3ONX6HpMXQa1ZhFQ7SU+GjvORCmShbCMltrtIDfkYhVHrOcPtj+KhmDBdPdZD1g==", + "dev": true, + "engines": { + "node": ">=10" + } + }, "node_modules/safer-buffer": { "version": "2.1.2", "dev": true, @@ -19177,6 +19186,89 @@ } } }, + "node_modules/ts-json-schema-generator": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/ts-json-schema-generator/-/ts-json-schema-generator-1.4.0.tgz", + "integrity": "sha512-wm8vyihmGgYpxrqRshmYkWGNwEk+sf3xV2rUgxv8Ryeh7bSpMO7pZQOht+2rS002eDkFTxR7EwRPXVzrS0WJTg==", + "dev": true, + "dependencies": { + "@types/json-schema": "^7.0.12", + "commander": "^11.0.0", + "glob": "^8.0.3", + "json5": "^2.2.3", + "normalize-path": "^3.0.0", + "safe-stable-stringify": "^2.4.3", + "typescript": "~5.2.2" + }, + "bin": { + "ts-json-schema-generator": "bin/ts-json-schema-generator" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/ts-json-schema-generator/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/ts-json-schema-generator/node_modules/commander": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-11.1.0.tgz", + "integrity": "sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ==", + "dev": true, + "engines": { + "node": ">=16" + } + }, + "node_modules/ts-json-schema-generator/node_modules/glob": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", + "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^5.0.1", + "once": "^1.3.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/ts-json-schema-generator/node_modules/minimatch": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", + "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/ts-json-schema-generator/node_modules/typescript": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.2.2.tgz", + "integrity": "sha512-mI4WrpHsbCIcwT9cF4FZvr80QUeKvsUsUvKDoR+X/7XHQH98xYD8YHZg7ANtz2GtZt/CBq2QJ0thkGJMHfqc1w==", + "dev": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, "node_modules/ts-node": { "version": "10.9.1", "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.1.tgz", @@ -20628,9 +20720,12 @@ "@accordproject/concerto-metamodel": "3.8.1", "@accordproject/concerto-util": "^4.0.0-alpha.0", "eslint": "8.2.0", + "jest": "28.1.1", "license-check-and-add": "2.3.6", "npm-run-all": "4.1.5", "rimraf": "3.0.2", + "ts-jest": "28.0.4", + "ts-json-schema-generator": "1.4.0", "typescript": "4.6.3" }, "engines": { diff --git a/packages/concerto-core/src/introspect/classdeclaration.js b/packages/concerto-core/src/introspect/classdeclaration.js index c74771593f..0499007d27 100644 --- a/packages/concerto-core/src/introspect/classdeclaration.js +++ b/packages/concerto-core/src/introspect/classdeclaration.js @@ -93,6 +93,13 @@ class ClassDeclaration extends Declaration { } } + if (!Array.isArray(this.ast.properties)) { + let formatter = Globalize.messageFormatter('classdeclaration-validate-undefined-properties'); + throw new IllegalModelException(formatter({ + 'class':this.name + }), this.modelFile, this.ast.location); + } + for (let n = 0; n < this.ast.properties.length; n++) { let thing = this.ast.properties[n]; @@ -194,23 +201,21 @@ class ClassDeclaration extends Declaration { validate() { super.validate(); - const declarations = this.getModelFile().getAllDeclarations(); - const declarationNames = declarations.map( - d => d.getFullyQualifiedName() - ); - const uniqueNames = new Set(declarationNames); - - if (uniqueNames.size !== declarations.length) { - const duplicateElements = declarationNames.filter( - (item, index) => declarationNames.indexOf(item) !== index - ); - throw new IllegalModelException( - `Duplicate class name ${duplicateElements[0]}` - ); - } - // if we have a super type make sure it exists if (this.superType !== null) { + // and make sure that the class isn't extending itself + // (an exemption is made for the core classes) + if ( + this.superType === this.name && + ![ + 'Asset', 'Concept', 'Event', 'Participant', 'Transaction', + ].includes(this.superType) + ) { + let formatter = Globalize('en').messageFormatter('classdeclaration-validate-selfextending'); + throw new IllegalModelException(formatter({ + 'class': this.name, + }), this.modelFile, this.ast.location); + } this._resolveSuperType(); } @@ -269,24 +274,21 @@ class ClassDeclaration extends Declaration { } // we also have to check fields defined in super classes const properties = this.getProperties(); - const propertyFieldNames = properties.map( - d => d.getName() - ); - const uniquePropertyFieldNames = new Set(propertyFieldNames); - - if (uniquePropertyFieldNames.size !== properties.length) { - const duplicateElements = propertyFieldNames - .filter( - (item, index) => propertyFieldNames.indexOf(item) !== index + const uniquePropertyNames = new Set(); + properties.forEach(p => { + const propertyName = p.getName(); + if (!uniquePropertyNames.has(propertyName)) { + uniquePropertyNames.add(propertyName); + } else { + const formatter = Globalize('en').messageFormatter( + 'classdeclaration-validate-duplicatefieldname' ); - const formatter = Globalize('en').messageFormatter( - 'classdeclaration-validate-duplicatefieldname' - ); - throw new IllegalModelException(formatter({ - 'class': this.name, - 'fieldName': duplicateElements[0] - }), this.modelFile, this.ast.location); - } + throw new IllegalModelException(formatter({ + 'class': this.name, + 'fieldName': propertyName + }), this.modelFile, this.ast.location); + } + }); for (let n = 0; n < properties.length; n++) { let field = properties[n]; diff --git a/packages/concerto-core/src/introspect/decorated.js b/packages/concerto-core/src/introspect/decorated.js index 85311e1072..05f6b3b207 100644 --- a/packages/concerto-core/src/introspect/decorated.js +++ b/packages/concerto-core/src/introspect/decorated.js @@ -89,23 +89,20 @@ class Decorated extends ModelElement { } // check we don't have this decorator twice - const decoratorNames = this.decorators.map( - d => d.getName() - ); - const uniqueDecoratorNames = new Set(decoratorNames); - - if (uniqueDecoratorNames.size !== this.decorators.length) { - const duplicateElements = decoratorNames - .filter( - (item, index) => decoratorNames.indexOf(item) !== index + const uniqueDecoratorNames = new Set(); + this.decorators.forEach(d => { + const decoratorName = d.getName(); + if(!uniqueDecoratorNames.has(decoratorName)) { + uniqueDecoratorNames.add(decoratorName); + } else { + const modelFile = this.getModelFile(); + throw new IllegalModelException( + `Duplicate decorator ${decoratorName}`, + modelFile, + this.ast.location, ); - const modelFile = this.getModelFile(); - throw new IllegalModelException( - `Duplicate decorator ${duplicateElements[0]}`, - modelFile, - this.ast.location, - ); - } + } + }); } } diff --git a/packages/concerto-core/src/introspect/modelfile.js b/packages/concerto-core/src/introspect/modelfile.js index 1bff4ea165..9cc8413acd 100644 --- a/packages/concerto-core/src/introspect/modelfile.js +++ b/packages/concerto-core/src/introspect/modelfile.js @@ -281,6 +281,22 @@ class ModelFile { }); // Validate all of the types in this model file. + // Check if names of the declarations are unique. + const uniqueNames = new Set(); + this.declarations.forEach( + d => { + const fqn = d.getFullyQualifiedName(); + if (!uniqueNames.has(fqn)) { + uniqueNames.add(fqn); + } else { + throw new IllegalModelException( + `Duplicate class name ${fqn}` + ); + } + } + ); + + // Run validations on class declarations for(let n=0; n < this.declarations.length; n++) { let classDeclaration = this.declarations[n]; classDeclaration.validate(); diff --git a/packages/concerto-core/src/introspect/stringvalidator.js b/packages/concerto-core/src/introspect/stringvalidator.js index 290b725ca0..b3c6db8a39 100644 --- a/packages/concerto-core/src/introspect/stringvalidator.js +++ b/packages/concerto-core/src/introspect/stringvalidator.js @@ -14,6 +14,7 @@ 'use strict'; +const { ErrorCodes } = require('@accordproject/concerto-util'); const { isNull } = require('../util'); const Validator = require('./validator'); @@ -70,7 +71,7 @@ class StringValidator extends Validator{ this.regex = new CustomRegExp(validator.pattern, validator.flags); } catch(exception) { - this.reportError(field.getName(), exception.message); + this.reportError(field.getName(), exception.message, ErrorCodes.REGEX_VALIDATOR_EXCEPTION); } } } diff --git a/packages/concerto-core/src/introspect/validator.js b/packages/concerto-core/src/introspect/validator.js index 9bf0b0ac07..3f790e6f36 100644 --- a/packages/concerto-core/src/introspect/validator.js +++ b/packages/concerto-core/src/introspect/validator.js @@ -14,6 +14,7 @@ 'use strict'; +const { BaseException, ErrorCodes } = require('@accordproject/concerto-util'); // Types needed for TypeScript generation. /* eslint-disable no-unused-vars */ /* istanbul ignore next */ @@ -46,10 +47,11 @@ class Validator { /** * @param {string} id the identifier of the instance * @param {string} msg the exception message + * @param {string} errorType the type of error * @throws {Error} throws an error to report the message */ - reportError(id, msg) { - throw new Error( 'Validator error for field `' + id + '`. ' + this.getFieldOrScalarDeclaration().getFullyQualifiedName() + ': ' + msg ); + reportError(id, msg, errorType=ErrorCodes.DEFAULT_VALIDATOR_EXCEPTION) { + throw new BaseException('Validator error for field `' + id + '`. ' + this.getFieldOrScalarDeclaration().getFullyQualifiedName() + ': ' + msg, undefined, errorType); } /** diff --git a/packages/concerto-core/src/messages/en.json b/packages/concerto-core/src/messages/en.json index f1bb58d299..4fb07782ab 100644 --- a/packages/concerto-core/src/messages/en.json +++ b/packages/concerto-core/src/messages/en.json @@ -20,6 +20,8 @@ "classdeclaration-validate-identifiernotstring": "Class \"{class}\" is identified by field \"{idField}\", but the type of the field is not \"String\".", "classdeclaration-validate-duplicatefieldname": "Class \"{class}\" has more than one field named \"{fieldName}\".", "classdeclaration-validate-missingidentifier" : "Class \"{class}\" is not declared as \"abstract\". It must define an identifying field.", + "classdeclaration-validate-selfextending": "Class \"{class}\" cannot extend itself.", + "classdeclaration-validate-undefined-properties": "Properties of Class \"{class}\" has to be defined.", "modelfile-constructor-unrecmodelelem": "Unrecognised model element \"{type}\".", "modelfile-resolvetype-undecltype": "Undeclared type \"{type}\" in \"{context}\".", diff --git a/packages/concerto-core/src/typenotfoundexception.js b/packages/concerto-core/src/typenotfoundexception.js index d478182abd..29f21de097 100644 --- a/packages/concerto-core/src/typenotfoundexception.js +++ b/packages/concerto-core/src/typenotfoundexception.js @@ -14,7 +14,7 @@ 'use strict'; -const { BaseException } = require('@accordproject/concerto-util'); +const { BaseException, ErrorCodes } = require('@accordproject/concerto-util'); const Globalize = require('./globalize'); /** @@ -31,8 +31,9 @@ class TypeNotFoundException extends BaseException { * @param {string} typeName - fully qualified type name. * @param {string|undefined} message - error message. * @param {string} component - the optional component which throws this error + * @param {string} errorType - the error code related to the error */ - constructor(typeName, message, component) { + constructor(typeName, message, component, errorType = ErrorCodes.TYPE_NOT_FOUND_EXCEPTION) { if (!message) { const formatter = Globalize.messageFormatter('typenotfounderror-defaultmessage'); message = formatter({ @@ -40,7 +41,7 @@ class TypeNotFoundException extends BaseException { }); } - super(message, component); + super(message, component, errorType); this.typeName = typeName; } diff --git a/packages/concerto-core/test/introspect/classdeclaration.js b/packages/concerto-core/test/introspect/classdeclaration.js index b1a867e466..ccfb9af7b7 100644 --- a/packages/concerto-core/test/introspect/classdeclaration.js +++ b/packages/concerto-core/test/introspect/classdeclaration.js @@ -79,7 +79,6 @@ describe('ClassDeclaration', () => { }); }).should.throw(/Invalid model element name '2nd'/); }); - }); describe('#validate', () => { diff --git a/packages/concerto-core/test/introspect/modelfile.js b/packages/concerto-core/test/introspect/modelfile.js index 469af7d5d9..65de1669b4 100644 --- a/packages/concerto-core/test/introspect/modelfile.js +++ b/packages/concerto-core/test/introspect/modelfile.js @@ -29,6 +29,7 @@ const fs = require('fs'); const path = require('path'); const Util = require('../composer/composermodelutility'); const ParserUtil = require('./parserutility'); +const IntrospectUtils = require('./introspectutils'); const { Parser } = require('@accordproject/concerto-cto'); @@ -44,10 +45,12 @@ describe('ModelFile', () => { const carLeaseModel = fs.readFileSync(path.resolve(__dirname, '../data/model/carlease.cto'), 'utf8'); let modelManager; let sandbox; + let introspectUtils; beforeEach(() => { modelManager = new ModelManager(); Util.addComposerModel(modelManager); + introspectUtils = new IntrospectUtils(modelManager); sandbox = sinon.createSandbox(); }); @@ -204,6 +207,48 @@ describe('ModelFile', () => { describe('#validate', () => { + it('should throw when scalar name is duplicted in a modelfile', () => { + let asset = introspectUtils.loadModelFile('test/data/parser/scalardeclaration.dupeboolean.cto'); + (() => { + asset.validate(); + }).should.throw(/Duplicate class/); + }); + + it('should throw when asset name is duplicted in a modelfile', () => { + let asset = introspectUtils.loadModelFile('test/data/parser/classdeclaration.dupeassetname.cto'); + (() => { + asset.validate(); + }).should.throw(/Duplicate class/); + }); + + it('should throw when transaction name is duplicted in a modelfile', () => { + let asset = introspectUtils.loadModelFile('test/data/parser/classdeclaration.dupetransactionname.cto'); + (() => { + asset.validate(); + }).should.throw(/Duplicate class/); + }); + + it('should throw when participant name is duplicted in a modelfile', () => { + let asset = introspectUtils.loadModelFile('test/data/parser/classdeclaration.dupeparticipantname.cto'); + (() => { + asset.validate(); + }).should.throw(/Duplicate class/); + }); + + it('should throw when concept name is duplicted in a modelfile', () => { + let asset = introspectUtils.loadModelFile('test/data/parser/classdeclaration.dupeconceptname.cto'); + (() => { + asset.validate(); + }).should.throw(/Duplicate class/); + }); + + it('should throw when enum name is duplicted in a modelfile', () => { + let asset = introspectUtils.loadModelFile('test/data/parser/classdeclaration.dupeenumname.cto'); + (() => { + asset.validate(); + }).should.throw(/Duplicate class/); + }); + it('should throw if an import exists for an invalid namespace', () => { const model = ` namespace org.acme@1.0.0 diff --git a/packages/concerto-cto/src/parser.js b/packages/concerto-cto/src/parser.js index 5dddcdf1a8..0aed6c222e 100644 --- a/packages/concerto-cto/src/parser.js +++ b/packages/concerto-cto/src/parser.js @@ -624,6 +624,9 @@ function peg$parse(input, options) { ...buildRange(location()) }; if (classExtension) { + if (classExtension.name === id.name) { + throw new Error(`The asset "${id.name}" cannot extend itself.`); + } result.superType = classExtension; } if (idField) { @@ -643,6 +646,9 @@ function peg$parse(input, options) { ...buildRange(location()) }; if (classExtension) { + if (classExtension.name === id.name) { + throw new Error(`The participant "${id.name}" cannot extend itself.`); + } result.superType = classExtension; } if (idField) { @@ -662,6 +668,9 @@ function peg$parse(input, options) { ...buildRange(location()) }; if (classExtension) { + if (classExtension.name === id.name) { + throw new Error(`The transaction "${id.name}" cannot extend itself.`); + } result.superType = classExtension; } if (idField) { @@ -681,6 +690,9 @@ function peg$parse(input, options) { ...buildRange(location()) }; if (classExtension) { + if (classExtension.name === id.name) { + throw new Error(`The event "${id.name}" cannot extend itself.`); + } result.superType = classExtension; } if (idField) { @@ -700,6 +712,9 @@ function peg$parse(input, options) { ...buildRange(location()) }; if (classExtension) { + if (classExtension.name === id.name) { + throw new Error(`The concept "${id.name}" cannot extend itself.`); + } result.superType = classExtension; } if (idField) { diff --git a/packages/concerto-cto/src/parser.pegjs b/packages/concerto-cto/src/parser.pegjs index b549c4f23f..bf4c22f8e3 100644 --- a/packages/concerto-cto/src/parser.pegjs +++ b/packages/concerto-cto/src/parser.pegjs @@ -966,6 +966,9 @@ AssetDeclaration ...buildRange(location()) }; if (classExtension) { + if (classExtension.name === id.name) { + throw new Error(`The asset "${id.name}" cannot extend itself.`); + } result.superType = classExtension; } if (idField) { @@ -989,6 +992,9 @@ ParticipantDeclaration ...buildRange(location()) }; if (classExtension) { + if (classExtension.name === id.name) { + throw new Error(`The participant "${id.name}" cannot extend itself.`); + } result.superType = classExtension; } if (idField) { @@ -1012,6 +1018,9 @@ TransactionDeclaration ...buildRange(location()) }; if (classExtension) { + if (classExtension.name === id.name) { + throw new Error(`The transaction "${id.name}" cannot extend itself.`); + } result.superType = classExtension; } if (idField) { @@ -1035,6 +1044,9 @@ EventDeclaration ...buildRange(location()) }; if (classExtension) { + if (classExtension.name === id.name) { + throw new Error(`The event "${id.name}" cannot extend itself.`); + } result.superType = classExtension; } if (idField) { @@ -1058,6 +1070,9 @@ ConceptDeclaration ...buildRange(location()) }; if (classExtension) { + if (classExtension.name === id.name) { + throw new Error(`The concept "${id.name}" cannot extend itself.`); + } result.superType = classExtension; } if (idField) { diff --git a/packages/concerto-cto/src/printer.js b/packages/concerto-cto/src/printer.js index c895e8a286..c6aacfe4b5 100644 --- a/packages/concerto-cto/src/printer.js +++ b/packages/concerto-cto/src/printer.js @@ -343,6 +343,9 @@ function declFromMetaModel(mm) { } } if (mm.superType) { + if (mm.superType.name === mm.name) { + throw new Error(`The declaration "${mm.name}" cannot extend itself.`); + } result += `extends ${mm.superType.name} `; } result += '{'; diff --git a/packages/concerto-cto/test/cto/bad/self-extending-asset.bad.cto b/packages/concerto-cto/test/cto/bad/self-extending-asset.bad.cto new file mode 100644 index 0000000000..504d32c1ce --- /dev/null +++ b/packages/concerto-cto/test/cto/bad/self-extending-asset.bad.cto @@ -0,0 +1,5 @@ +namespace com.acme@1.0.0 + +asset Self_Extending extends Self_Extending { + o String foo optional +} diff --git a/packages/concerto-cto/test/cto/bad/self-extending-concept.bad.cto b/packages/concerto-cto/test/cto/bad/self-extending-concept.bad.cto new file mode 100644 index 0000000000..09ee02c28b --- /dev/null +++ b/packages/concerto-cto/test/cto/bad/self-extending-concept.bad.cto @@ -0,0 +1,5 @@ +namespace com.acme@1.0.0 + +concept Self_Extending extends Self_Extending { + o String foo optional +} diff --git a/packages/concerto-cto/test/cto/bad/self-extending-event.bad.cto b/packages/concerto-cto/test/cto/bad/self-extending-event.bad.cto new file mode 100644 index 0000000000..1905ad5be1 --- /dev/null +++ b/packages/concerto-cto/test/cto/bad/self-extending-event.bad.cto @@ -0,0 +1,5 @@ +namespace com.acme@1.0.0 + +event Self_Extending extends Self_Extending { + o String foo optional +} diff --git a/packages/concerto-cto/test/cto/bad/self-extending-participant.bad.cto b/packages/concerto-cto/test/cto/bad/self-extending-participant.bad.cto new file mode 100644 index 0000000000..58ec17ede4 --- /dev/null +++ b/packages/concerto-cto/test/cto/bad/self-extending-participant.bad.cto @@ -0,0 +1,5 @@ +namespace com.acme@1.0.0 + +participant Self_Extending extends Self_Extending { + o String foo optional +} diff --git a/packages/concerto-cto/test/cto/bad/self-extending-transaction.bad.cto b/packages/concerto-cto/test/cto/bad/self-extending-transaction.bad.cto new file mode 100644 index 0000000000..e87064a98b --- /dev/null +++ b/packages/concerto-cto/test/cto/bad/self-extending-transaction.bad.cto @@ -0,0 +1,5 @@ +namespace com.acme@1.0.0 + +transaction Self_Extending extends Self_Extending { + o String foo optional +} diff --git a/packages/concerto-cto/test/parserMain.js b/packages/concerto-cto/test/parserMain.js index d430c550ae..e9d1d3796f 100644 --- a/packages/concerto-cto/test/parserMain.js +++ b/packages/concerto-cto/test/parserMain.js @@ -81,6 +81,24 @@ describe('parser', () => { }); }); + describe('self-extending', () => { + const declarationTypes = [ + 'asset', + 'participant', + 'transaction', + 'event', + 'concept', + ]; + declarationTypes.forEach(declarationType => { + it(`Should not parse a self-extending ${declarationType}`, () => { + let content = fs.readFileSync(`./test/cto/bad/self-extending-${declarationType}.bad.cto`, 'utf8'); + (() => { + Parser.parse(content); + }).should.throw(new RegExp(`The ${declarationType} ".+" cannot extend itself.`)); + }); + }); + }); + describe('identifiers', () => { const acceptedIdentifiers = [ diff --git a/packages/concerto-cto/test/printer.js b/packages/concerto-cto/test/printer.js index 60ecf8afd7..b3529ce5f4 100644 --- a/packages/concerto-cto/test/printer.js +++ b/packages/concerto-cto/test/printer.js @@ -62,4 +62,30 @@ describe('parser', () => { declarations: [], })).should.throw(Error, 'Unrecognized import'); }); + + it('Should throw error for a self-extending declaration', () => { + (() => Printer.toCTO({ + '$class': 'concerto.metamodel@1.0.0.Model', + 'namespace': 'com.acme@1.0.0', + 'declarations': [ + { + '$class': 'concerto.metamodel@1.0.0.AssetDeclaration', + 'name': 'Self_Extending', + 'isAbstract': false, + 'properties': [ + { + '$class': 'concerto.metamodel@1.0.0.StringProperty', + 'name': 'foo', + 'isArray': false, + 'isOptional': true + } + ], + 'superType': { + '$class': 'concerto.metamodel@1.0.0.TypeIdentifier', + 'name': 'Self_Extending' + } + } + ] + })).should.throw(Error, 'The declaration "Self_Extending" cannot extend itself.'); + }); }); diff --git a/packages/concerto-util/src/baseexception.js b/packages/concerto-util/src/baseexception.js index cb82e199fe..ec71cc5e42 100644 --- a/packages/concerto-util/src/baseexception.js +++ b/packages/concerto-util/src/baseexception.js @@ -14,6 +14,8 @@ 'use strict'; +const ErrorCodes = require('./errorcodes'); + /** * A base class for all Concerto exceptions * @extends Error @@ -25,12 +27,14 @@ class BaseException extends Error { * Create the BaseException. * @param {string} message - The exception message. * @param {string} component - The optional component which throws this error. + * @param {string} errorType - The optional error code regarding the error */ - constructor(message, component) { + constructor(message, component, errorType) { super(message); this.component = component || process.env.npm_package_name; this.name = this.constructor.name; this.message = message; + this.errorType = errorType || ErrorCodes.DEFAULT_BASE_EXCEPTION; if (typeof Error.captureStackTrace === 'function') { Error.captureStackTrace(this, this.constructor); } diff --git a/packages/concerto-util/src/errorcodes.js b/packages/concerto-util/src/errorcodes.js new file mode 100644 index 0000000000..45b9c9d830 --- /dev/null +++ b/packages/concerto-util/src/errorcodes.js @@ -0,0 +1,26 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +'use strict'; + +//default base exception +const DEFAULT_BASE_EXCEPTION = 'DefaultBaseException'; +//default validator exception which is being used when there is no specified validator exception in introspect +const DEFAULT_VALIDATOR_EXCEPTION = 'DefaultValidatorException'; +// exception code for regex validator format error +const REGEX_VALIDATOR_EXCEPTION = 'RegexValidatorException'; +// base exception for Type not found +const TYPE_NOT_FOUND_EXCEPTION = 'TypeNotFoundException'; + +module.exports = {DEFAULT_BASE_EXCEPTION, DEFAULT_VALIDATOR_EXCEPTION, REGEX_VALIDATOR_EXCEPTION, TYPE_NOT_FOUND_EXCEPTION}; diff --git a/packages/concerto-util/src/index.ts b/packages/concerto-util/src/index.ts index b08738b79e..98459718b0 100644 --- a/packages/concerto-util/src/index.ts +++ b/packages/concerto-util/src/index.ts @@ -52,6 +52,9 @@ const Label = require('./label'); // Identifiers const Identifiers = require('./identifiers'); +//errorcodes +const ErrorCodes = require('./errorcodes'); + module.exports = { BaseException, BaseFileException, @@ -67,5 +70,6 @@ module.exports = { Logger, TypedStack, Label, - Identifiers + Identifiers, + ErrorCodes }; diff --git a/packages/concerto-util/types/index.d.ts b/packages/concerto-util/types/index.d.ts index 3d3d7a0248..e87904ac9a 100644 --- a/packages/concerto-util/types/index.d.ts +++ b/packages/concerto-util/types/index.d.ts @@ -13,4 +13,5 @@ import Logger = require("./lib/logger"); import TypedStack = require("./lib/typedstack"); import Label = require("./lib/label"); import Identifiers = require("./lib/identifiers"); -export { BaseException, BaseFileException, FileDownloader, CompositeFileLoader, DefaultFileLoader, GitHubFileLoader, HTTPFileLoader, Writer, FileWriter, InMemoryWriter, ModelWriter, Logger, TypedStack, Label, Identifiers }; +import ErrorCodes = require("./lib/errorcodes"); +export { BaseException, BaseFileException, FileDownloader, CompositeFileLoader, DefaultFileLoader, GitHubFileLoader, HTTPFileLoader, Writer, FileWriter, InMemoryWriter, ModelWriter, Logger, TypedStack, Label, Identifiers, ErrorCodes }; diff --git a/packages/concerto-util/types/lib/baseexception.d.ts b/packages/concerto-util/types/lib/baseexception.d.ts index bdc321df80..f6f5cadd8d 100644 --- a/packages/concerto-util/types/lib/baseexception.d.ts +++ b/packages/concerto-util/types/lib/baseexception.d.ts @@ -10,7 +10,9 @@ declare class BaseException extends Error { * Create the BaseException. * @param {string} message - The exception message. * @param {string} component - The optional component which throws this error. + * @param {string} errorType - The optional error code regarding the error */ - constructor(message: string, component: string); + constructor(message: string, component: string, errorType: string); component: any; + errorType: string; } diff --git a/packages/concerto-util/types/lib/errorcodes.d.ts b/packages/concerto-util/types/lib/errorcodes.d.ts new file mode 100644 index 0000000000..5bdae05915 --- /dev/null +++ b/packages/concerto-util/types/lib/errorcodes.d.ts @@ -0,0 +1,4 @@ +export const DEFAULT_BASE_EXCEPTION: "DefaultBaseException"; +export const DEFAULT_VALIDATOR_EXCEPTION: "DefaultValidatorException"; +export const REGEX_VALIDATOR_EXCEPTION: "RegexValidatorException"; +export const TYPE_NOT_FOUND_EXCEPTION: "TypeNotFoundException";