Skip to content

Commit

Permalink
feat(map): Extend Type support (#677)
Browse files Browse the repository at this point in the history
* feat(map): add to introspection API

Signed-off-by: jonathan.casey <[email protected]>

* feat(map): extend JsonGenerator for Map Types

Signed-off-by: jonathan.casey <[email protected]>

* feat(map): extend JsonPopulator for Map Types

Signed-off-by: jonathan.casey <[email protected]>

* feat(map): extend ResourceValidator for Map Types

Signed-off-by: jonathan.casey <[email protected]>

* feat(map): add test cases for Map serialisation <> deserialisation

Signed-off-by: jonathan.casey <[email protected]>

* feat(map): cleanup

Signed-off-by: jonathan.casey <[email protected]>

* feat(map): add type definitions

Signed-off-by: jonathan.casey <[email protected]>

* feat(map): satisfy version checker

Signed-off-by: jonathan.casey <[email protected]>

* feat(map): remove variable assignment for chai import

Signed-off-by: jonathan.casey <[email protected]>

* feat(map): satisfy version checker

Signed-off-by: jonathan.casey <[email protected]>

* feat(map): update test cases to match on error handling changes

Signed-off-by: jonathan.casey <[email protected]>

* feat(map): cleanup

Signed-off-by: jonathan.casey <[email protected]>

* feat(map): cleanup test cases

Signed-off-by: jonathan.casey <[email protected]>

* test(map): add tests for missed cases

Signed-off-by: Matt Roberts <[email protected]>

* feat(map): fixes metamodel parsing for MapDeclarations

Signed-off-by: jonathan.casey <[email protected]>

* feat(map): printer update reads new metamodel map shape

Signed-off-by: jonathan.casey <[email protected]>

* feat(map): updates test data for new metamodel design

Signed-off-by: jonathan.casey <[email protected]>

* feat(map): initial rework of introspection

Signed-off-by: jonathan.casey <[email protected]>

* feat(map): add more test coverage

Signed-off-by: jonathan.casey <[email protected]>

* feat(map): improve error messaging & cleanup

Signed-off-by: jonathan.casey <[email protected]>

* feat(map): adds more test coverage for MapKeyType introspection

Signed-off-by: jonathan.casey <[email protected]>

* feat(map): adds more test coverage for MapValueType introspection

Signed-off-by: jonathan.casey <[email protected]>

* feat(map): refactor serialization

Signed-off-by: jonathan.casey <[email protected]>

* feat(map): refactor serialization test coverage

Signed-off-by: jonathan.casey <[email protected]>

* feat(map): cleanup

Signed-off-by: jonathan.casey <[email protected]>

* feat(map): DRY up code in the JSONPopulator

Signed-off-by: jonathan.casey <[email protected]>

* feat(map): adds coverage to new introspection functions

Signed-off-by: jonathan.casey <[email protected]>

* feat(map): adds type definitions

Signed-off-by: jonathan.casey <[email protected]>

* feat(map): updates changelog

Signed-off-by: jonathan.casey <[email protected]>

* fix: Private identifiers are only available when targeting ECMAScript 2015 and higher

Signed-off-by: jonathan.casey <[email protected]>

* fix: updates changelog

Signed-off-by: jonathan.casey <[email protected]>

* feat(map): moves isValidMapKey & isValidMapValue to ModelUtils

Signed-off-by: jonathan.casey <[email protected]>

* feat(map): removes unreachable default case

Signed-off-by: jonathan.casey <[email protected]>

* feat(map): removes unreachable code

Signed-off-by: jonathan.casey <[email protected]>

* feat(map): adds more test coverage

Signed-off-by: jonathan.casey <[email protected]>

* feat(map): remove dead code

Signed-off-by: jonathan.casey <[email protected]>

* feat(map): adds JSDoc @Private

Signed-off-by: jonathan.casey <[email protected]>

* feat(map): adds type defs & change log

Signed-off-by: jonathan.casey <[email protected]>

* feat(map): improve error message

Signed-off-by: jonathan.casey <[email protected]>

* feat(map): add util method

Signed-off-by: jonathan.casey <[email protected]>

* fix: update type def

Signed-off-by: jonathan.casey <[email protected]>

---------

Signed-off-by: jonathan.casey <[email protected]>
Signed-off-by: Matt Roberts <[email protected]>
Co-authored-by: Matt Roberts <[email protected]>
  • Loading branch information
jonathan-casey and mttrbrts authored Aug 16, 2023
1 parent e55a60d commit f8da8a9
Show file tree
Hide file tree
Showing 33 changed files with 3,459 additions and 833 deletions.
5 changes: 4 additions & 1 deletion packages/concerto-core/api.txt
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,6 @@ class MapDeclaration extends Declaration {
+ string getName()
+ string getKey()
+ string getValue()
+ array getProperties()
+ String toString()
+ string declarationKind()
+ boolean isMapDeclaration()
Expand All @@ -174,6 +173,8 @@ class MapKeyType extends Decorated {
+ MapDeclaration getParent()
+ string getType()
+ String toString()
+ boolean isKey()
+ boolean isValue()
}
class MapValueType extends Decorated {
+ void constructor(MapDeclaration,Object) throws IllegalModelException
Expand All @@ -182,6 +183,8 @@ class MapValueType extends Decorated {
+ MapDeclaration getParent()
+ string getType()
+ String toString()
+ boolean isKey()
+ boolean isValue()
}
+ ModelManager newMetaModelManager()
+ object validateMetaModel()
Expand Down
15 changes: 15 additions & 0 deletions packages/concerto-core/changelog.txt
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,21 @@
# Note that the latest public API is documented using JSDocs and is available in api.txt.
#

Version 3.8.5 {9cd54e67c4a39a3041706e4b37dce9f1} 2023-08-14
- Makes MapKeyType MapValueType processType() private

Version 3.8.4 {a99786670d2561bf2b84cb3eb1bb1a08} 2023-08-14
- Move isValidMapKey & isValidMapValue to ModelUtils

Version 3.8.3 {7dd828f29c49cedfb8eee2f3c6fefa6e} 2023-08-10
- Removes private identifiers from MapKeyType & MapValueType

Version 3.8.2 {1a497711c4cca84d1dfa92c0b581fadf} 2023-08-10
- Add isValidMapKey(Object) isValidMapValue(Object)

Version 3.8.1 {794268f69b81f05f711d38a9ef1a7833} 2023-08-1
- Add To MapKeyType, MapValue functionality

Version 3.7.0 {a97cb6ebd45679354ba4da1940d2bb8d} 2023-05-19
- Add MapDeclaration, MapKeyType, AggregateValueType

Expand Down
30 changes: 8 additions & 22 deletions packages/concerto-core/lib/introspect/mapdeclaration.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,6 @@

'use strict';

const { MetaModelNamespace } = require('@accordproject/concerto-metamodel');

const Declaration = require('./declaration');
const IllegalModelException = require('./illegalmodelexception');
const MapValueType = require('./mapvaluetype');
Expand Down Expand Up @@ -65,24 +63,21 @@ class MapDeclaration extends Declaration {
process() {
super.process();

if (this.ast.properties.length !== 2) {
throw new IllegalModelException(`MapDeclaration must contain exactly two properties - MapKeyType & MapyValueType ${this.ast.name}`, this.modelFile, this.ast.location);
if (!this.ast.key || !this.ast.value) {
throw new IllegalModelException(`MapDeclaration must contain Key & Value properties ${this.ast.name}`, this.modelFile, this.ast.location);
}

const key = this.ast.properties.find(property => property.$class === `${MetaModelNamespace}.MapKeyType`);
const value = this.ast.properties.find(property => property.$class === `${MetaModelNamespace}.AggregateValueType` || property.$class === `${MetaModelNamespace}.AggregateRelationshipValueType`);

if (!key) {
throw new IllegalModelException(`MapDeclaration must contain MapKeyType ${this.ast.name}`, this.modelFile, this.ast.location);
if (!ModelUtil.isValidMapKey(this.ast.key)) {
throw new IllegalModelException(`MapDeclaration must contain valid MapKeyType ${this.ast.name}`, this.modelFile, this.ast.location);
}

if (!value) {
throw new IllegalModelException(`MapDeclaration must contain AggregateValueType ${this.ast.name}`, this.modelFile, this.ast.location);
if (!ModelUtil.isValidMapValue(this.ast.value)) {
throw new IllegalModelException(`MapDeclaration must contain valid MapValueType, for MapDeclaration ${this.ast.name}` , this.modelFile, this.ast.location);
}

this.name = this.ast.name;
this.key = new MapKeyType(this, key);
this.value = new MapValueType(this, value);
this.key = new MapKeyType(this, this.ast.key);
this.value = new MapValueType(this, this.ast.value);
this.fqn = ModelUtil.getFullyQualifiedName(this.modelFile.getNamespace(), this.ast.name);
}

Expand Down Expand Up @@ -146,15 +141,6 @@ class MapDeclaration extends Declaration {
return this.value;
}

/**
* Returns the MapDeclaration properties
*
* @return {array} the MapDeclaration properties
*/
getProperties() {
return this.ast.properties;
}

/**
* Returns the string representation of this class
* @return {String} the string representation of the class
Expand Down
75 changes: 54 additions & 21 deletions packages/concerto-core/lib/introspect/mapkeytype.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@

'use strict';

const ModelUtil = require('../modelutil');
const { MetaModelNamespace } = require('@accordproject/concerto-metamodel');

const Decorated = require('./decorated');
Expand Down Expand Up @@ -46,7 +47,6 @@ class MapKeyType extends Decorated {
constructor(parent, ast) {
super(ast);
this.parent = parent;
this.type = this.ast.name;
this.process();
}

Expand All @@ -58,6 +58,7 @@ class MapKeyType extends Decorated {
*/
process() {
super.process();
this.processType(this.ast);
}

/**
Expand All @@ -67,30 +68,44 @@ class MapKeyType extends Decorated {
* @protected
*/
validate() {
const declaration = this.getModelFile().getAllDeclarations();
const key = declaration.find(decl => decl.name === this.type);

if (!key?.isConcept?.() &&
!key?.isEnum?.() &&
!key?.isScalarDeclaration?.() &&
!['String', 'DateTime'].includes(this.type)) {
throw new IllegalModelException(`MapKeyType has invalid Type: ${this.type}`);
}

if (key?.isConcept?.() && !key.isIdentified()) {
throw new IllegalModelException(
`ConceptDeclaration must be identified in context of MapKeyType: ${this.type}`
);
}
if (!ModelUtil.isPrimitiveType(this.type)) {
let decl = this.parent.getModelFile().getAllDeclarations().find(d => d.name === this.ast.type?.name);

if (!ModelUtil.isValidMapKeyScalar(decl)) {
throw new IllegalModelException(
`Scalar must be one of StringScalar, DateTimeScalar in context of MapKeyType. Invalid Scalar: ${this.type}, for MapDeclaration ${this.parent.name}`
);
}

if (key?.isScalarDeclaration?.() &&
!(key?.ast.$class === `${MetaModelNamespace}.StringScalar`) &&
!(key?.ast.$class === `${MetaModelNamespace}.DateTimeScalar` )) {
throw new IllegalModelException(
`Scalar must be one of StringScalar, DateTimeScalar in context of MapKeyType. Invalid Scalar: ${this.type}`
);
if (decl?.isConcept?.() || decl?.isClassDeclaration?.()) {
throw new IllegalModelException(
`Invalid Map key type in MapDeclaration ${this.parent.name}. Only String and DateTime types are supported for Map key types`
);
}
}
}

/**
* Sets the Type name for the Map Key
*
* @param {Object} ast - The AST created by the parser
* @private
*/
processType(ast) {
let decl;
switch(this.ast.$class) {
case `${MetaModelNamespace}.DateTimeMapKeyType`:
this.type = 'DateTime';
break;
case `${MetaModelNamespace}.StringMapKeyType`:
this.type = 'String';
break;
case `${MetaModelNamespace}.ObjectMapKeyType`:
decl = this.parent.getModelFile().getAllDeclarations().find(d => d.name === this.ast.type.name);
this.type = decl.getName();
break;
}
}

/**
Expand Down Expand Up @@ -129,6 +144,24 @@ class MapKeyType extends Decorated {
toString() {
return 'MapKeyType {id=' + this.getType() + '}';
}

/**
* Returns true if this class is the definition of a Map Key.
*
* @return {boolean} true if the class is a Map Key
*/
isKey() {
return true;
}

/**
* Returns true if this class is the definition of a Map Value.
*
* @return {boolean} true if the class is a Map Value
*/
isValue() {
return false;
}
}

module.exports = MapKeyType;
99 changes: 81 additions & 18 deletions packages/concerto-core/lib/introspect/mapvaluetype.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,10 @@
'use strict';

const Decorated = require('./decorated');
const { MetaModelNamespace } = require('@accordproject/concerto-metamodel');
const IllegalModelException = require('../../lib/introspect/illegalmodelexception');
const ModelUtil = require('../modelutil');


// Types needed for TypeScript generation.
/* eslint-disable no-unused-vars */
Expand Down Expand Up @@ -43,7 +46,6 @@ class MapValueType extends Decorated {
constructor(parent, ast) {
super(ast);
this.parent = parent;
this.type = this.ast.name;
this.process();
}

Expand All @@ -55,6 +57,7 @@ class MapValueType extends Decorated {
*/
process() {
super.process();
this.processType(this.ast);
}

/**
Expand All @@ -64,23 +67,65 @@ class MapValueType extends Decorated {
* @protected
*/
validate() {
const declarations = this.getModelFile().getAllDeclarations();

const value = declarations.find(decl => decl.name === this.type);

if (!value?.isConcept?.() &&
!value?.isEnum?.() &&
!value?.isAsset?.() &&
!value?.isEvent?.() &&
!value?.isParticipant?.() &&
!value?.isTransaction?.() &&
!value?.isMapDeclaration?.() &&
!value?.isScalarDeclaration?.() &&
!['String', 'Long', 'Integer', 'Double', 'Boolean', 'DateTime'].includes(this.type)) {

throw new IllegalModelException(
`MapPropertyType has invalid Type: ${this.type}`
);
if (!ModelUtil.isPrimitiveType(this.type)) {
let decl = this.parent.getModelFile().getAllDeclarations().find(d => d.name === this.ast.type?.name);

// All declarations, with the exception of MapDeclarations are valid Values.
if(decl.isMapDeclaration?.()) {
throw new IllegalModelException(
`MapDeclaration as Map Type Value is not supported: ${this.type}`
);
}
}
}

/**
* Sets the Type name for the Map Value
*
* @param {Object} ast - The AST created by the parser
* @private
*/
processType(ast) {
let decl;
switch(this.ast.$class) {
case `${MetaModelNamespace}.ObjectMapValueType`:
decl = this.parent.getModelFile().getAllDeclarations().find(d => d.name === this.ast.type.name);

// ObjectMapValueType must have TypeIdentifier.
if (!('type' in ast)) {
throw new IllegalModelException(`ObjectMapValueType must contain property 'type', for MapDeclaration named ${this.parent.name}`);
}

// ObjectMapValueType TypeIdentifier must be properly formed.
if (!('$class' in ast.type) || !('name' in ast.type)) {
throw new IllegalModelException(`ObjectMapValueType type must contain property '$class' and property 'name', for MapDeclaration named ${this.parent.name}`);
}

// And the $class must be valid.
if (ast.type.$class !== '[email protected]') {
throw new IllegalModelException(`ObjectMapValueType type $class must be of TypeIdentifier for MapDeclaration named ${this.parent.name}`);
}

this.type = decl.getName();
break;
case `${MetaModelNamespace}.BooleanMapValueType`:
this.type = 'Boolean';
break;
case `${MetaModelNamespace}.DateTimeMapValueType`:
this.type = 'DateTime';
break;
case `${MetaModelNamespace}.StringMapValueType`:
this.type = 'String';
break;
case `${MetaModelNamespace}.IntegerMapValueType`:
this.type = 'Integer';
break;
case `${MetaModelNamespace}.LongMapValueType`:
this.type = 'Long';
break;
case `${MetaModelNamespace}.DoubleMapValueType`:
this.type = 'Double';
break;
}
}

Expand Down Expand Up @@ -121,6 +166,24 @@ class MapValueType extends Decorated {
return 'MapValueType {id=' + this.getType() + '}';
}

/**
* Returns true if this class is the definition of a Map Key.
*
* @return {boolean} true if the class is a Map Key
*/
isKey() {
return false;
}

/**
* Returns true if this class is the definition of a Map Value.
*
* @return {boolean} true if the class is a Map Value
*/
isValue() {
return true;
}

}

module.exports = MapValueType;
Loading

0 comments on commit f8da8a9

Please sign in to comment.