Skip to content

Commit bfd514d

Browse files
feat(map): Support Map Key Type Imports (#739)
* feat(map): handle imports for map key Signed-off-by: Jonathan Casey <jonathan.casey@docusign.com> * feat(map): add test coverage Signed-off-by: Jonathan Casey <jonathan.casey@docusign.com> * feat(map): add type defs Signed-off-by: Jonathan Casey <jonathan.casey@docusign.com> --------- Signed-off-by: Jonathan Casey <jonathan.casey@docusign.com>
1 parent 8d4fe1b commit bfd514d

12 files changed

+173
-50
lines changed

packages/concerto-core/lib/introspect/mapkeytype.js

+2-1
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ class MapKeyType extends Decorated {
4747
constructor(parent, ast) {
4848
super(ast);
4949
this.parent = parent;
50+
this.modelFile = parent.getModelFile();
5051
this.process();
5152
}
5253

@@ -70,7 +71,7 @@ class MapKeyType extends Decorated {
7071
validate() {
7172

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

7576
if (!ModelUtil.isValidMapKeyScalar(decl)) {
7677
throw new IllegalModelException(

packages/concerto-core/lib/introspect/mapvaluetype.js

+1-18
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ class MapValueType extends Decorated {
6969
*/
7070
validate() {
7171
if (!ModelUtil.isPrimitiveType(this.type)) {
72-
const decl = this.getTypeDeclaration(this.ast.type.name);
72+
const decl = ModelUtil.getTypeDeclaration(this.ast.type.name, this.modelFile);
7373

7474
// All declarations, with the exception of MapDeclarations, are valid Values.
7575
if(decl.isMapDeclaration?.()) {
@@ -184,23 +184,6 @@ class MapValueType extends Decorated {
184184
isValue() {
185185
return true;
186186
}
187-
188-
/**
189-
* Returns the corresponding ClassDeclaration representation of the Type
190-
*
191-
* @param {string} type - the Type of the Map Value
192-
* @return {Object} the corresponding ClassDeclaration representation
193-
* @private
194-
*/
195-
getTypeDeclaration(type) {
196-
if (this.modelFile.isLocalType(this.ast.type.name)) {
197-
return this.modelFile.getAllDeclarations().find(d => d.name === this.ast.type.name);
198-
} else {
199-
const fqn = this.modelFile.resolveImport(this.ast.type.name);
200-
return this.modelFile.getModelManager().getType(fqn);
201-
}
202-
}
203-
204187
}
205188

206189
module.exports = MapValueType;

packages/concerto-core/lib/modelutil.js

+27-5
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,13 @@ const { MetaModelUtil } = require('@accordproject/concerto-metamodel');
1919
const semver = require('semver');
2020
const Globalize = require('./globalize');
2121

22+
// Types needed for TypeScript generation.
23+
/* eslint-disable no-unused-vars */
24+
/* istanbul ignore next */
25+
if (global === undefined) {
26+
const ModelFile = require('../lib/introspect/modelfile');
27+
}
28+
2229
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;
2330

2431
const privateReservedProperties = [
@@ -315,11 +322,11 @@ class ModelUtil {
315322
}
316323

317324
/**
318-
* Returns true if this Value is a valid Map Value.
319-
*
320-
* @param {Object} value - the Value of the Map Declaration
321-
* @return {boolean} true if the Value is a valid Map Value
322-
*/
325+
* Returns true if this Value is a valid Map Value.
326+
*
327+
* @param {Object} value - the Value of the Map Declaration
328+
* @return {boolean} true if the Value is a valid Map Value
329+
*/
323330
static isValidMapValue(value) {
324331
return [
325332
`${MetaModelNamespace}.BooleanMapValueType`,
@@ -331,6 +338,21 @@ class ModelUtil {
331338
`${MetaModelNamespace}.ObjectMapValueType`
332339
].includes(value.$class);
333340
}
341+
342+
/**
343+
* Returns the corresponding ClassDeclaration representation of the Map Type
344+
* @param {string} type - the Type of the Map Value
345+
* @param {ModelFile} modelFile - the ModelFile that owns the Property
346+
* @return {Object} the corresponding ClassDeclaration representation
347+
*/
348+
static getTypeDeclaration(type, modelFile) {
349+
if (modelFile.isLocalType(type)) {
350+
return modelFile.getAllDeclarations().find(d => d.name === type);
351+
} else {
352+
const fqn = modelFile.resolveImport(type);
353+
return modelFile.getModelManager().getType(fqn);
354+
}
355+
}
334356
}
335357

336358
module.exports = ModelUtil;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
/*
2+
* Licensed under the Apache License, Version 2.0 (the "License");
3+
* you may not use this file except in compliance with the License.
4+
* You may obtain a copy of the License at
5+
*
6+
* http://www.apache.org/licenses/LICENSE-2.0
7+
*
8+
* Unless required by applicable law or agreed to in writing, software
9+
* distributed under the License is distributed on an "AS IS" BASIS,
10+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11+
* See the License for the specific language governing permissions and
12+
* limitations under the License.
13+
*/
14+
namespace com.testing.base@1.0.0
15+
16+
concept Thing {
17+
o String name
18+
}
19+
20+
scalar Time extends DateTime
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
/*
2+
* Licensed under the Apache License, Version 2.0 (the "License");
3+
* you may not use this file except in compliance with the License.
4+
* You may obtain a copy of the License at
5+
*
6+
* http://www.apache.org/licenses/LICENSE-2.0
7+
*
8+
* Unless required by applicable law or agreed to in writing, software
9+
* distributed under the License is distributed on an "AS IS" BASIS,
10+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11+
* See the License for the specific language governing permissions and
12+
* limitations under the License.
13+
*/
14+
namespace com.testing@1.0.0
15+
16+
import com.testing.base@1.0.0.{Thing}
17+
18+
map Dictionary {
19+
o Thing
20+
o String
21+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
/*
2+
* Licensed under the Apache License, Version 2.0 (the "License");
3+
* you may not use this file except in compliance with the License.
4+
* You may obtain a copy of the License at
5+
*
6+
* http://www.apache.org/licenses/LICENSE-2.0
7+
*
8+
* Unless required by applicable law or agreed to in writing, software
9+
* distributed under the License is distributed on an "AS IS" BASIS,
10+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11+
* See the License for the specific language governing permissions and
12+
* limitations under the License.
13+
*/
14+
namespace com.testing@1.0.0
15+
16+
import com.testing.base@1.0.0.Time
17+
18+
map Dictionary {
19+
o Time
20+
o String
21+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
/*
2+
* Licensed under the Apache License, Version 2.0 (the "License");
3+
* you may not use this file except in compliance with the License.
4+
* You may obtain a copy of the License at
5+
*
6+
* http://www.apache.org/licenses/LICENSE-2.0
7+
*
8+
* Unless required by applicable law or agreed to in writing, software
9+
* distributed under the License is distributed on an "AS IS" BASIS,
10+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11+
* See the License for the specific language governing permissions and
12+
* limitations under the License.
13+
*/
14+
namespace com.testing@1.0.0
15+
16+
import com.testing.base@1.0.0.Thing
17+
18+
map Dictionary {
19+
o String
20+
o Thing
21+
}

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

+37-13
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ const ParserUtil = require('./parserutility');
2525

2626
const ModelManager = require('../../lib/modelmanager');
2727
const Util = require('../composer/composermodelutility');
28+
const fs = require('fs');
2829

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

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

49-
it('should throw if ast contains no Map Key Property', () => {
50+
it('should throw if ast contains no Map Key Type', () => {
5051
(() => {
5152
new MapDeclaration(modelFile, {
5253
$class: 'concerto.metamodel@1.0.0.MapDeclaration',
@@ -162,6 +163,13 @@ describe('MapDeclaration', () => {
162163
let decl = introspectUtils.loadLastDeclaration('test/data/parser/mapdeclaration/mapdeclaration.goodkey.scalar.string.cto', MapDeclaration);
163164
decl.validate();
164165
});
166+
167+
it('should validate when map key is imported and is of valid map key type', () => {
168+
const base_cto = fs.readFileSync('test/data/parser/mapdeclaration/base.cto', 'utf-8');
169+
introspectUtils.modelManager.addCTOModel(base_cto, 'base.cto');
170+
let decl = introspectUtils.loadLastDeclaration('test/data/parser/mapdeclaration/mapdeclaration.goodkey.imported.scalar.cto', MapDeclaration);
171+
decl.validate();
172+
});
165173
});
166174

167175
describe('#validate success scenarios - Map Value', () => {
@@ -254,11 +262,18 @@ describe('MapDeclaration', () => {
254262
let decl = introspectUtils.loadLastDeclaration('test/data/parser/mapdeclaration/mapdeclaration.goodvalue.primitive.long.cto', MapDeclaration);
255263
decl.validate();
256264
});
265+
266+
it('should validate when map value is imported and is of valid map key type', () => {
267+
const base_cto = fs.readFileSync('test/data/parser/mapdeclaration/base.cto', 'utf-8');
268+
introspectUtils.modelManager.addCTOModel(base_cto, 'base.cto');
269+
let decl = introspectUtils.loadLastDeclaration('test/data/parser/mapdeclaration/mapdeclaration.goodvalue.imported.thing.cto', MapDeclaration);
270+
decl.validate();
271+
});
257272
});
258273

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

261-
it('should throw if ast contains illegal Map Key Property', () => {
276+
it('should throw if ast contains illegal Map Key Type', () => {
262277
(() => {
263278
new MapDeclaration(modelFile, {
264279
$class: 'concerto.metamodel@1.0.0.MapDeclaration',
@@ -273,42 +288,42 @@ describe('MapDeclaration', () => {
273288
}).should.throw(IllegalModelException);
274289
});
275290

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

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

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

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

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

311-
it('should throw if ast contains illegal Map Key Property - Boolean', () => {
326+
it('should throw if ast contains illegal Map Key Type - Boolean', () => {
312327
(() => {
313328
new MapDeclaration(modelFile, {
314329
$class: 'concerto.metamodel@1.0.0.MapDeclaration',
@@ -323,7 +338,7 @@ describe('MapDeclaration', () => {
323338
}).should.throw(IllegalModelException);
324339
});
325340

326-
it('should throw if ast contains illegal Map Key Property - Integer', () => {
341+
it('should throw if ast contains illegal Map Key Type - Integer', () => {
327342
(() => {
328343
new MapDeclaration(modelFile, {
329344
$class: 'concerto.metamodel@1.0.0.MapDeclaration',
@@ -338,7 +353,7 @@ describe('MapDeclaration', () => {
338353
}).should.throw(IllegalModelException);
339354
});
340355

341-
it('should throw if ast contains illegal Map Key Property - Long', () => {
356+
it('should throw if ast contains illegal Map Key Type - Long', () => {
342357
(() => {
343358
new MapDeclaration(modelFile, {
344359
$class: 'concerto.metamodel@1.0.0.MapDeclaration',
@@ -353,7 +368,7 @@ describe('MapDeclaration', () => {
353368
}).should.throw(IllegalModelException);
354369
});
355370

356-
it('should throw if ast contains illegal Map Key Property - Double', () => {
371+
it('should throw if ast contains illegal Map Key Type - Double', () => {
357372
(() => {
358373
new MapDeclaration(modelFile, {
359374
$class: 'concerto.metamodel@1.0.0.MapDeclaration',
@@ -368,7 +383,7 @@ describe('MapDeclaration', () => {
368383
}).should.throw(IllegalModelException);
369384
});
370385

371-
it('should throw if ast contains illegal Map Key Property - Enum', () => {
386+
it('should throw if ast contains illegal Map Key Type - Enum', () => {
372387
(() => {
373388
new MapDeclaration(modelFile, {
374389
$class: 'concerto.metamodel@1.0.0.MapDeclaration',
@@ -386,6 +401,15 @@ describe('MapDeclaration', () => {
386401
});
387402
}).should.throw(IllegalModelException);
388403
});
404+
405+
it('should throw when map key is imported and is an illegal Map Key Type', () => {
406+
(() => {
407+
const base_cto = fs.readFileSync('test/data/parser/mapdeclaration/base.cto', 'utf-8');
408+
introspectUtils.modelManager.addCTOModel(base_cto, 'base.cto');
409+
let decl = introspectUtils.loadLastDeclaration('test/data/parser/mapdeclaration/mapdeclaration.badkey.imported.thing.cto', MapDeclaration);
410+
decl.validate().should.throw(IllegalModelException);
411+
});
412+
});
389413
});
390414

391415
describe('#validate failure scenarios - Map Value', () => {
@@ -489,7 +513,7 @@ describe('MapDeclaration', () => {
489513
});
490514

491515
describe('#getKey', () => {
492-
it('should return the map key property', () => {
516+
it('should return the Map Key Type', () => {
493517
let clz = new MapDeclaration(modelFile, {
494518
$class: 'concerto.metamodel@1.0.0.MapDeclaration',
495519
name: 'MapPermutation1',

packages/concerto-core/types/lib/decoratormanager.d.ts

+9
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,15 @@ declare class DecoratorManager {
2727
* @returns {object} the migrated DecoratorCommandSet object
2828
*/
2929
private static migrateTo;
30+
/**
31+
* Checks if the supplied decoratorCommandSet can be migrated.
32+
* Migrations should only take place across minor versions of the same major version.
33+
* @private
34+
* @param {*} decoratorCommandSet the DecoratorCommandSet object
35+
* @param {*} DCS_VERSION the DecoratorCommandSet version
36+
* @returns {boolean} returns true if major versions are equal
37+
*/
38+
private static canMigrate;
3039
/**
3140
* Applies all the decorator commands from the DecoratorCommandSet
3241
* to the ModelManager.

packages/concerto-core/types/lib/introspect/mapkeytype.d.ts

+2
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ declare class MapKeyType extends Decorated {
1717
*/
1818
constructor(parent: MapDeclaration, ast: any);
1919
parent: MapDeclaration;
20+
modelFile: ModelFile;
2021
/**
2122
* Semantic validation of the structure of this class.
2223
*
@@ -60,3 +61,4 @@ declare class MapKeyType extends Decorated {
6061
}
6162
import Decorated = require("./decorated");
6263
import MapDeclaration = require("./mapdeclaration");
64+
import ModelFile = require("./modelfile");

packages/concerto-core/types/lib/introspect/mapvaluetype.d.ts

-8
Original file line numberDiff line numberDiff line change
@@ -57,14 +57,6 @@ declare class MapValueType extends Decorated {
5757
* @return {boolean} true if the class is a Map Value
5858
*/
5959
isValue(): boolean;
60-
/**
61-
* Returns the corresponding ClassDeclaration representation of the Type
62-
*
63-
* @param {string} type - the Type of the Map Value
64-
* @return {Object} the corresponding ClassDeclaration representation
65-
* @private
66-
*/
67-
private getTypeDeclaration;
6860
}
6961
import Decorated = require("./decorated");
7062
import MapDeclaration = require("./mapdeclaration");

0 commit comments

Comments
 (0)