diff --git a/lib/codegen/fromcto/java/javavisitor.js b/lib/codegen/fromcto/java/javavisitor.js index 08160891..a3f296cf 100644 --- a/lib/codegen/fromcto/java/javavisitor.js +++ b/lib/codegen/fromcto/java/javavisitor.js @@ -288,7 +288,7 @@ class JavaVisitor { break; default: if (ModelUtil.isMap(field)) { - const decl = field.getModelFile().getType(field.ast.type.name); + const decl = field.getModelFile().getType(field.getType()); parameters.fileWriter.writeLine(1, `private Map<${this.toJavaType(decl.getKey().getType())}, ${this.toJavaType(decl.getValue().getType())}> ${field.getName()} = new HashMap<>();`); } else { parameters.fileWriter.writeLine(1, 'private ' + fieldType + ' ' + fieldName + ';' ); diff --git a/lib/codegen/fromcto/protobuf/protobufvisitor.js b/lib/codegen/fromcto/protobuf/protobufvisitor.js index 24858134..9cde4e0a 100644 --- a/lib/codegen/fromcto/protobuf/protobufvisitor.js +++ b/lib/codegen/fromcto/protobuf/protobufvisitor.js @@ -57,12 +57,12 @@ class ProtobufVisitor { /** * Transform a Concerto primitive type into a Proto3 one. - * @param {Object} field - the Concerto primitive type + * @param {Object} type - the Concerto primitive type * @return {Object} the Proto3 primitive type * @public */ - concertoToProto3PrimitiveType(field) { - switch (field.getType()) { + concertoToProto3PrimitiveType(type) { + switch (type) { case 'String': return 'string'; case 'Double': @@ -394,9 +394,23 @@ class ProtobufVisitor { debug('entering visitField', field.getName()); const preposition = this.concertoToProto3FieldRule(field); + + if (ModelUtil.isMap(field)) { + const mapDeclaration = field.getModelFile().getType(field.getType()); + const keyType = mapDeclaration.getKey().getType(); + const valueType = mapDeclaration.getValue().getType(); + + let key = ModelUtil.isPrimitiveType(keyType) ? this.concertoToProto3PrimitiveType(keyType) : keyType; + let value = ModelUtil.isPrimitiveType(valueType) ? this.concertoToProto3PrimitiveType(valueType) : valueType; + + parameters.fileWriter.writeLine(0, ` ${preposition ? `${preposition} ` : ''}map<${key}, ${value}> ${mapDeclaration.getName()} = ${parameters.fieldIndex ?? '0'};`); + + return; + } + const type = field.isPrimitive() // Primitive Concerto types are mapped to specific Proto3 types. - ? this.concertoToProto3PrimitiveType(field) + ? this.concertoToProto3PrimitiveType(field.getType()) // The rest are references to classes and enums. : this.concertoToProto3MessageOrEnumType(field); // Proto3 is not happy with the "$" sign, so we are replacing it with an "_". diff --git a/test/codegen/__snapshots__/codegen.js.snap b/test/codegen/__snapshots__/codegen.js.snap index 35c8d7ed..3a3846ab 100644 --- a/test/codegen/__snapshots__/codegen.js.snap +++ b/test/codegen/__snapshots__/codegen.js.snap @@ -3681,11 +3681,11 @@ import "google/protobuf/timestamp.proto"; import "org.acme.hr.base.v.proto"; message Company { - optional CompanyProperties companyProperties = 1; - optional EmployeeDirectory employeeDirectory = 2; - optional EmployeeProfiles employeeProfiles = 3; - optional EmployeeSocialSecurityNumbers employeeSocialSecurityNumbers = 4; - optional EmployeeTShirtSizes employeeTShirtSizes = 5; + optional map CompanyProperties = 1; + optional map EmployeeDirectory = 2; + optional map EmployeeProfiles = 3; + optional map EmployeeSocialSecurityNumbers = 4; + optional map EmployeeTShirtSizes = 5; Address headquarters = 6; string name = 7; } @@ -3735,7 +3735,7 @@ message Employee { string lastName = 9; optional string manager = 10; optional string middleNames = 11; - NextOfKin nextOfKin = 12; + map NextOfKin = 12; sint64 numDependents = 13; Address officeAddress = 14; bool retired = 15; @@ -3760,7 +3760,7 @@ message Contractor { string lastName = 7; optional string manager = 8; optional string middleNames = 9; - NextOfKin nextOfKin = 10; + map NextOfKin = 10; string ssn = 11; } @@ -3776,7 +3776,7 @@ message Manager { string lastName = 9; optional string manager = 10; optional string middleNames = 11; - NextOfKin nextOfKin = 12; + map NextOfKin = 12; sint64 numDependents = 13; Address officeAddress = 14; repeated string reports = 15; @@ -9314,11 +9314,11 @@ import "google/protobuf/timestamp.proto"; import "org.acme.hr.base.v1_0_0.proto"; message Company { - optional CompanyProperties companyProperties = 1; - optional EmployeeDirectory employeeDirectory = 2; - optional EmployeeProfiles employeeProfiles = 3; - optional EmployeeSocialSecurityNumbers employeeSocialSecurityNumbers = 4; - optional EmployeeTShirtSizes employeeTShirtSizes = 5; + optional map CompanyProperties = 1; + optional map EmployeeDirectory = 2; + optional map EmployeeProfiles = 3; + optional map EmployeeSocialSecurityNumbers = 4; + optional map EmployeeTShirtSizes = 5; Address headquarters = 6; string name = 7; } @@ -9368,7 +9368,7 @@ message Employee { string lastName = 9; optional string manager = 10; optional string middleNames = 11; - NextOfKin nextOfKin = 12; + map NextOfKin = 12; sint64 numDependents = 13; Address officeAddress = 14; bool retired = 15; @@ -9393,7 +9393,7 @@ message Contractor { string lastName = 7; optional string manager = 8; optional string middleNames = 9; - NextOfKin nextOfKin = 10; + map NextOfKin = 10; string ssn = 11; } @@ -9409,7 +9409,7 @@ message Manager { string lastName = 9; optional string manager = 10; optional string middleNames = 11; - NextOfKin nextOfKin = 12; + map NextOfKin = 12; sint64 numDependents = 13; Address officeAddress = 14; repeated string reports = 15; diff --git a/test/codegen/fromcto/protobuf/protobufvisitor.js b/test/codegen/fromcto/protobuf/protobufvisitor.js index 6fe741f2..077956df 100644 --- a/test/codegen/fromcto/protobuf/protobufvisitor.js +++ b/test/codegen/fromcto/protobuf/protobufvisitor.js @@ -30,6 +30,7 @@ const ModelManager = require('@accordproject/concerto-core').ModelManager; const ModelLoader = require('@accordproject/concerto-core').ModelLoader; const AssetDeclaration = require('@accordproject/concerto-core').AssetDeclaration; const ClassDeclaration = require('@accordproject/concerto-core').ClassDeclaration; +const MapDeclaration = require('@accordproject/concerto-core').MapDeclaration; const EnumDeclaration = require('@accordproject/concerto-core').EnumDeclaration; const EnumValueDeclaration = require('@accordproject/concerto-core').EnumValueDeclaration; const Field = require('@accordproject/concerto-core').Field; @@ -37,6 +38,9 @@ const RelationshipDeclaration = require('@accordproject/concerto-core').Relation const TransactionDeclaration = require('@accordproject/concerto-core').TransactionDeclaration; const FileWriter = require('@accordproject/concerto-util').FileWriter; const { InMemoryWriter } = require('@accordproject/concerto-util'); +const ModelUtil = require('@accordproject/concerto-core').ModelUtil; +let sandbox = sinon.createSandbox(); + describe('ProtobufVisitor', function () { let protobufVisitor; @@ -277,6 +281,12 @@ describe('ProtobufVisitor', function () { }); describe('visitField', () => { + before(() => { + sandbox.stub(ModelUtil, 'isMap').callsFake(() => { + return false; + }); + }); + it('should return an object representing a Proto3 field coming from a Concerto String', () => { let param = { fileWriter: mockFileWriter @@ -396,10 +406,313 @@ describe('ProtobufVisitor', function () { param.fileWriter.writeLine.callCount.should.deep.equal(1); param.fileWriter.writeLine.getCall(0).args.should.deep.equal([0, ' repeated string Bob = 0;']); }); + + it('should return an object representing a Proto3 field that is a map ', () => { + let param = { + fileWriter: mockFileWriter + }; + + sandbox.restore(); + + sandbox.stub(ModelUtil, 'isMap').callsFake(() => { + return true; + }); + + const getType = sinon.stub(); + const mockMapDeclaration = sinon.createStubInstance(MapDeclaration); + const mockField = sinon.createStubInstance(Field); + const findStub = sinon.stub(); + const getKeyType = sinon.stub(); + const getValueType = sinon.stub(); + + mockField.getModelFile.returns({ getType: getType }); + getType.returns(mockMapDeclaration); + findStub.returns(mockMapDeclaration); + getKeyType.returns('String'); + getValueType.returns('String'); + mockMapDeclaration.getName.returns('map1'); + mockMapDeclaration.isMapDeclaration.returns(true); + mockMapDeclaration.getKey.returns({ getType: getKeyType }); + mockMapDeclaration.getValue.returns({ getType: getValueType }); + + protobufVisitor.visitField(mockField, param); + param.fileWriter.writeLine.callCount.should.deep.equal(1); + param.fileWriter.writeLine.getCall(0).args.should.deep.equal([0, ' map map1 = 0;']); + + sandbox.restore(); + }); + + it('should return an object representing a Proto3 field that is a map ', () => { + let param = { + fileWriter: mockFileWriter + }; + + sandbox.stub(ModelUtil, 'isMap').callsFake(() => { + return true; + }); + + const getType = sinon.stub(); + const mockMapDeclaration = sinon.createStubInstance(MapDeclaration); + const mockField = sinon.createStubInstance(Field); + const findStub = sinon.stub(); + const getKeyType = sinon.stub(); + const getValueType = sinon.stub(); + + mockField.getModelFile.returns({ getType: getType }); + getType.returns(mockMapDeclaration); + findStub.returns(mockMapDeclaration); + getKeyType.returns('String'); + getValueType.returns('DateTime'); + mockMapDeclaration.getName.returns('map1'); + mockMapDeclaration.isMapDeclaration.returns(true); + mockMapDeclaration.getKey.returns({ getType: getKeyType }); + mockMapDeclaration.getValue.returns({ getType: getValueType }); + + protobufVisitor.visitField(mockField, param); + param.fileWriter.writeLine.callCount.should.deep.equal(1); + param.fileWriter.writeLine.getCall(0).args.should.deep.equal([0, ' map map1 = 0;']); + + sandbox.restore(); + }); + + it('should return an object representing a Proto3 field that is a map ', () => { + let param = { + fileWriter: mockFileWriter + }; + + sandbox.stub(ModelUtil, 'isMap').callsFake(() => { + return true; + }); + + const getType = sinon.stub(); + const mockMapDeclaration = sinon.createStubInstance(MapDeclaration); + const mockField = sinon.createStubInstance(Field); + const findStub = sinon.stub(); + const getKeyType = sinon.stub(); + const getValueType = sinon.stub(); + + mockField.getModelFile.returns({ getType: getType }); + getType.returns(mockMapDeclaration); + findStub.returns(mockMapDeclaration); + getKeyType.returns('String'); + getValueType.returns('SSN'); + mockMapDeclaration.getName.returns('map1'); + mockMapDeclaration.isMapDeclaration.returns(true); + mockMapDeclaration.getKey.returns({ getType: getKeyType }); + mockMapDeclaration.getValue.returns({ getType: getValueType }); + + protobufVisitor.visitField(mockField, param); + param.fileWriter.writeLine.callCount.should.deep.equal(1); + param.fileWriter.writeLine.getCall(0).args.should.deep.equal([0, ' map map1 = 0;']); + + sandbox.restore(); + }); + + it('should return an object representing a Proto3 field that is a map ', () => { + let param = { + fileWriter: mockFileWriter + }; + + sandbox.stub(ModelUtil, 'isMap').callsFake(() => { + return true; + }); + + const getType = sinon.stub(); + const mockMapDeclaration = sinon.createStubInstance(MapDeclaration); + const mockField = sinon.createStubInstance(Field); + const findStub = sinon.stub(); + const getKeyType = sinon.stub(); + const getValueType = sinon.stub(); + + mockField.getModelFile.returns({ getType: getType }); + getType.returns(mockMapDeclaration); + findStub.returns(mockMapDeclaration); + getKeyType.returns('String'); + getValueType.returns('Concept'); + mockMapDeclaration.getName.returns('map1'); + mockMapDeclaration.isMapDeclaration.returns(true); + mockMapDeclaration.getKey.returns({ getType: getKeyType }); + mockMapDeclaration.getValue.returns({ getType: getValueType }); + + protobufVisitor.visitField(mockField, param); + param.fileWriter.writeLine.callCount.should.deep.equal(1); + param.fileWriter.writeLine.getCall(0).args.should.deep.equal([0, ' map map1 = 0;']); + + sandbox.restore(); + }); + + it('should return an object representing a Proto3 field that is a map ', () => { + let param = { + fileWriter: mockFileWriter + }; + + sandbox.stub(ModelUtil, 'isMap').callsFake(() => { + return true; + }); + + const getType = sinon.stub(); + const mockMapDeclaration = sinon.createStubInstance(MapDeclaration); + const mockField = sinon.createStubInstance(Field); + const findStub = sinon.stub(); + const getKeyType = sinon.stub(); + const getValueType = sinon.stub(); + + mockField.getModelFile.returns({ getType: getType }); + getType.returns(mockMapDeclaration); + findStub.returns(mockMapDeclaration); + getKeyType.returns('SSN'); + getValueType.returns('String'); + mockMapDeclaration.getName.returns('map1'); + mockMapDeclaration.isMapDeclaration.returns(true); + mockMapDeclaration.getKey.returns({ getType: getKeyType }); + mockMapDeclaration.getValue.returns({ getType: getValueType }); + + protobufVisitor.visitField(mockField, param); + param.fileWriter.writeLine.callCount.should.deep.equal(1); + param.fileWriter.writeLine.getCall(0).args.should.deep.equal([0, ' map map1 = 0;']); + + sandbox.restore(); + }); + + it('should return an object representing a Proto3 field that is a map ', () => { + let param = { + fileWriter: mockFileWriter + }; + + sandbox.restore(); + + sandbox.stub(ModelUtil, 'isMap').callsFake(() => { + return true; + }); + + const getType = sinon.stub(); + const mockMapDeclaration = sinon.createStubInstance(MapDeclaration); + const mockField = sinon.createStubInstance(Field); + const findStub = sinon.stub(); + const getKeyType = sinon.stub(); + const getValueType = sinon.stub(); + + + mockField.getModelFile.returns({ getType: getType }); + getType.returns(mockMapDeclaration); + findStub.returns(mockMapDeclaration); + getKeyType.returns('SSN'); + getValueType.returns('Employee'); + mockMapDeclaration.getName.returns('map1'); + mockMapDeclaration.isMapDeclaration.returns(true); + mockMapDeclaration.getKey.returns({ getType: getKeyType }); + mockMapDeclaration.getValue.returns({ getType: getValueType }); + + protobufVisitor.visitField(mockField, param); + param.fileWriter.writeLine.callCount.should.deep.equal(1); + param.fileWriter.writeLine.getCall(0).args.should.deep.equal([0, ' map map1 = 0;']); + + sandbox.restore(); + }); + + it('should return an object representing a Proto3 field that is a map ', () => { + let param = { + fileWriter: mockFileWriter + }; + + sandbox.restore(); + + sandbox.stub(ModelUtil, 'isMap').callsFake(() => { + return true; + }); + + const getType = sinon.stub(); + const mockMapDeclaration = sinon.createStubInstance(MapDeclaration); + const mockField = sinon.createStubInstance(Field); + const findStub = sinon.stub(); + const getKeyType = sinon.stub(); + const getValueType = sinon.stub(); + + mockField.getModelFile.returns({ getType: getType }); + getType.returns(mockMapDeclaration); + findStub.returns(mockMapDeclaration); + getKeyType.returns('String'); + getValueType.returns('Double'); + mockMapDeclaration.getName.returns('map1'); + mockMapDeclaration.isMapDeclaration.returns(true); + mockMapDeclaration.getKey.returns({ getType: getKeyType }); + mockMapDeclaration.getValue.returns({ getType: getValueType }); + + protobufVisitor.visitField(mockField, param); + param.fileWriter.writeLine.callCount.should.deep.equal(1); + param.fileWriter.writeLine.getCall(0).args.should.deep.equal([0, ' map map1 = 0;']); + }); + + it('should return an object representing a Proto3 field that is a map ', () => { + let param = { + fileWriter: mockFileWriter + }; + + sandbox.restore(); + + sandbox.stub(ModelUtil, 'isMap').callsFake(() => { + return true; + }); + + const getType = sinon.stub(); + const mockMapDeclaration = sinon.createStubInstance(MapDeclaration); + const mockField = sinon.createStubInstance(Field); + const findStub = sinon.stub(); + const getKeyType = sinon.stub(); + const getValueType = sinon.stub(); + + mockField.getModelFile.returns({ getType: getType }); + getType.returns(mockMapDeclaration); + findStub.returns(mockMapDeclaration); + getKeyType.returns('String'); + getValueType.returns('Integer'); + mockMapDeclaration.getName.returns('map1'); + mockMapDeclaration.isMapDeclaration.returns(true); + mockMapDeclaration.getKey.returns({ getType: getKeyType }); + mockMapDeclaration.getValue.returns({ getType: getValueType }); + + protobufVisitor.visitField(mockField, param); + param.fileWriter.writeLine.callCount.should.deep.equal(1); + param.fileWriter.writeLine.getCall(0).args.should.deep.equal([0, ' map map1 = 0;']); + }); + + it('should return an object representing a Proto3 field that is a map ', () => { + let param = { + fileWriter: mockFileWriter + }; + + sandbox.restore(); + + sandbox.stub(ModelUtil, 'isMap').callsFake(() => { + return true; + }); + + const getType = sinon.stub(); + const mockMapDeclaration = sinon.createStubInstance(MapDeclaration); + const mockField = sinon.createStubInstance(Field); + const findStub = sinon.stub(); + const getKeyType = sinon.stub(); + const getValueType = sinon.stub(); + + mockField.getModelFile.returns({ getType: getType }); + getType.returns(mockMapDeclaration); + findStub.returns(mockMapDeclaration); + getKeyType.returns('String'); + getValueType.returns('Long'); + mockMapDeclaration.getName.returns('map1'); + mockMapDeclaration.isMapDeclaration.returns(true); + mockMapDeclaration.getKey.returns({ getType: getKeyType }); + mockMapDeclaration.getValue.returns({ getType: getValueType }); + + protobufVisitor.visitField(mockField, param); + param.fileWriter.writeLine.callCount.should.deep.equal(1); + param.fileWriter.writeLine.getCall(0).args.should.deep.equal([0, ' map map1 = 0;']); + }); }); describe('visit CTO file', () => { it('should process an APAP protocol CTO file', async () => { + sandbox.restore(); const modelManager = await ModelLoader.loadModelManager( [path.resolve(__dirname, './data/apapProtocol.cto')] ); diff --git a/types/lib/codegen/fromcto/protobuf/protobufvisitor.d.ts b/types/lib/codegen/fromcto/protobuf/protobufvisitor.d.ts index 44ebc773..b0afbff9 100644 --- a/types/lib/codegen/fromcto/protobuf/protobufvisitor.d.ts +++ b/types/lib/codegen/fromcto/protobuf/protobufvisitor.d.ts @@ -23,11 +23,11 @@ declare class ProtobufVisitor { public concertoToProto3FieldRule(field: any): any; /** * Transform a Concerto primitive type into a Proto3 one. - * @param {Object} field - the Concerto primitive type + * @param {Object} type - the Concerto primitive type * @return {Object} the Proto3 primitive type * @public */ - public concertoToProto3PrimitiveType(field: any): any; + public concertoToProto3PrimitiveType(type: any): any; /** * Transform a Concerto class or enum type into a Proto3 message or enum one. * @param {Object} field - the Concerto class or enum type