-
-
Notifications
You must be signed in to change notification settings - Fork 63
Fix numeric format with pattern quirk - infer type from format for all numeric types #869
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
Changes from all commits
e061e12
8ce7259
2c6327f
21a05c3
5373ccb
8ea8a21
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -19,6 +19,7 @@ public CustomCSharpClientGenerator Create() | |
| } | ||
| } | ||
|
|
||
| FixMissingTypesWithIntegerFormat(); | ||
| ApplyCustomIntegerType(); | ||
|
|
||
| var csharpClientGeneratorSettings = new CSharpClientGeneratorSettings | ||
|
|
@@ -64,6 +65,104 @@ public CustomCSharpClientGenerator Create() | |
| return generator; | ||
| } | ||
|
|
||
| private void FixMissingTypesWithIntegerFormat() | ||
| { | ||
| ProcessSchemasForMissingTypes(document.Components.Schemas); | ||
|
|
||
| foreach (var path in document.Paths) | ||
| { | ||
| if (path.Value == null) continue; | ||
|
|
||
| foreach (var operation in path.Value.Values) | ||
| { | ||
| if (operation == null) continue; | ||
|
|
||
| foreach (var parameter in operation.Parameters) | ||
| { | ||
| FixSchemaTypeFromFormat(parameter.ActualSchema); | ||
| } | ||
|
|
||
| if (operation.RequestBody?.Content != null) | ||
| { | ||
| foreach (var content in operation.RequestBody.Content.Values) | ||
| { | ||
| ProcessSchemaForMissingTypes(content.Schema); | ||
| } | ||
| } | ||
|
|
||
| foreach (var response in operation.Responses.Values) | ||
| { | ||
| if (response.Content != null) | ||
| { | ||
| foreach (var content in response.Content.Values) | ||
| { | ||
| ProcessSchemaForMissingTypes(content.Schema); | ||
| } | ||
| } | ||
| } | ||
|
Comment on lines
+93
to
+102
|
||
| } | ||
|
Comment on lines
+76
to
+103
|
||
| } | ||
|
Comment on lines
+72
to
+104
|
||
| } | ||
|
|
||
| private void ProcessSchemasForMissingTypes(IDictionary<string, JsonSchema> schemas) | ||
| { | ||
| foreach (var schema in schemas.Values) | ||
| { | ||
| ProcessSchemaForMissingTypes(schema); | ||
| } | ||
| } | ||
|
|
||
| private void ProcessSchemaForMissingTypes(JsonSchema? schema) | ||
| { | ||
| if (schema == null) return; | ||
|
|
||
| var actualSchema = schema.ActualSchema; | ||
|
|
||
| FixSchemaTypeFromFormat(actualSchema); | ||
|
|
||
| foreach (var property in actualSchema.Properties.Values) | ||
| { | ||
| ProcessSchemaForMissingTypes(property); | ||
| } | ||
|
|
||
| if (actualSchema.Item != null) | ||
| { | ||
| ProcessSchemaForMissingTypes(actualSchema.Item); | ||
| } | ||
|
|
||
| if (actualSchema.AdditionalPropertiesSchema != null) | ||
| { | ||
| ProcessSchemaForMissingTypes(actualSchema.AdditionalPropertiesSchema); | ||
| } | ||
|
|
||
| var subSchemas = actualSchema.AllOf | ||
| .Concat(actualSchema.OneOf) | ||
| .Concat(actualSchema.AnyOf) | ||
| .ToArray(); | ||
|
|
||
| foreach (var subSchema in subSchemas) | ||
| { | ||
| ProcessSchemaForMissingTypes(subSchema); | ||
| } | ||
| } | ||
|
|
||
| private void FixSchemaTypeFromFormat(JsonSchema schema) | ||
| { | ||
| // If type is not set but format indicates a numeric type (int32, int64, float, double), set the type based on format | ||
| if ((schema.Type == JsonObjectType.None || schema.Type == JsonObjectType.Null) && | ||
| !string.IsNullOrEmpty(schema.Format)) | ||
| { | ||
| if (schema.Format == "int32" || schema.Format == "int64") | ||
|
christianhelle marked this conversation as resolved.
|
||
| { | ||
| schema.Type = JsonObjectType.Integer; | ||
| } | ||
| else if (schema.Format == "float" || schema.Format == "double") | ||
| { | ||
| schema.Type = JsonObjectType.Number; | ||
| } | ||
| } | ||
| } | ||
|
Comment on lines
+149
to
+164
|
||
|
|
||
| private void ApplyCustomIntegerType() | ||
| { | ||
| var customIntegerType = settings.CodeGeneratorSettings?.IntegerType ?? IntegerType.Int32; | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,93 @@ | ||
| using FluentAssertions; | ||
| using Refitter.Core; | ||
| using Refitter.Tests.Build; | ||
| using Refitter.Tests.TestUtilities; | ||
| using TUnit.Core; | ||
|
|
||
| namespace Refitter.Tests.Examples; | ||
|
|
||
| public class Int32FormatWithPatternTests | ||
| { | ||
| private const string OpenApiSpec = @" | ||
| openapi: 3.0.4 | ||
| info: | ||
| version: '1.0.0' | ||
| title: 'WeatherForecast API' | ||
| servers: | ||
| - url: 'https://api.example.com/v1' | ||
| paths: | ||
| /weatherforecast: | ||
| get: | ||
| operationId: 'getWeatherForecast' | ||
| responses: | ||
| '200': | ||
| description: 'Success' | ||
| content: | ||
| application/json: | ||
| schema: | ||
| type: 'array' | ||
| items: | ||
| $ref: '#/components/schemas/WeatherForecast' | ||
| components: | ||
| schemas: | ||
| WeatherForecast: | ||
| type: object | ||
| properties: | ||
| date: | ||
| type: string | ||
| format: date | ||
| temperatureC: | ||
| pattern: '^-?(?:0|[1-9]\\d*)$' | ||
| format: int32 | ||
| temperatureF: | ||
| pattern: '^-?(?:0|[1-9]\\d*)$' | ||
| format: int32 | ||
| summary: | ||
| type: string | ||
| nullable: true | ||
| "; | ||
|
|
||
| [Test] | ||
| public async Task Can_Generate_Code() | ||
| { | ||
| string generatedCode = await GenerateCode(); | ||
| generatedCode.Should().NotBeNullOrWhiteSpace(); | ||
| } | ||
|
|
||
| [Test] | ||
| public async Task Should_Generate_Int_For_Properties_With_Format_Int32_And_Pattern() | ||
| { | ||
| string generatedCode = await GenerateCode(); | ||
|
|
||
| // Should generate int, not object | ||
| generatedCode.Should().Contain("public int TemperatureC { get; set; }"); | ||
| generatedCode.Should().Contain("public int TemperatureF { get; set; }"); | ||
|
|
||
| // Should not generate as object | ||
| generatedCode.Should().NotContain("public object TemperatureC { get; set; }"); | ||
| generatedCode.Should().NotContain("public object TemperatureF { get; set; }"); | ||
| } | ||
|
|
||
| [Test] | ||
| public async Task Can_Build_Generated_Code() | ||
| { | ||
| string generatedCode = await GenerateCode(); | ||
| BuildHelper | ||
| .BuildCSharp(generatedCode) | ||
| .Should() | ||
| .BeTrue(); | ||
| } | ||
|
|
||
| private static async Task<string> GenerateCode() | ||
| { | ||
| var swaggerFile = await SwaggerFileHelper.CreateSwaggerFile(OpenApiSpec); | ||
| var settings = new RefitGeneratorSettings | ||
| { | ||
| OpenApiPath = swaggerFile, | ||
| }; | ||
|
|
||
| var sut = await RefitGenerator.CreateAsync(settings); | ||
| var generatedCode = sut.Generate(); | ||
| return generatedCode; | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,110 @@ | ||
| using FluentAssertions; | ||
| using Refitter.Core; | ||
| using Refitter.Tests.Build; | ||
| using Refitter.Tests.TestUtilities; | ||
|
|
||
| namespace Refitter.Tests.Examples | ||
| { | ||
| public class NumericFormatWithPatternTests | ||
| { | ||
| private const string OpenApiSpec = @" | ||
| openapi: 3.0.4 | ||
| info: | ||
| version: '1.0.0' | ||
| title: 'Sensor Data API' | ||
| servers: | ||
| - url: 'https://api.example.com/v1' | ||
| paths: | ||
| /sensor: | ||
| get: | ||
| operationId: 'getSensorData' | ||
| responses: | ||
| '200': | ||
| description: 'Success' | ||
| content: | ||
| application/json: | ||
| schema: | ||
| $ref: '#/components/schemas/SensorData' | ||
| components: | ||
| schemas: | ||
| SensorData: | ||
| type: object | ||
| properties: | ||
| temperature: | ||
| pattern: '^-?\\d+\\.\\d+$' | ||
| format: float | ||
| humidity: | ||
| pattern: '^-?\\d+\\.\\d+$' | ||
| format: double | ||
| pressure: | ||
| pattern: '^-?(?:0|[1-9]\\d*)$' | ||
| format: int64 | ||
| "; | ||
|
|
||
| [Test] | ||
| public async Task Can_Generate_Code() | ||
| { | ||
| string generatedCode = await GenerateCode(); | ||
| generatedCode.Should().NotBeNullOrWhiteSpace(); | ||
| } | ||
|
|
||
| [Test] | ||
| public async Task Should_Generate_Float_For_Properties_With_Format_Float_And_Pattern() | ||
| { | ||
| string generatedCode = await GenerateCode(); | ||
|
|
||
| // Should generate float, not object | ||
| generatedCode.Should().Contain("public float Temperature { get; set; }"); | ||
|
|
||
| // Should not generate as object | ||
| generatedCode.Should().NotContain("public object Temperature { get; set; }"); | ||
| } | ||
|
|
||
| [Test] | ||
| public async Task Should_Generate_Double_For_Properties_With_Format_Double_And_Pattern() | ||
| { | ||
| string generatedCode = await GenerateCode(); | ||
|
|
||
| // Should generate double, not object | ||
| generatedCode.Should().Contain("public double Humidity { get; set; }"); | ||
|
|
||
| // Should not generate as object | ||
| generatedCode.Should().NotContain("public object Humidity { get; set; }"); | ||
| } | ||
|
|
||
| [Test] | ||
| public async Task Should_Generate_Long_For_Properties_With_Format_Int64_And_Pattern() | ||
| { | ||
| string generatedCode = await GenerateCode(); | ||
|
|
||
| // Should generate long, not object | ||
| generatedCode.Should().Contain("public long Pressure { get; set; }"); | ||
|
|
||
| // Should not generate as object | ||
| generatedCode.Should().NotContain("public object Pressure { get; set; }"); | ||
| } | ||
|
|
||
| [Test] | ||
| public async Task Can_Build_Generated_Code() | ||
| { | ||
| string generatedCode = await GenerateCode(); | ||
| BuildHelper | ||
| .BuildCSharp(generatedCode) | ||
| .Should() | ||
| .BeTrue(); | ||
| } | ||
|
|
||
| private static async Task<string> GenerateCode() | ||
| { | ||
| var swaggerFile = await SwaggerFileHelper.CreateSwaggerFile(OpenApiSpec); | ||
| var settings = new RefitGeneratorSettings | ||
| { | ||
| OpenApiPath = swaggerFile, | ||
| }; | ||
|
|
||
| var sut = await RefitGenerator.CreateAsync(settings); | ||
| var generatedCode = sut.Generate(); | ||
| return generatedCode; | ||
| } | ||
| } | ||
| } |
Uh oh!
There was an error while loading. Please reload this page.