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(map): Support Map Key Type Imports #739

Merged
merged 3 commits into from
Oct 19, 2023
Merged
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
3 changes: 2 additions & 1 deletion packages/concerto-core/lib/introspect/mapkeytype.js
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ class MapKeyType extends Decorated {
constructor(parent, ast) {
super(ast);
this.parent = parent;
this.modelFile = parent.getModelFile();
this.process();
}

Expand All @@ -70,7 +71,7 @@ class MapKeyType extends Decorated {
validate() {

if (!ModelUtil.isPrimitiveType(this.type)) {
let decl = this.parent.getModelFile().getAllDeclarations().find(d => d.name === this.ast.type?.name);
const decl = ModelUtil.getTypeDeclaration(this.ast.type.name, this.modelFile);

if (!ModelUtil.isValidMapKeyScalar(decl)) {
throw new IllegalModelException(
Expand Down
19 changes: 1 addition & 18 deletions packages/concerto-core/lib/introspect/mapvaluetype.js
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ class MapValueType extends Decorated {
*/
validate() {
if (!ModelUtil.isPrimitiveType(this.type)) {
const decl = this.getTypeDeclaration(this.ast.type.name);
const decl = ModelUtil.getTypeDeclaration(this.ast.type.name, this.modelFile);

// All declarations, with the exception of MapDeclarations, are valid Values.
if(decl.isMapDeclaration?.()) {
Expand Down Expand Up @@ -184,23 +184,6 @@ class MapValueType extends Decorated {
isValue() {
return true;
}

/**
* Returns the corresponding ClassDeclaration representation of the Type
*
* @param {string} type - the Type of the Map Value
* @return {Object} the corresponding ClassDeclaration representation
* @private
*/
getTypeDeclaration(type) {
if (this.modelFile.isLocalType(this.ast.type.name)) {
return this.modelFile.getAllDeclarations().find(d => d.name === this.ast.type.name);
} else {
const fqn = this.modelFile.resolveImport(this.ast.type.name);
return this.modelFile.getModelManager().getType(fqn);
}
}

}

module.exports = MapValueType;
32 changes: 27 additions & 5 deletions packages/concerto-core/lib/modelutil.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,13 @@ const { MetaModelUtil } = require('@accordproject/concerto-metamodel');
const semver = require('semver');
const Globalize = require('./globalize');

// Types needed for TypeScript generation.
/* eslint-disable no-unused-vars */
/* istanbul ignore next */
if (global === undefined) {
const ModelFile = require('../lib/introspect/modelfile');
}

const ID_REGEX = /^(\p{Lu}|\p{Ll}|\p{Lt}|\p{Lm}|\p{Lo}|\p{Nl}|\$|_|\\u[0-9A-Fa-f]{4})(?:\p{Lu}|\p{Ll}|\p{Lt}|\p{Lm}|\p{Lo}|\p{Nl}|\$|_|\\u[0-9A-Fa-f]{4}|\p{Mn}|\p{Mc}|\p{Nd}|\p{Pc}|\u200C|\u200D)*$/u;

const privateReservedProperties = [
Expand Down Expand Up @@ -315,11 +322,11 @@ class ModelUtil {
}

/**
* Returns true if this Value is a valid Map Value.
*
* @param {Object} value - the Value of the Map Declaration
* @return {boolean} true if the Value is a valid Map Value
*/
* Returns true if this Value is a valid Map Value.
*
* @param {Object} value - the Value of the Map Declaration
* @return {boolean} true if the Value is a valid Map Value
*/
static isValidMapValue(value) {
return [
`${MetaModelNamespace}.BooleanMapValueType`,
Expand All @@ -331,6 +338,21 @@ class ModelUtil {
`${MetaModelNamespace}.ObjectMapValueType`
].includes(value.$class);
}

/**
* Returns the corresponding ClassDeclaration representation of the Map Type
Copy link
Contributor

@dselman dselman Oct 20, 2023

Choose a reason for hiding this comment

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

Typo in the jsdoc - this is not map specific.

Not sure we need this function. ModelFile.getType takes a short type name and returns the FQN, taking care of primitive, imported types and reports errors.

The docs for this function are not clear in whether type is a short name or FQN and what happens with primitives, and what happens if the type does not exist (null/undefined return or throws an exception).

In general in Concerto we use "from biggest to smallest" when we order function args, so this would be (model, type).

If you decide we need this function, then the implementation could be:

// decl will be null for primitive types ... or throw exception?

const decl = !ModelUtil.isPrimitiveType((shortName) ? modelFile.getModelManager().getType(modelFile.getType(shortName)) : null;

* @param {string} type - the Type of the Map Value
* @param {ModelFile} modelFile - the ModelFile that owns the Property
* @return {Object} the corresponding ClassDeclaration representation
*/
static getTypeDeclaration(type, modelFile) {
if (modelFile.isLocalType(type)) {
return modelFile.getAllDeclarations().find(d => d.name === type);
} else {
const fqn = modelFile.resolveImport(type);
return modelFile.getModelManager().getType(fqn);
}
}
}

module.exports = ModelUtil;
20 changes: 20 additions & 0 deletions packages/concerto-core/test/data/parser/mapdeclaration/base.cto
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
/*
* 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.
*/
namespace [email protected]

concept Thing {
o String name
}

scalar Time extends DateTime
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
/*
* 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.
*/
namespace [email protected]

import [email protected].{Thing}

map Dictionary {
o Thing
o String
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
/*
* 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.
*/
namespace [email protected]

import [email protected]

map Dictionary {
o Time
o String
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
/*
* 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.
*/
namespace [email protected]

import [email protected]

map Dictionary {
o String
o Thing
}
50 changes: 37 additions & 13 deletions packages/concerto-core/test/introspect/mapdeclaration.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ const ParserUtil = require('./parserutility');

const ModelManager = require('../../lib/modelmanager');
const Util = require('../composer/composermodelutility');
const fs = require('fs');

const sinon = require('sinon');
const expect = require('chai').expect;
Expand All @@ -46,7 +47,7 @@ describe('MapDeclaration', () => {

describe('#constructor', () => {

it('should throw if ast contains no Map Key Property', () => {
it('should throw if ast contains no Map Key Type', () => {
(() => {
new MapDeclaration(modelFile, {
$class: '[email protected]',
Expand Down Expand Up @@ -162,6 +163,13 @@ describe('MapDeclaration', () => {
let decl = introspectUtils.loadLastDeclaration('test/data/parser/mapdeclaration/mapdeclaration.goodkey.scalar.string.cto', MapDeclaration);
decl.validate();
});

it('should validate when map key is imported and is of valid map key type', () => {
const base_cto = fs.readFileSync('test/data/parser/mapdeclaration/base.cto', 'utf-8');
introspectUtils.modelManager.addCTOModel(base_cto, 'base.cto');
let decl = introspectUtils.loadLastDeclaration('test/data/parser/mapdeclaration/mapdeclaration.goodkey.imported.scalar.cto', MapDeclaration);
decl.validate();
});
});

describe('#validate success scenarios - Map Value', () => {
Expand Down Expand Up @@ -254,11 +262,18 @@ describe('MapDeclaration', () => {
let decl = introspectUtils.loadLastDeclaration('test/data/parser/mapdeclaration/mapdeclaration.goodvalue.primitive.long.cto', MapDeclaration);
decl.validate();
});

it('should validate when map value is imported and is of valid map key type', () => {
const base_cto = fs.readFileSync('test/data/parser/mapdeclaration/base.cto', 'utf-8');
introspectUtils.modelManager.addCTOModel(base_cto, 'base.cto');
let decl = introspectUtils.loadLastDeclaration('test/data/parser/mapdeclaration/mapdeclaration.goodvalue.imported.thing.cto', MapDeclaration);
decl.validate();
});
});

describe('#validate failure scenarios - Map Key', () => {

it('should throw if ast contains illegal Map Key Property', () => {
it('should throw if ast contains illegal Map Key Type', () => {
(() => {
new MapDeclaration(modelFile, {
$class: '[email protected]',
Expand All @@ -273,42 +288,42 @@ describe('MapDeclaration', () => {
}).should.throw(IllegalModelException);
});

it('should throw if ast contains illegal Map Key Property - Concept', () => {
it('should throw if ast contains illegal Map Key Type - Concept', () => {
(() => {
let decl = introspectUtils.loadLastDeclaration('test/data/parser/mapdeclaration/mapdeclaration.badkey.declaration.concept.cto', MapDeclaration);
decl.validate().should.throw(IllegalModelException);
});
});

it('should throw if ast contains illegal Map Key Property - Scalar Long', () => {
it('should throw if ast contains illegal Map Key Type - Scalar Long', () => {
(() => {
let decl = introspectUtils.loadLastDeclaration('test/data/parser/mapdeclaration/mapdeclaration.badkey.scalar.long.cto', MapDeclaration);
decl.validate();
});
});

it('should throw if ast contains illegal Map Key Property - Scalar Integer', () => {
it('should throw if ast contains illegal Map Key Type - Scalar Integer', () => {
(() => {
let decl = introspectUtils.loadLastDeclaration('test/data/parser/mapdeclaration/mapdeclaration.badkey.scalar.integer.cto', MapDeclaration);
decl.validate().should.throw(IllegalModelException);
});
});

it('should throw if ast contains illegal Map Key Property - Scalar Double', () => {
it('should throw if ast contains illegal Map Key Type - Scalar Double', () => {
(() => {
let decl = introspectUtils.loadLastDeclaration('test/data/parser/mapdeclaration/mapdeclaration.badkey.scalar.double.cto', MapDeclaration);
decl.validate().should.throw(IllegalModelException);
});
});

it('should throw if ast contains illegal Map Key Property - Scalar Boolean', () => {
it('should throw if ast contains illegal Map Key Type - Scalar Boolean', () => {
(() => {
let decl = introspectUtils.loadLastDeclaration('test/data/parser/mapdeclaration/mapdeclaration.badkey.scalar.boolean.cto', MapDeclaration);
decl.validate().should.throw(IllegalModelException);
});
});

it('should throw if ast contains illegal Map Key Property - Boolean', () => {
it('should throw if ast contains illegal Map Key Type - Boolean', () => {
(() => {
new MapDeclaration(modelFile, {
$class: '[email protected]',
Expand All @@ -323,7 +338,7 @@ describe('MapDeclaration', () => {
}).should.throw(IllegalModelException);
});

it('should throw if ast contains illegal Map Key Property - Integer', () => {
it('should throw if ast contains illegal Map Key Type - Integer', () => {
(() => {
new MapDeclaration(modelFile, {
$class: '[email protected]',
Expand All @@ -338,7 +353,7 @@ describe('MapDeclaration', () => {
}).should.throw(IllegalModelException);
});

it('should throw if ast contains illegal Map Key Property - Long', () => {
it('should throw if ast contains illegal Map Key Type - Long', () => {
(() => {
new MapDeclaration(modelFile, {
$class: '[email protected]',
Expand All @@ -353,7 +368,7 @@ describe('MapDeclaration', () => {
}).should.throw(IllegalModelException);
});

it('should throw if ast contains illegal Map Key Property - Double', () => {
it('should throw if ast contains illegal Map Key Type - Double', () => {
(() => {
new MapDeclaration(modelFile, {
$class: '[email protected]',
Expand All @@ -368,7 +383,7 @@ describe('MapDeclaration', () => {
}).should.throw(IllegalModelException);
});

it('should throw if ast contains illegal Map Key Property - Enum', () => {
it('should throw if ast contains illegal Map Key Type - Enum', () => {
(() => {
new MapDeclaration(modelFile, {
$class: '[email protected]',
Expand All @@ -386,6 +401,15 @@ describe('MapDeclaration', () => {
});
}).should.throw(IllegalModelException);
});

it('should throw when map key is imported and is an illegal Map Key Type', () => {
(() => {
const base_cto = fs.readFileSync('test/data/parser/mapdeclaration/base.cto', 'utf-8');
introspectUtils.modelManager.addCTOModel(base_cto, 'base.cto');
let decl = introspectUtils.loadLastDeclaration('test/data/parser/mapdeclaration/mapdeclaration.badkey.imported.thing.cto', MapDeclaration);
decl.validate().should.throw(IllegalModelException);
});
});
});

describe('#validate failure scenarios - Map Value', () => {
Expand Down Expand Up @@ -489,7 +513,7 @@ describe('MapDeclaration', () => {
});

describe('#getKey', () => {
it('should return the map key property', () => {
it('should return the Map Key Type', () => {
let clz = new MapDeclaration(modelFile, {
$class: '[email protected]',
name: 'MapPermutation1',
Expand Down
9 changes: 9 additions & 0 deletions packages/concerto-core/types/lib/decoratormanager.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,15 @@ declare class DecoratorManager {
* @returns {object} the migrated DecoratorCommandSet object
*/
private static migrateTo;
/**
* Checks if the supplied decoratorCommandSet can be migrated.
* Migrations should only take place across minor versions of the same major version.
* @private
* @param {*} decoratorCommandSet the DecoratorCommandSet object
* @param {*} DCS_VERSION the DecoratorCommandSet version
* @returns {boolean} returns true if major versions are equal
*/
private static canMigrate;
/**
* Applies all the decorator commands from the DecoratorCommandSet
* to the ModelManager.
Expand Down
2 changes: 2 additions & 0 deletions packages/concerto-core/types/lib/introspect/mapkeytype.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ declare class MapKeyType extends Decorated {
*/
constructor(parent: MapDeclaration, ast: any);
parent: MapDeclaration;
modelFile: ModelFile;
/**
* Semantic validation of the structure of this class.
*
Expand Down Expand Up @@ -60,3 +61,4 @@ declare class MapKeyType extends Decorated {
}
import Decorated = require("./decorated");
import MapDeclaration = require("./mapdeclaration");
import ModelFile = require("./modelfile");
8 changes: 0 additions & 8 deletions packages/concerto-core/types/lib/introspect/mapvaluetype.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,14 +57,6 @@ declare class MapValueType extends Decorated {
* @return {boolean} true if the class is a Map Value
*/
isValue(): boolean;
/**
* Returns the corresponding ClassDeclaration representation of the Type
*
* @param {string} type - the Type of the Map Value
* @return {Object} the corresponding ClassDeclaration representation
* @private
*/
private getTypeDeclaration;
}
import Decorated = require("./decorated");
import MapDeclaration = require("./mapdeclaration");
Expand Down
Loading
Loading