Skip to content

Commit

Permalink
feat(map): Support Map Key Type Imports (#739)
Browse files Browse the repository at this point in the history
* feat(map): handle imports for map key

Signed-off-by: Jonathan Casey <[email protected]>

* feat(map): add test coverage

Signed-off-by: Jonathan Casey <[email protected]>

* feat(map): add type defs

Signed-off-by: Jonathan Casey <[email protected]>

---------

Signed-off-by: Jonathan Casey <[email protected]>
  • Loading branch information
jonathan-casey authored Oct 19, 2023
1 parent 8d4fe1b commit bfd514d
Show file tree
Hide file tree
Showing 12 changed files with 173 additions and 50 deletions.
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
* @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

0 comments on commit bfd514d

Please sign in to comment.