Skip to content

Commit 1ba9c07

Browse files
feat(map): refactor map shape (#674)
* feat(map): refactor map shape Signed-off-by: jonathan.casey <[email protected]> * feat(*): map can be empty Signed-off-by: jonathan.casey <[email protected]> * feat(*): map cannot use private reserved properties as keys Signed-off-by: jonathan.casey <[email protected]> * feat(*): refactor test Signed-off-by: jonathan.casey <[email protected]> --------- Signed-off-by: jonathan.casey <[email protected]>
1 parent ac70543 commit 1ba9c07

File tree

6 files changed

+42
-52
lines changed

6 files changed

+42
-52
lines changed

packages/concerto-core/lib/serializer/jsongenerator.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ class JSONGenerator {
8585
*/
8686
visitMapDeclaration(mapDeclaration, parameters) {
8787
const obj = parameters.stack.pop();
88-
return { $class: obj.$class, value: Object.fromEntries(obj.value)};
88+
return Object.fromEntries(obj);
8989
}
9090

9191
/**

packages/concerto-core/lib/serializer/jsonpopulator.js

Lines changed: 4 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ function getAssignableProperties(resourceData, classDeclaration) {
4646
}
4747

4848
if (properties.includes('$timestamp') &&
49-
!(classDeclaration.isTransaction() || classDeclaration.isEvent())
49+
!(classDeclaration.isTransaction?.() || classDeclaration.isEvent?.())
5050
) {
5151
const errorText = `Unexpected property for type ${classDeclaration.getFullyQualifiedName()}: $timestamp`;
5252
throw new ValidationException(errorText);
@@ -172,17 +172,11 @@ class JSONPopulator {
172172
visitMapDeclaration(mapDeclaration, parameters) {
173173
const jsonObj = parameters.jsonStack.pop();
174174
parameters.path ?? (parameters.path = new TypedStack('$'));
175-
const path = parameters.path.stack.join('');
176-
177-
if(!jsonObj.$class) {
178-
throw new Error(`Invalid JSON data at "${path}". Map value does not contain a $class type identifier.`);
179-
}
180175

181-
if(!jsonObj.value) {
182-
throw new Error(`Invalid JSON data at "${path}". Map value does not contain a value property.`);
183-
}
176+
// throws if Map contains private reserved properties as keys.
177+
getAssignableProperties(jsonObj, mapDeclaration);
184178

185-
return { $class: jsonObj.$class, value: new Map(Object.entries(jsonObj.value)) };
179+
return new Map(Object.entries(jsonObj));
186180
}
187181

188182
/**

packages/concerto-core/lib/serializer/resourcevalidator.js

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -116,15 +116,20 @@ class ResourceValidator {
116116
visitMapDeclaration(mapDeclaration, parameters) {
117117
const obj = parameters.stack.pop();
118118

119-
if (!((obj.value instanceof Map))) {
119+
if (!((obj instanceof Map))) {
120120
throw new Error('Expected a Map, but found ' + JSON.stringify(obj));
121121
}
122122

123-
if (obj.$class !== mapDeclaration.getFullyQualifiedName()) {
123+
if(!obj.has('$class')) {
124+
throw new Error('Invalid Map. Map must contain a properly formatted $class property');
125+
}
126+
127+
if (obj.get('$class') !== mapDeclaration.getFullyQualifiedName()) {
124128
throw new Error(`$class value must match ${mapDeclaration.getFullyQualifiedName()}`);
125129
}
126130

127-
obj.value.forEach((value, key) => {
131+
132+
obj.forEach((value, key) => {
128133
if(!ModelUtil.isSystemProperty(key)) {
129134
if (typeof key !== 'string') {
130135
ResourceValidator.reportInvalidMap(parameters.rootResourceIdentifier, mapDeclaration, obj);

packages/concerto-core/test/model/concept.js

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -141,10 +141,8 @@ describe('Concept', function () {
141141
invType:'NEWBATCH',
142142
dictionary: {
143143
$class: 'org.acme.biznet.Dictionary',
144-
value: {
145-
'key1': 'value1',
146-
'key2': 'value2',
147-
}
144+
key1: 'value1',
145+
key2: 'value2',
148146
}
149147
};
150148
const obj = serializer.fromJSON(jsObject);

packages/concerto-core/test/serializer.js

Lines changed: 24 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -243,16 +243,15 @@ describe('Serializer', () => {
243243
});
244244
});
245245

246-
it('should generate concept with a map value', () => {
246+
it('should generate concept with a Map value', () => {
247247
let address = factory.newConcept('org.acme.sample', 'Address');
248248
address.city = 'Winchester';
249249
address.country = 'UK';
250250
address.elevation = 3.14;
251251
address.postcode = 'SO21 2JN';
252-
address.dict = {
253-
$class: 'org.acme.sample.Dictionary',
254-
value: new Map(Object.entries({'Lorem':'Ipsum'}))
255-
};
252+
address.dict = new Map();
253+
address.dict.set('$class', 'org.acme.sample.Dictionary');
254+
address.dict.set('Lorem', 'Ipsum');
256255

257256
// todo test for reserved identifiers in keys ($class)
258257
const json = serializer.toJSON(address);
@@ -264,13 +263,12 @@ describe('Serializer', () => {
264263
postcode: 'SO21 2JN',
265264
dict: {
266265
$class: 'org.acme.sample.Dictionary',
267-
value: { 'Lorem': 'Ipsum' }
266+
Lorem: 'Ipsum'
268267
}
269268
});
270269
});
271270

272-
273-
it('should throw if the value for a map is not a Map instance', () => {
271+
it('should throw if the value for a Map is not a Map instance', () => {
274272
let address = factory.newConcept('org.acme.sample', 'Address');
275273
address.city = 'Winchester';
276274
address.country = 'UK';
@@ -288,10 +286,10 @@ describe('Serializer', () => {
288286
address.country = 'UK';
289287
address.elevation = 3.14;
290288
address.postcode = 'SO21 2JN';
291-
address.dict = {
292-
$class: 'org.acme.sample.PhoneBook',
293-
value: new Map(Object.entries({'Lorem':'Ipsum'}))
294-
};
289+
address.dict = new Map();
290+
address.dict.set('$class', 'org.acme.sample.PhoneBook'); // dict is not a PhoneBook.
291+
address.dict.set('Lorem', 'Ipsum');
292+
295293
(() => {
296294
serializer.toJSON(address);
297295
}).should.throw('$class value must match org.acme.sample.Dictionary');
@@ -397,8 +395,7 @@ describe('Serializer', () => {
397395
resource.postcode.should.equal('SO21 2JN');
398396
});
399397

400-
it('should deserialize a valid concept with a map', () => {
401-
398+
it('should deserialize a valid concept with a Map', () => {
402399
let json = {
403400
$class: 'org.acme.sample.Address',
404401
city: 'Winchester',
@@ -407,9 +404,7 @@ describe('Serializer', () => {
407404
postcode: 'SO21 2JN',
408405
dict: {
409406
'$class': 'org.acme.sample.Dictionary',
410-
value: {
411-
'Lorem': 'Ipsum'
412-
}
407+
'Lorem': 'Ipsum'
413408
}
414409
};
415410
let resource = serializer.fromJSON(json);
@@ -419,12 +414,12 @@ describe('Serializer', () => {
419414
resource.country.should.equal('UK');
420415
resource.elevation.should.equal(3.14);
421416
resource.postcode.should.equal('SO21 2JN');
422-
resource.dict.$class.should.equal('org.acme.sample.Dictionary');
423-
resource.dict.value.should.be.an.instanceOf(Map);
424-
resource.dict.value.get('Lorem').should.equal('Ipsum');
417+
resource.dict.should.be.an.instanceOf(Map);
418+
resource.dict.get('$class').should.equal('org.acme.sample.Dictionary');
419+
resource.dict.get('Lorem').should.equal('Ipsum');
425420
});
426421

427-
it('should throw an error when deserializing a map without a $class property', () => {
422+
it('should throw an error when deserializing a Map without a $class property', () => {
428423

429424
let json = {
430425
$class: 'org.acme.sample.Address',
@@ -434,17 +429,17 @@ describe('Serializer', () => {
434429
postcode: 'SO21 2JN',
435430
dict: {
436431
// '$class': 'org.acme.sample.Dictionary',
437-
value: {
438-
'Lorem': 'Ipsum'
439-
}
432+
'Lorem': 'Ipsum'
440433
}
441434
};
442435
(() => {
443436
serializer.fromJSON(json);
444-
}).should.throw('Invalid JSON data at "$.dict". Map value does not contain a $class type identifier.');
437+
}).should.throw('Invalid Map. Map must contain a properly formatted $class property');
445438
});
446439

447-
it('should throw an error when deserializing a map without a value property', () => {
440+
441+
it('should throw an error when deserializing a Map with a private reserved property', () => {
442+
448443
let json = {
449444
$class: 'org.acme.sample.Address',
450445
city: 'Winchester',
@@ -453,14 +448,13 @@ describe('Serializer', () => {
453448
postcode: 'SO21 2JN',
454449
dict: {
455450
'$class': 'org.acme.sample.Dictionary',
456-
// value: {
457-
// 'Lorem': 'Ipsum'
458-
// }
451+
'$namespace': 'com.reserved.property',
452+
'Lorem': 'Ipsum'
459453
}
460454
};
461455
(() => {
462456
serializer.fromJSON(json);
463-
}).should.throw('Invalid JSON data at "$.dict". Map value does not contain a value property.');
457+
}).should.throw('Unexpected reserved properties for type org.acme.sample.Dictionary: $namespace');
464458
});
465459

466460
it('should throw validation errors if the validate flag is not specified', () => {

packages/concerto-core/test/serializer/resourcevalidator.js

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -348,15 +348,15 @@ describe('ResourceValidator', function () {
348348

349349
describe('#visitMapDeclaration', function() {
350350
it('should validate map', function () {
351-
const map = { $class: 'org.acme.map.PhoneBook', value: new Map([['Lorem', 'Ipsum']]) };
351+
const map = new Map([['$class', 'org.acme.map.PhoneBook'], ['Lorem', 'Ipsum']]);
352352
const typedStack = new TypedStack(map);
353353
const mapDeclaration = modelManager.getType('org.acme.map.PhoneBook');
354354
const parameters = { stack : typedStack, 'modelManager' : modelManager, rootResourceIdentifier : 'TEST' };
355355
mapDeclaration.accept(resourceValidator,parameters );
356356
});
357357

358358
it('should not validate map with bad value', function () {
359-
const map = { $class: 'org.acme.map.PhoneBook', value: new Map([['Lorem', 3]]) };
359+
const map = new Map([['$class', 'org.acme.map.PhoneBook'], ['Lorem', 3]]);
360360
const typedStack = new TypedStack(map);
361361
const mapDeclaration = modelManager.getType('org.acme.map.PhoneBook');
362362
const parameters = { stack : typedStack, 'modelManager' : modelManager, rootResourceIdentifier : 'TEST' };
@@ -367,8 +367,7 @@ describe('ResourceValidator', function () {
367367
});
368368

369369
it('should not validate map with bad key', function () {
370-
const map = { $class: 'org.acme.map.PhoneBook', value: new Map([[1, 'Ipsum']]) };
371-
370+
const map = new Map([['$class', 'org.acme.map.PhoneBook'], [1, 'Ipsum']]);
372371
const typedStack = new TypedStack(map);
373372
const mapDeclaration = modelManager.getType('org.acme.map.PhoneBook');
374373
const parameters = { stack : typedStack, 'modelManager' : modelManager, rootResourceIdentifier : 'TEST' };

0 commit comments

Comments
 (0)