Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(tools): csharp codegen nullable type support #604

Merged
merged 1 commit into from
Feb 9, 2023
Merged
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
Original file line number Diff line number Diff line change
Expand Up @@ -217,6 +217,7 @@ class CSharpVisitor {
'$class',
'String',
'',
'',
`{ get; } = "${classDeclaration.getFullyQualifiedName()}";`,
parameters
);
Expand Down Expand Up @@ -272,23 +273,24 @@ class CSharpVisitor {
array = '[]';
}

let nullableType = '';
if(field.isOptional() && field.isTypeEnum()){
nullableType = '?';
}

let isIdentifier = field.getName() === field.getParent()?.getIdentifierFieldName();
if (isIdentifier) {
parameters.fileWriter.writeLine(1, '[AccordProject.Concerto.Identifier()]');
}

let fieldType = externalFieldType ? externalFieldType : field.getType();

let nullableType = '';
if(field.isOptional() && fieldType !== 'String'){ //string type is nullable by default.
nullableType = '?';
}

const lines = this.toCSharpProperty(
'public',
field.getParent()?.getName(),
field.getName(),
fieldType+nullableType,
fieldType,
nullableType,
array,
'{ get; set; }',
parameters
Expand Down Expand Up @@ -335,6 +337,7 @@ class CSharpVisitor {
relationship.getParent()?.getName(),
relationship.getName(),
relationship.getType(),
'',
array,
'{ get; set; }',
parameters
Expand All @@ -350,11 +353,12 @@ class CSharpVisitor {
* @param {string} propertyName the Concerto property name
* @param {string} propertyType the Concerto property type
* @param {string} array the array declaration
* @param {string} nullableType the nullable expression ?
* @param {string} getset the getter and setter declaration
* @param {Object} [parameters] - the parameter
* @returns {string} the property declaration
*/
toCSharpProperty(access, parentName, propertyName, propertyType, array, getset, parameters) {
toCSharpProperty(access, parentName, propertyName, propertyType, array, nullableType, getset, parameters) {
const identifier = this.toCSharpIdentifier(parentName, propertyName, parameters);
const type = this.toCSharpType(propertyType, parameters);

Expand All @@ -369,7 +373,7 @@ class CSharpVisitor {
}
}

lines.push(`${access} ${type}${array} ${identifier} ${getset}`);
lines.push(`${access} ${type}${array}${nullableType} ${identifier} ${getset}`);
return lines;
}

Expand Down Expand Up @@ -461,7 +465,7 @@ class CSharpVisitor {
case 'Integer':
return 'int';
case 'concerto.scalar.UUID':
return 'Guid';
return 'System.Guid';
default:
return this.toCSharpIdentifier(undefined, type, parameters);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -230,6 +230,42 @@ describe('CSharpVisitor', function () {
file1.should.match(/public string _Identifier/);
});

it('should add ?(nullable) expression for optional fields except for string type', () => {
const modelManager = new ModelManager({ strict: true });
modelManager.addCTOModel(`
namespace [email protected]
concept Thing identified by thingId {
o String thingId
o String value optional
o Integer nullableIntValue optional
o Integer nonNullableIntValue
o Double nullableDoubleValue optional
o Double nonNullableDoubleValue
o Boolean nullableBooleanValue optional
o Boolean nonNullableBooleanValue
o DateTime nullableDateTimeValue optional
o DateTime nonNullableDateTimeValue
o Long nullableLongValue optional
o Long nonNullableLongValue
}
`);
csharpVisitor.visit(modelManager, { fileWriter, pascalCase: true });
const files = fileWriter.getFilesInMemory();
const file1 = files.get('[email protected]');
file1.should.match(/public string ThingId/);
file1.should.match(/public string Value/);
file1.should.match(/public int\? NullableIntValue/);
file1.should.match(/public int NonNullableIntValue/);
file1.should.match(/public float\? NullableDoubleValue/);
file1.should.match(/public float NonNullableDoubleValue/);
file1.should.match(/public bool\? NullableBooleanValue/);
file1.should.match(/public bool NonNullableBooleanValue/);
file1.should.match(/public System.DateTime\? NullableDateTimeValue/);
file1.should.match(/public System.DateTime NonNullableDateTimeValue/);
file1.should.match(/public long\? NullableLongValue/);
file1.should.match(/public long NonNullableLongValue/);
});

it('should add identifier attributes for concepts with identified by', () => {
const modelManager = new ModelManager({ strict: true });
modelManager.addCTOModel(`
Expand Down Expand Up @@ -262,14 +298,16 @@ describe('CSharpVisitor', function () {

concept Thing {
o UUID ThingId
o UUID SomeOtherId optional
}
`);
csharpVisitor.visit(modelManager, { fileWriter });
const files = fileWriter.getFilesInMemory();
const file1 = files.get('[email protected]');
file1.should.match(/namespace org.acme;/);
file1.should.match(/class Thing/);
file1.should.match(/public Guid ThingId/);
file1.should.match(/public System.Guid ThingId/);
file1.should.match(/public System.Guid? SomeOtherId/);
});

it('should use string for scalar type UUID but with different namespace than concerto.scalar ', () => {
Expand Down Expand Up @@ -310,6 +348,7 @@ describe('CSharpVisitor', function () {

concept Thing {
o SSN ThingId
o SSN SomeOtherId optional
}
`);
csharpVisitor.visit(modelManager, { fileWriter });
Expand All @@ -318,6 +357,7 @@ describe('CSharpVisitor', function () {
file1.should.match(/namespace org.acme;/);
file1.should.match(/class Thing/);
file1.should.match(/public string ThingId/);
file1.should.match(/public string SomeOtherId/);
});
});

Expand Down Expand Up @@ -911,7 +951,27 @@ describe('CSharpVisitor', function () {
mockField.getScalarField.returns(mockScalarField);

csharpVisitor.visitScalarField(mockField, param);
param.fileWriter.writeLine.withArgs(1, 'public Guid someId { get; set; }').calledOnce.should.be.ok;
param.fileWriter.writeLine.withArgs(1, 'public System.Guid someId { get; set; }').calledOnce.should.be.ok;
});

it('should write a line for scalar optional field of type UUID with dotnet type nullable Guid', () => {
const mockField = sinon.createStubInstance(Field);
mockField.isPrimitive.returns(false);
mockField.getName.returns('someId');
mockField.getType.returns('UUID');
mockField.isArray.returns(false);
mockField.isTypeScalar.returns(true);

mockField.getFullyQualifiedTypeName.returns('[email protected]');

const mockScalarField = sinon.createStubInstance(Field);
mockScalarField.getType.returns('String');
mockScalarField.getName.returns('someId');
mockScalarField.isOptional.returns(true);
mockField.getScalarField.returns(mockScalarField);

csharpVisitor.visitScalarField(mockField, param);
param.fileWriter.writeLine.withArgs(1, 'public System.Guid? someId { get; set; }').calledOnce.should.be.ok;
});

it('should write a line for scalar field of type UUID from org specific namespce with dotnet type string', () => {
Expand Down Expand Up @@ -1075,7 +1135,7 @@ describe('CSharpVisitor', function () {
csharpVisitor.toCSharpType('Integer').should.deep.equal('int');
});
it('should return Guid for Scalar type UUID', () => {
csharpVisitor.toCSharpType('concerto.scalar.UUID').should.deep.equal('Guid');
csharpVisitor.toCSharpType('concerto.scalar.UUID').should.deep.equal('System.Guid');
});
it('should return passed in type by default', () => {
csharpVisitor.toCSharpType('Penguin').should.deep.equal('Penguin');
Expand Down