Skip to content

Commit

Permalink
feat: rendering only not-required value-typed properties as nullable (#…
Browse files Browse the repository at this point in the history
  • Loading branch information
slowikowskiarkadiusz authored May 18, 2022
1 parent 446d25a commit 21953c8
Show file tree
Hide file tree
Showing 9 changed files with 146 additions and 19 deletions.
17 changes: 17 additions & 0 deletions examples/csharp-generate-required-properties/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# C# Generate required properties

An example showing that when a property of a not-nullable type (value type) is set as required, it's not going to be made into nullable.

## How to run this example

Run this example using:

```sh
npm i && npm run start
```

If you are on Windows, use the `start:windows` script instead:

```sh
npm i && npm run start:windows
```
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`Should be able to render required properties without the question mark at the end if the type of it is not nullable and should log expected output to console 1`] = `
Array [
"public class Root
{
private bool requiredBoolean;
private bool? notRequiredBoolean;
private string requiredString;
private string notRequiredString;
public bool RequiredBoolean
{
get { return requiredBoolean; }
set { requiredBoolean = value; }
}
public bool? NotRequiredBoolean
{
get { return notRequiredBoolean; }
set { notRequiredBoolean = value; }
}
public string RequiredString
{
get { return requiredString; }
set { requiredString = value; }
}
public string NotRequiredString
{
get { return notRequiredString; }
set { notRequiredString = value; }
}
}",
]
`;
14 changes: 14 additions & 0 deletions examples/csharp-generate-required-properties/index.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
const spy = jest.spyOn(global.console, 'log').mockImplementation(() => { return; });
import {generate} from './index';

describe('Should be able to render required properties without the question mark at the end if the type of it is not nullable', () => {
afterAll(() => {
jest.restoreAllMocks();
});
test('and should log expected output to console', async () => {
await generate();
//Generate is called 2x, so even though we expect 1 model, we double it
expect(spy.mock.calls.length).toEqual(2);
expect(spy.mock.calls[1]).toMatchSnapshot();
});
});
31 changes: 31 additions & 0 deletions examples/csharp-generate-required-properties/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { CSharpGenerator } from '../../src';

const generator = new CSharpGenerator();
const jsonSchemaDraft7 = {
$schema: 'http://json-schema.org/draft-07/schema#',
type: 'object',
additionalProperties: false,
properties: {
requiredBoolean: {
type: 'boolean',
},
notRequiredBoolean: {
type: 'boolean',
},
requiredString: {
type: 'string'
},
notRequiredString: {
type: 'string'
},
},
required: ['requiredBoolean', 'requiredString']
};

export async function generate(): Promise<void> {
const models = await generator.generate(jsonSchemaDraft7);
for (const model of models) {
console.log(model.result);
}
}
generate();
10 changes: 10 additions & 0 deletions examples/csharp-generate-required-properties/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

12 changes: 12 additions & 0 deletions examples/csharp-generate-required-properties/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"config": {
"example_name": "csharp-generate-required-properties"
},
"scripts": {
"install": "cd ../.. && npm i",
"start": "../../node_modules/.bin/ts-node --cwd ../../ ./examples/$npm_package_config_example_name/index.ts",
"start:windows": "..\\..\\node_modules\\.bin\\ts-node --cwd ..\\..\\ .\\examples\\%npm_package_config_example_name%\\index.ts",
"test": "../../node_modules/.bin/jest --config=../../jest.config.js ./examples/$npm_package_config_example_name/index.spec.ts",
"test:windows": "..\\..\\node_modules\\.bin\\jest --config=..\\..\\jest.config.js examples/%npm_package_config_example_name%/index.spec.ts"
}
}
28 changes: 17 additions & 11 deletions src/generators/csharp/CSharpRenderer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,47 +50,49 @@ export abstract class CSharpRenderer extends AbstractRenderer<CSharpOptions> {
return this.runPreset('property', { propertyName, property, options, type });
}

