Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
62 changes: 42 additions & 20 deletions lib/codegen/fromcto/golang/golangvisitor.js
Original file line number Diff line number Diff line change
Expand Up @@ -170,31 +170,21 @@ class GoLangVisitor {
array = '[]';
}

if(ModelUtil.isMap(field)) {
const mapDeclaration = field.getModelFile().getType(field.getType());
let mapKeyType = mapDeclaration.getKey().getType();
let mapValueType = mapDeclaration.getValue().getType();

if (mapDeclaration.getModelFile().getType(mapKeyType).isScalarDeclaration?.()) {
mapKeyType = mapDeclaration.getModelFile().getType(mapKeyType).getType();
}

const goKeyType = this.toGoType(mapKeyType);

if (mapDeclaration.getModelFile().getType(mapValueType).isScalarDeclaration?.()) {
mapValueType = mapDeclaration.getModelFile().getType(mapValueType).getType();
}
// we export all fields by capitalizing them
// we strip $ as it is not legal in Go
const name = field.getName().startsWith('$') ? field.getName().substring(1) : field.getName();
const fieldName = ModelUtil.capitalizeFirstLetter(name);

const goValueType = this.toGoType(mapValueType);
if(ModelUtil.isMap(field)) {
const mapTypes = this.resolveMapTypes(field);
const goKeyType = this.toGoType(mapTypes.keyType);
const goValueType = this.toGoType(mapTypes.valueType);

parameters.fileWriter.writeLine(1, `${field.getName()} := make(map[${goKeyType}]${goValueType})` + ' `json:"' + field.getName() + '"`');
parameters.fileWriter.writeLine(1, `${fieldName} map[${goKeyType}]${goValueType}` + ' `json:"' + field.getName() + '"`');
return null;
}

// we export all fields by capitalizing them
// we strip $ as it is not legal in Go
const name = field.getName().startsWith('$') ? field.getName().substring(1) : field.getName();
parameters.fileWriter.writeLine(1, ModelUtil.capitalizeFirstLetter(name) + ' ' + array + this.toGoType(field.getType()) + ' `json:"' + field.getName() + '"`' );
parameters.fileWriter.writeLine(1, fieldName + ' ' + array + this.toGoType(field.getType()) + ' `json:"' + field.getName() + '"`' );
return null;
}

Expand Down Expand Up @@ -264,11 +254,43 @@ class GoLangVisitor {
if(field.getType() === 'DateTime') {
return true;
}
if(typeof field.getParent === 'function' && field.getParent() === classDecl && ModelUtil.isMap(field)) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit · conventions

The map key/value resolution (get the map declaration, follow scalar declarations to the underlying type) is now written out twice: here and in visitField. A small private helper that returns the resolved key and value types would keep the two copies from drifting apart. Optional.

const mapTypes = this.resolveMapTypes(field);
if (mapTypes && (mapTypes.keyType === 'DateTime' || mapTypes.valueType === 'DateTime')) {
return true;
}
}
}
}
return false;
}

/**
* Resolves the key and value types for a map field, unwrapping scalar declarations.
* @param {Field} field - the map field
* @return {Object|null} the resolved key and value types, or null if map declaration not found
* @private
*/
resolveMapTypes(field) {
const mapDeclaration = field.getModelFile().getType(field.getType());
if (!mapDeclaration) {
return null;
}

let keyType = mapDeclaration.getKey().getType();
let valueType = mapDeclaration.getValue().getType();

if (mapDeclaration.getModelFile().getType(keyType)?.isScalarDeclaration?.()) {
keyType = mapDeclaration.getModelFile().getType(keyType).getType();
}

if (mapDeclaration.getModelFile().getType(valueType)?.isScalarDeclaration?.()) {
valueType = mapDeclaration.getModelFile().getType(valueType).getType();
}

return { keyType, valueType };
}