renderType(model: CommonModel): string {
renderType(model: CommonModel, modelName?: string): string {
if (model.$ref !== undefined) {
return this.nameType(model.$ref);
}

const isRequired = modelName ? this.model.isRequired(modelName) || this.model.required?.map(x => this.nameProperty(x)).includes(modelName) : false;

if (Array.isArray(model.type)) {
return model.type.length > 1 ? 'dynamic' : `${this.toCSharpType(model.type[0], model)}`;
return model.type.length > 1 ? 'dynamic' : `${this.toCSharpType(model.type[0], model, modelName, isRequired)}`;
}

return this.toCSharpType(model.type, model);
return this.toCSharpType(model.type, model, modelName, isRequired);
}

renderComments(lines: string | string[]): string {
lines = FormatHelpers.breakLines(lines);
return lines.map(line => `// ${line}`).join('\n');
}

toCSharpType(type: string | undefined, model: CommonModel): string {
toCSharpType(type: string | undefined, model: CommonModel, modelName?: string, isRequired?: boolean): string {
switch (type) {
case 'integer':
case 'int32':
return 'int?';
return `int${this.questionMark(isRequired)}`;
case 'long':
case 'int64':
return 'long?';
return `long${this.questionMark(isRequired)}`;
case 'boolean':
return 'bool?';
return `bool${this.questionMark(isRequired)}`;
case 'date':
case 'time':
case 'dateTime':
case 'date-time':
return 'System.DateTime?';
return `System.DateTime${this.questionMark(isRequired)}`;
case 'string':
case 'password':
case 'byte':
return 'string';
case 'float':
return 'float?';
return `float${this.questionMark(isRequired)}`;
case 'double':
case 'number':
return 'double?';
return `double${this.questionMark(isRequired)}`;
case 'binary':
return 'byte[]';
case 'object':
Expand All @@ -105,7 +107,7 @@ export abstract class CSharpRenderer extends AbstractRenderer<CSharpOptions> {
arrayItemModel = CommonModel.mergeCommonModels(arrayItemModel, model.additionalItems, {});
}
}
const newType = arrayItemModel ? this.renderType(arrayItemModel as CommonModel) : 'dynamic';
const newType = arrayItemModel ? this.renderType(arrayItemModel as CommonModel, modelName) : 'dynamic';
if (this.options.collectionType && this.options.collectionType === 'List') {
return `IEnumerable<${newType}>`;
}
Expand All @@ -114,4 +116,8 @@ export abstract class CSharpRenderer extends AbstractRenderer<CSharpOptions> {
default: return 'dynamic';
}
}

private questionMark(isRequired?: boolean): string {
return isRequired ? '' : '?';
}
}
4 changes: 2 additions & 2 deletions src/generators/csharp/renderers/ClassRenderer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ export const CSHARP_DEFAULT_CLASS_PRESET: CsharpClassPreset = {
},
async property({ renderer, propertyName, options, property, type }) {
propertyName = renderer.nameProperty(propertyName, property);
let propertyType = renderer.renderType(property);
let propertyType = renderer.renderType(property, propertyName);
if (type === PropertyType.additionalProperty || type === PropertyType.patternProperties) {
propertyType = `Dictionary<string, ${propertyType}>`;
}
Expand All @@ -120,7 +120,7 @@ export const CSHARP_DEFAULT_CLASS_PRESET: CsharpClassPreset = {
},
async accessor({ renderer, propertyName, options, property, type }) {
const formattedAccessorName = pascalCase(renderer.nameProperty(propertyName, property));
let propertyType = renderer.renderType(property);
let propertyType = renderer.renderType(property, propertyName);
if (type === PropertyType.additionalProperty || type === PropertyType.patternProperties) {
propertyType = `Dictionary<string, ${propertyType}>`;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ exports[`CSharpGenerator should render \`class\` type 1`] = `
private string streetName;
private string city;
private string state;
private double? houseNumber;
private double houseNumber;
private bool? marriage;
private dynamic members;
private dynamic[] tupleType;
Expand All @@ -104,7 +104,7 @@ exports[`CSharpGenerator should render \`class\` type 1`] = `
set { state = value; }
}
public double? HouseNumber
public double HouseNumber
{
get { return houseNumber; }
set { houseNumber = value; }
Expand Down Expand Up @@ -154,7 +154,7 @@ exports[`CSharpGenerator should render \`class\` type 2`] = `
private string streetName;
private string city;
private string state;
private double? houseNumber;
private double houseNumber;
private bool? marriage;
private dynamic members;
private dynamic[] tupleType;
Expand All @@ -180,7 +180,7 @@ exports[`CSharpGenerator should render \`class\` type 2`] = `
set { state = value; }
}
public double? HouseNumber
public double HouseNumber
{
get { return houseNumber; }
set { houseNumber = value; }
Expand Down Expand Up @@ -468,7 +468,7 @@ exports[`CSharpGenerator should render models and their dependencies 1`] = `
private string streetName;
private string city;
private string state;
private double? houseNumber;
private double houseNumber;
private bool? marriage;
private dynamic members;
private dynamic[] arrayType;
Expand All @@ -494,7 +494,7 @@ exports[`CSharpGenerator should render models and their dependencies 1`] = `
set { state = value; }
}
public double? HouseNumber
public double HouseNumber
{
get { return houseNumber; }
set { houseNumber = value; }
Expand Down

0 comments on commit 21953c8

Please sign in to comment.