/**
* Converts a Concerto type to a Go Lang type. Primitive types are converted
* everything else is passed through unchanged.
Expand Down
12 changes: 6 additions & 6 deletions test/codegen/__snapshots__/codegen.js.snap
Original file line number Diff line number Diff line change
Expand Up @@ -1059,11 +1059,11 @@ type Company struct {
concerto_1_0_0.Concept
Name string \`json:"name"\`
Headquarters Address \`json:"headquarters"\`
companyProperties := make(map[string]string) \`json:"companyProperties"\`
employeeDirectory := make(map[string]Employee) \`json:"employeeDirectory"\`
employeeTShirtSizes := make(map[string]TShirtSizeType) \`json:"employeeTShirtSizes"\`
employeeProfiles := make(map[string]Concept) \`json:"employeeProfiles"\`
employeeSocialSecurityNumbers := make(map[string]string) \`json:"employeeSocialSecurityNumbers"\`
CompanyProperties map[string]string \`json:"companyProperties"\`
EmployeeDirectory map[string]Employee \`json:"employeeDirectory"\`
EmployeeTShirtSizes map[string]TShirtSizeType \`json:"employeeTShirtSizes"\`
EmployeeProfiles map[string]Concept \`json:"employeeProfiles"\`
EmployeeSocialSecurityNumbers map[string]string \`json:"employeeSocialSecurityNumbers"\`
}
type Department int
const (
Expand Down Expand Up @@ -1097,7 +1097,7 @@ type Person struct {
Ssn string \`json:"ssn"\`
Height float64 \`json:"height"\`
Dob time.Time \`json:"dob"\`
nextOfKin := make(map[string]string) \`json:"nextOfKin"\`
NextOfKin map[string]string \`json:"nextOfKin"\`
}
type Employee struct {
Person
Expand Down
158 changes: 157 additions & 1 deletion test/codegen/fromcto/golang/golangvisitor.js
Original file line number Diff line number Diff line change
Expand Up @@ -382,7 +382,76 @@ describe('GoLangVisitor', function () {

goVisit.visitField(mockField,param);

param.fileWriter.writeLine.withArgs(1, 'dictionary := make(map[string]string) `json:"dictionary"`').calledOnce.should.be.ok;
param.fileWriter.writeLine.withArgs(1, 'Dictionary map[string]string `json:"dictionary"`').calledOnce.should.be.ok;
Comment thread
chaitijain593-prog marked this conversation as resolved.
Resolved
});

it('should handle map field when getType returns null for external or primitive types', () => {
let param = {
fileWriter: mockFileWriter,
};

sandbox.restore();
sandbox.stub(ModelUtil, 'isMap').callsFake(() => {
return true;
});

const mockField = sinon.createStubInstance(Field);
const mockMapDeclaration = sinon.createStubInstance(MapDeclaration);

mockField.getType.returns('Map1');
mockField.getName.returns('properties');
mockField.getModelFile.returns({
getType: sinon.stub().returns(mockMapDeclaration),
});

mockMapDeclaration.getName.returns('Map1');
mockMapDeclaration.getKey.returns({ getType: () => 'String' });
mockMapDeclaration.getValue.returns({ getType: () => 'ExternalType' });
// getType returns null (e.g. cross-namespace or unresolved type)
mockMapDeclaration.getModelFile.returns({
getType: sinon.stub().returns(null),
});

goVisit.visitField(mockField, param);

param.fileWriter.writeLine.withArgs(1, 'Properties map[string]ExternalType `json:"properties"`').calledOnce.should.be.ok;
});

it('should handle map field with leading $ in field name', () => {
let param = {
fileWriter: mockFileWriter,
};

sandbox.restore();
sandbox.stub(ModelUtil, 'isMap').callsFake(() => {
return true;
});

const mockField = sinon.createStubInstance(Field);
const getType = sinon.stub();

mockField.dummy = 'Dummy Value';
mockField.getModelFile.returns({ getType: getType });

const mockMapDeclaration = sinon.createStubInstance(MapDeclaration);
const getKeyType = sinon.stub();
const getValueType = sinon.stub();

mockField.getType.returns('Map1');

getType.returns(mockMapDeclaration);
getKeyType.returns('String');
getValueType.returns('String');
mockField.getName.returns('$dictionary');
mockMapDeclaration.getName.returns('Map1');
mockMapDeclaration.isMapDeclaration.returns(true);
mockMapDeclaration.getKey.returns({ getType: getKeyType });
mockMapDeclaration.getValue.returns({ getType: getValueType });
mockMapDeclaration.getModelFile.returns({ getType: getType });

goVisit.visitField(mockField, param);

param.fileWriter.writeLine.withArgs(1, 'Dictionary map[string]string `json:"$dictionary"`').calledOnce.should.be.ok;
});

});
Expand Down Expand Up @@ -477,6 +546,93 @@ describe('GoLangVisitor', function () {

goVisit.containsDateTimeField(mockModelFileDefinition).should.deep.equal(false);
});

it('should return true if the model file contains a map field with DateTime value', () => {
const mm = new ModelManager();
const modelFile = mm.addCTOModel(`
namespace org.acme@1.0.0
map TimestampMap {
o String
o DateTime
}
concept EventLog {
o TimestampMap timestamps
}
`);
goVisit.containsDateTimeField(modelFile).should.be.true;
});

it('should return true if the model file contains a map field with a scalar extending DateTime', () => {
const mm = new ModelManager();
const modelFile = mm.addCTOModel(`
namespace org.acme@1.0.0
scalar EventTime extends DateTime
map EventMap {
o String
o EventTime
}
concept LogEntry {
o EventMap events
}
`);
goVisit.containsDateTimeField(modelFile).should.be.true;
});

it('should return false if the model file contains a map field without DateTime', () => {
const mm = new ModelManager();
const modelFile = mm.addCTOModel(`
namespace org.acme@1.0.0
map StringMap {
o String
o String
}
concept LogEntry {
o StringMap items
}
`);
goVisit.containsDateTimeField(modelFile).should.be.false;
});

it('should write import "time" when the only DateTime in the model is in a map field', () => {
const mm = new ModelManager();
const modelFile = mm.addCTOModel(`
namespace org.acme@1.0.0
map TimestampMap {
o String
o DateTime
}
concept EventLog {
o TimestampMap timestamps
}
`);
let param = {
fileWriter: mockFileWriter,
};
goVisit.visitModelFile(modelFile, param);
param.fileWriter.writeLine.withArgs(0, 'import "time"').calledOnce.should.be.ok;
});

it('should return false for child class inheriting DateTime map from another namespace', () => {
const mm = new ModelManager();
mm.addCTOModel(`
namespace base@1.0.0
map TimestampMap {
o String
o DateTime
}
concept Parent {
o TimestampMap timestamps
}
`);
const childModelFile = mm.addCTOModel(`
namespace child@1.0.0
import base@1.0.0.Parent
concept Child extends Parent {
o String name
}
`);
goVisit.containsDateTimeField(childModelFile).should.be.false;
});
});

describe('toGoType', () => {
Expand Down
Loading