From f5f894b8538fbe7e19551004c0f5eb7cb5a787bf Mon Sep 17 00:00:00 2001 From: Christian Helle Date: Sun, 19 Oct 2025 00:24:33 +0200 Subject: [PATCH 01/20] Introduce --integer-type CLI argument --- src/Refitter/Settings.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/Refitter/Settings.cs b/src/Refitter/Settings.cs index 9b4aebf0d..ad222be68 100644 --- a/src/Refitter/Settings.cs +++ b/src/Refitter/Settings.cs @@ -258,4 +258,9 @@ payloads with (yet) unknown types are offered by newer versions of an API [CommandOption("--no-inline-json-converters")] [DefaultValue(false)] public bool NoInlineJsonConverters { get; set; } + + [Description("The .NET type to use for OpenAPI integer types without a format specifier. Common values: 'int' (default), 'long'")] + [CommandOption("--integer-type")] + [DefaultValue("int")] + public string? IntegerType { get; set; } } From 0ca657056684c11c427b767ce4cf20969244829e Mon Sep 17 00:00:00 2001 From: Christian Helle Date: Sun, 19 Oct 2025 00:24:43 +0200 Subject: [PATCH 02/20] Add IntegerType property to CodeGeneratorSettings --- src/Refitter.Core/Settings/CodeGeneratorSettings.cs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/Refitter.Core/Settings/CodeGeneratorSettings.cs b/src/Refitter.Core/Settings/CodeGeneratorSettings.cs index fd3e7b359..4d2b8ab7e 100644 --- a/src/Refitter.Core/Settings/CodeGeneratorSettings.cs +++ b/src/Refitter.Core/Settings/CodeGeneratorSettings.cs @@ -58,6 +58,13 @@ Gets or sets a value indicating whether a required property must be defined in J [Description("Gets or sets the time span .NET type (default: 'TimeSpan').")] public string TimeSpanType { get; set; } = "System.TimeSpan"; + /// + /// Gets or sets the .NET type for OpenAPI integers without a format specifier (default: 'int'). + /// Common values: 'int' (default), 'long'. + /// + [Description("Gets or sets the .NET type for OpenAPI integers without a format specifier (default: 'int'). Common values: 'int' (default), 'long'.")] + public string IntegerType { get; set; } = "int"; + /// /// Gets or sets the generic array .NET type (default: 'ICollection'). /// From bf528008a8efc14edbc1ef47abd8254cb548ab23 Mon Sep 17 00:00:00 2001 From: Christian Helle Date: Sun, 19 Oct 2025 00:25:08 +0200 Subject: [PATCH 03/20] Enable IntegerType setting in GenerateCommand --- src/Refitter/GenerateCommand.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Refitter/GenerateCommand.cs b/src/Refitter/GenerateCommand.cs index 071eec1a3..26e2041ab 100644 --- a/src/Refitter/GenerateCommand.cs +++ b/src/Refitter/GenerateCommand.cs @@ -311,7 +311,8 @@ private static RefitGeneratorSettings CreateRefitGeneratorSettings(Settings sett GenerateXmlDocCodeComments = !settings.NoXmlDocCodeComments, CodeGeneratorSettings = new CodeGeneratorSettings { - InlineJsonConverters = !settings.NoInlineJsonConverters + InlineJsonConverters = !settings.NoInlineJsonConverters, + IntegerType = settings.IntegerType ?? "int" } }; } From dc53d8988923acd5c8db0c526cf35202e51b8678 Mon Sep 17 00:00:00 2001 From: Christian Helle Date: Sun, 19 Oct 2025 00:28:39 +0200 Subject: [PATCH 04/20] Add custom integer type processing in CSharpClientGeneratorFactory --- .../CSharpClientGeneratorFactory.cs | 92 +++++++++++++++++++ 1 file changed, 92 insertions(+) diff --git a/src/Refitter.Core/CSharpClientGeneratorFactory.cs b/src/Refitter.Core/CSharpClientGeneratorFactory.cs index ed259c4a7..def3a4482 100644 --- a/src/Refitter.Core/CSharpClientGeneratorFactory.cs +++ b/src/Refitter.Core/CSharpClientGeneratorFactory.cs @@ -1,4 +1,5 @@ using System.Reflection; +using NJsonSchema; using NJsonSchema.CodeGeneration; using NJsonSchema.CodeGeneration.CSharp; using NSwag; @@ -18,6 +19,8 @@ public CustomCSharpClientGenerator Create() } } + ApplyCustomIntegerType(); + var csharpClientGeneratorSettings = new CSharpClientGeneratorSettings { GenerateClientClasses = false, @@ -60,6 +63,95 @@ public CustomCSharpClientGenerator Create() return generator; } + private void ApplyCustomIntegerType() + { + var customIntegerType = settings.CodeGeneratorSettings?.IntegerType; + if (string.IsNullOrEmpty(customIntegerType) || customIntegerType == "int") + return; + + if (customIntegerType != "long") + return; + + ProcessSchemasForIntegerType(document.Components.Schemas); + + foreach (var path in document.Paths) + { + foreach (var operation in path.Value.Values) + { + if (operation == null) continue; + + foreach (var parameter in operation.Parameters) + { + if (parameter.ActualSchema.Type == JsonObjectType.Integer && + string.IsNullOrEmpty(parameter.ActualSchema.Format)) + { + parameter.ActualSchema.Format = "int64"; + } + } + + if (operation.RequestBody?.Content != null) + { + foreach (var content in operation.RequestBody.Content.Values) + { + ProcessSchemaForIntegerType(content.Schema); + } + } + + foreach (var response in operation.Responses.Values) + { + if (response.Content != null) + { + foreach (var content in response.Content.Values) + { + ProcessSchemaForIntegerType(content.Schema); + } + } + } + } + } + } + + private void ProcessSchemasForIntegerType(IDictionary schemas) + { + foreach (var schema in schemas.Values) + { + ProcessSchemaForIntegerType(schema); + } + } + + private void ProcessSchemaForIntegerType(JsonSchema? schema) + { + if (schema == null) return; + + var actualSchema = schema.ActualSchema; + + if (actualSchema.Type == JsonObjectType.Integer && + string.IsNullOrEmpty(actualSchema.Format)) + { + actualSchema.Format = "int64"; + } + + foreach (var property in actualSchema.Properties.Values) + { + ProcessSchemaForIntegerType(property); + } + + if (actualSchema.Item != null) + { + ProcessSchemaForIntegerType(actualSchema.Item); + } + + if (actualSchema.AdditionalPropertiesSchema != null) + { + ProcessSchemaForIntegerType(actualSchema.AdditionalPropertiesSchema); + } + + foreach (var subSchema in actualSchema.AllOf.Concat(actualSchema.OneOf).Concat(actualSchema.AnyOf)) + { + ProcessSchemaForIntegerType(subSchema); + } + } + private static void MapCSharpGeneratorSettings( CodeGeneratorSettings? source, CSharpGeneratorSettings destination) From deeaf2b751262584f6e5d0c87daf272578551b56 Mon Sep 17 00:00:00 2001 From: Christian Helle Date: Sun, 19 Oct 2025 00:28:57 +0200 Subject: [PATCH 05/20] Add tests for integer type generation with and without format --- .../Examples/IntegerWithoutFormatTests.cs | 139 ++++++++++++++++++ 1 file changed, 139 insertions(+) create mode 100644 src/Refitter.Tests/Examples/IntegerWithoutFormatTests.cs diff --git a/src/Refitter.Tests/Examples/IntegerWithoutFormatTests.cs b/src/Refitter.Tests/Examples/IntegerWithoutFormatTests.cs new file mode 100644 index 000000000..c27da3b96 --- /dev/null +++ b/src/Refitter.Tests/Examples/IntegerWithoutFormatTests.cs @@ -0,0 +1,139 @@ +using FluentAssertions; +using Refitter.Core; +using Refitter.Tests.Build; +using Refitter.Tests.TestUtilities; +using Xunit; + +namespace Refitter.Tests.Examples; + +public class IntegerWithoutFormatTests +{ + private const string OpenApiSpec = + """ + { + "openapi": "3.0.1", + "info": { + "title": "Test API", + "version": "1.0.0" + }, + "paths": { + "/test": { + "get": { + "operationId": "TestEndpoint", + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TestModel" + } + } + } + } + } + } + } + }, + "components": { + "schemas": { + "TestModel": { + "type": "object", + "properties": { + "integerWithoutFormat": { + "type": "integer", + "description": "Integer without format - should default to int but optionally be long" + }, + "integerWithInt32Format": { + "type": "integer", + "format": "int32" + }, + "integerWithInt64Format": { + "type": "integer", + "format": "int64" + } + } + } + } + } + } + """; + + [Fact] + public async Task Default_Generates_Integer_Without_Format_As_Int() + { + string generatedCode = await GenerateCode(); + generatedCode.Should().Contain("int IntegerWithoutFormat"); + } + + [Fact] + public async Task Can_Generate_Integer_Without_Format_As_Long() + { + string generatedCode = await GenerateCodeWithLongIntegers(); + generatedCode.Should().Contain("long IntegerWithoutFormat"); + } + + [Fact] + public async Task Always_Respects_Explicit_Int32_Format() + { + string generatedCode = await GenerateCodeWithLongIntegers(); + generatedCode.Should().Contain("int IntegerWithInt32Format"); + } + + [Fact] + public async Task Always_Respects_Explicit_Int64_Format() + { + string generatedCode = await GenerateCode(); + generatedCode.Should().Contain("long IntegerWithInt64Format"); + } + + [Fact] + public async Task Can_Build_Generated_Code_With_Default() + { + string generatedCode = await GenerateCode(); + BuildHelper + .BuildCSharp(generatedCode) + .Should() + .BeTrue(); + } + + [Fact] + public async Task Can_Build_Generated_Code_With_Long() + { + string generatedCode = await GenerateCodeWithLongIntegers(); + BuildHelper + .BuildCSharp(generatedCode) + .Should() + .BeTrue(); + } + + private static async Task 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; + } + + private static async Task GenerateCodeWithLongIntegers() + { + var swaggerFile = await SwaggerFileHelper.CreateSwaggerFile(OpenApiSpec); + var settings = new RefitGeneratorSettings + { + OpenApiPath = swaggerFile, + CodeGeneratorSettings = new CodeGeneratorSettings + { + IntegerType = "long" + } + }; + + var sut = await RefitGenerator.CreateAsync(settings); + var generatedCode = sut.Generate(); + return generatedCode; + } +} From cc253ea4524b3dd4b294274f4fc6e390682272b3 Mon Sep 17 00:00:00 2001 From: Christian Helle Date: Sun, 19 Oct 2025 00:30:25 +0200 Subject: [PATCH 06/20] Update README to document --integer-type option for OpenAPI integer types --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index 0ca63fe64..9fe1a0f31 100644 --- a/README.md +++ b/README.md @@ -134,6 +134,7 @@ OPTIONS: See https://learn.microsoft.com/en-us/dotnet/standard/serialization/system-text-json/polymorphism for more information --disposable Generate refit clients that implement IDisposable --no-inline-json-converters Don't inline JsonConverter attributes for enum properties. When disabled, enum properties will not have [JsonConverter(typeof(JsonStringEnumConverter))] attributes + --integer-type int The .NET type to use for OpenAPI integer types without a format specifier. Common values: 'int' (default), 'long' ``` To generate code from an OpenAPI specifications file, run the following: @@ -291,6 +292,7 @@ The following is an example `.refitter` file "dictionaryInstanceType": "System.Collections.Generic.Dictionary", "arrayBaseType": "System.Collections.ObjectModel.Collection", "dictionaryBaseType": "System.Collections.Generic.Dictionary", + "integerType": "int", // Optional. Default="int". The .NET type for OpenAPI integers without a format. Common values: "int", "long" "propertySetterAccessModifier": "", "generateImmutableArrayProperties": false, "generateImmutableDictionaryProperties": false, @@ -378,6 +380,7 @@ The following is an example `.refitter` file - `dictionaryInstanceType` - Default is `System.Collections.Generic.Dictionary`, - `arrayBaseType` - Default is `System.Collections.ObjectModel.Collection`, - `dictionaryBaseType` - Default is `System.Collections.Generic.Dictionary`, + - `integerType` - Default is `int`. The .NET type to use for OpenAPI integer types without a format specifier. Common values: `int`, `long` - `propertySetterAccessModifier` - Default is ``, - `generateImmutableArrayProperties` - Default is false, - `generateImmutableDictionaryProperties` - Default is false, From a1a3b8e57d360a0644e180f39e01e11ed878b658 Mon Sep 17 00:00:00 2001 From: Christian Helle Date: Sun, 19 Oct 2025 00:33:04 +0200 Subject: [PATCH 07/20] Add `integerType` property to JSON Schema definition --- docs/json-schema.json | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/json-schema.json b/docs/json-schema.json index 22fd00a73..f0cfdd9a6 100644 --- a/docs/json-schema.json +++ b/docs/json-schema.json @@ -103,6 +103,10 @@ "description": "Gets or sets the generic dictionary .NET type which is used as base class (default: 'Dictionary').", "type": "string" }, + "integerType": { + "description": "Gets or sets the .NET type for OpenAPI integers without a format specifier (default: 'int'). Common values: 'int' (default), 'long'.", + "type": "string" + }, "propertySetterAccessModifier": { "description": "Gets the access modifier of property setters (default: '').", "type": "string" From e0a61e64b0f586013431febb808135a3fa6cfbfc Mon Sep 17 00:00:00 2001 From: Christian Helle Date: Sun, 19 Oct 2025 00:33:43 +0200 Subject: [PATCH 08/20] Document usage of `--integer-type` CLI argument in NuGet README --- src/Refitter/README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/Refitter/README.md b/src/Refitter/README.md index ff0b3c353..4d39a5afb 100644 --- a/src/Refitter/README.md +++ b/src/Refitter/README.md @@ -44,6 +44,7 @@ EXAMPLES: refitter ./openapi.json --optional-nullable-parameters refitter ./openapi.json --use-polymorphic-serialization refitter ./openapi.json --no-inline-json-converters + refitter ./openapi.json --integer-type long ARGUMENTS: [URL or input file] URL or file path to OpenAPI Specification file @@ -113,6 +114,7 @@ OPTIONS: See https://learn.microsoft.com/en-us/dotnet/standard/serialization/system-text-json/polymorphism for more information --disposable Generate refit clients that implement IDisposable --no-inline-json-converters Don't inline JsonConverter attributes for enum properties. When disabled, enum properties will not have [JsonConverter(typeof(JsonStringEnumConverter))] attributes + --integer-type int The .NET type to use for OpenAPI integer types without a format specifier. Common values: 'int' (default), 'long' ``` ## CLI Tool Output Example @@ -215,6 +217,7 @@ The following is an example `.refitter` file "dictionaryInstanceType": "System.Collections.Generic.Dictionary", "arrayBaseType": "System.Collections.ObjectModel.Collection", "dictionaryBaseType": "System.Collections.Generic.Dictionary", + "integerType": "int", // Optional. Default="int". The .NET type for OpenAPI integers without a format. Common values: "int", "long" "propertySetterAccessModifier": "", "generateImmutableArrayProperties": false, "generateImmutableDictionaryProperties": false, @@ -299,6 +302,7 @@ The following is an example `.refitter` file - `dictionaryInstanceType` - Default is `System.Collections.Generic.Dictionary`, - `arrayBaseType` - Default is `System.Collections.ObjectModel.Collection`, - `dictionaryBaseType` - Default is `System.Collections.Generic.Dictionary`, + - `integerType` - Default is `int`. The .NET type to use for OpenAPI integer types without a format specifier. Common values: `int`, `long` - `propertySetterAccessModifier` - Default is ``, - `generateImmutableArrayProperties` - Default is false, - `generateImmutableDictionaryProperties` - Default is false, From e3b29ebb47133e01a3fccd6c246a1fbaaff5849f Mon Sep 17 00:00:00 2001 From: Christian Helle Date: Sun, 19 Oct 2025 00:34:40 +0200 Subject: [PATCH 09/20] Update MSBuild README --- src/Refitter.MSBuild/README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Refitter.MSBuild/README.md b/src/Refitter.MSBuild/README.md index 8e7ae98e7..c23d825c7 100644 --- a/src/Refitter.MSBuild/README.md +++ b/src/Refitter.MSBuild/README.md @@ -105,6 +105,7 @@ The following is an example `.refitter` file "dictionaryInstanceType": "System.Collections.Generic.Dictionary", "arrayBaseType": "System.Collections.ObjectModel.Collection", "dictionaryBaseType": "System.Collections.Generic.Dictionary", + "integerType": "int", // Optional. Default="int". The .NET type for OpenAPI integers without a format. Common values: "int", "long" "propertySetterAccessModifier": "", "generateImmutableArrayProperties": false, "generateImmutableDictionaryProperties": false, @@ -181,6 +182,7 @@ The following is an example `.refitter` file - `dictionaryInstanceType` - Default is `System.Collections.Generic.Dictionary`, - `arrayBaseType` - Default is `System.Collections.ObjectModel.Collection`, - `dictionaryBaseType` - Default is `System.Collections.Generic.Dictionary`, + - `integerType` - Default is `int`. The .NET type to use for OpenAPI integer types without a format specifier. Common values: `int`, `long` - `propertySetterAccessModifier` - Default is ``, - `generateImmutableArrayProperties` - Default is false, - `generateImmutableDictionaryProperties` - Default is false, From 59c3b863e1a80c7910735d8710f69de310a2c440 Mon Sep 17 00:00:00 2001 From: Christian Helle Date: Sun, 19 Oct 2025 00:34:48 +0200 Subject: [PATCH 10/20] Update source generator README --- src/Refitter.SourceGenerator/README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Refitter.SourceGenerator/README.md b/src/Refitter.SourceGenerator/README.md index 170a1ae58..f93d09efc 100644 --- a/src/Refitter.SourceGenerator/README.md +++ b/src/Refitter.SourceGenerator/README.md @@ -101,6 +101,7 @@ The following is an example `.refitter` file "dictionaryInstanceType": "System.Collections.Generic.Dictionary", "arrayBaseType": "System.Collections.ObjectModel.Collection", "dictionaryBaseType": "System.Collections.Generic.Dictionary", + "integerType": "int", // Optional. Default="int". The .NET type for OpenAPI integers without a format. Common values: "int", "long" "propertySetterAccessModifier": "", "generateImmutableArrayProperties": false, "generateImmutableDictionaryProperties": false, @@ -179,6 +180,7 @@ The following is an example `.refitter` file - `dictionaryInstanceType` - Default is `System.Collections.Generic.Dictionary`, - `arrayBaseType` - Default is `System.Collections.ObjectModel.Collection`, - `dictionaryBaseType` - Default is `System.Collections.Generic.Dictionary`, + - `integerType` - Default is `int`. The .NET type to use for OpenAPI integer types without a format specifier. Common values: `int`, `long` - `propertySetterAccessModifier` - Default is ``, - `generateImmutableArrayProperties` - Default is false, - `generateImmutableDictionaryProperties` - Default is false, From 0f373e1c4ea0831065deb0e2d3a3342d9e066572 Mon Sep 17 00:00:00 2001 From: Christian Helle Date: Sun, 19 Oct 2025 00:35:06 +0200 Subject: [PATCH 11/20] Update refitter-file-format docfx article --- docs/docfx_project/articles/refitter-file-format.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/docfx_project/articles/refitter-file-format.md b/docs/docfx_project/articles/refitter-file-format.md index 40c9df13e..67992e425 100644 --- a/docs/docfx_project/articles/refitter-file-format.md +++ b/docs/docfx_project/articles/refitter-file-format.md @@ -99,6 +99,7 @@ The following is an example `.refitter` file "dictionaryInstanceType": "System.Collections.Generic.Dictionary", "arrayBaseType": "System.Collections.ObjectModel.Collection", "dictionaryBaseType": "System.Collections.Generic.Dictionary", + "integerType": "int", // Optional. Default="int". The .NET type for OpenAPI integers without a format. Common values: "int", "long" "propertySetterAccessModifier": "", "generateImmutableArrayProperties": false, "generateImmutableDictionaryProperties": false, @@ -192,6 +193,7 @@ The following is an example `.refitter` file - `dictionaryInstanceType` - Default is `System.Collections.Generic.Dictionary`, - `arrayBaseType` - Default is `System.Collections.ObjectModel.Collection`, - `dictionaryBaseType` - Default is `System.Collections.Generic.Dictionary`, + - `integerType` - Default is `int`. The .NET type to use for OpenAPI integer types without a format specifier. Common values: `int`, `long` - `propertySetterAccessModifier` - Default is ``, - `generateImmutableArrayProperties` - Default is false, - `generateImmutableDictionaryProperties` - Default is false, From 819825904db01b814f53a978d805ad8cb4cbb9c7 Mon Sep 17 00:00:00 2001 From: Christian Helle Date: Sun, 19 Oct 2025 00:35:17 +0200 Subject: [PATCH 12/20] Update cli tool docfx article --- docs/docfx_project/articles/cli-tool.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/docfx_project/articles/cli-tool.md b/docs/docfx_project/articles/cli-tool.md index ac54cbae5..ec530f342 100644 --- a/docs/docfx_project/articles/cli-tool.md +++ b/docs/docfx_project/articles/cli-tool.md @@ -42,6 +42,7 @@ EXAMPLES: refitter ./openapi.json --collection-format Csv refitter ./openapi.json --simple-output refitter ./openapi.json --no-inline-json-converters + refitter ./openapi.json --integer-type long ARGUMENTS: [URL or input file] URL or file path to OpenAPI Specification file @@ -118,6 +119,7 @@ OPTIONS: See https://learn.microsoft.com/en-us/dotnet/standard/serialization/system-text-json/polymorphism for more information --disposable Generate refit clients that implement IDisposable --no-inline-json-converters Don't inline JsonConverter attributes for enum properties. When disabled, enum properties will not have [JsonConverter(typeof(JsonStringEnumConverter))] attributes + --integer-type int The .NET type to use for OpenAPI integer types without a format specifier. Common values: 'int' (default), 'long' ``` ## CLI Tool Output Example From 0f8685c2b11167ba7a404df4d584318243b6c31a Mon Sep 17 00:00:00 2001 From: Christian Helle Date: Sun, 19 Oct 2025 00:40:35 +0200 Subject: [PATCH 13/20] Rename test class to `IntegerFormatTests` for consistency --- .../{IntegerWithoutFormatTests.cs => IntegerFormatTests.cs} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename src/Refitter.Tests/Examples/{IntegerWithoutFormatTests.cs => IntegerFormatTests.cs} (99%) diff --git a/src/Refitter.Tests/Examples/IntegerWithoutFormatTests.cs b/src/Refitter.Tests/Examples/IntegerFormatTests.cs similarity index 99% rename from src/Refitter.Tests/Examples/IntegerWithoutFormatTests.cs rename to src/Refitter.Tests/Examples/IntegerFormatTests.cs index c27da3b96..d274bf748 100644 --- a/src/Refitter.Tests/Examples/IntegerWithoutFormatTests.cs +++ b/src/Refitter.Tests/Examples/IntegerFormatTests.cs @@ -6,7 +6,7 @@ namespace Refitter.Tests.Examples; -public class IntegerWithoutFormatTests +public class IntegerFormatTests { private const string OpenApiSpec = """ From 50662f3d1d0ee36e9db470b1ad8f6dfc875f242a Mon Sep 17 00:00:00 2001 From: Christian Helle Date: Sun, 19 Oct 2025 22:37:44 +0200 Subject: [PATCH 14/20] Introduce `IntegerType` enum for OpenAPI integer type handling --- .../CSharpClientGeneratorFactory.cs | 7 ++----- src/Refitter.Core/IntegerType.cs | 18 ++++++++++++++++++ .../Settings/CodeGeneratorSettings.cs | 7 +++---- .../Examples/IntegerFormatTests.cs | 2 +- src/Refitter/GenerateCommand.cs | 2 +- src/Refitter/Settings.cs | 8 ++++---- 6 files changed, 29 insertions(+), 15 deletions(-) create mode 100644 src/Refitter.Core/IntegerType.cs diff --git a/src/Refitter.Core/CSharpClientGeneratorFactory.cs b/src/Refitter.Core/CSharpClientGeneratorFactory.cs index def3a4482..e580afd34 100644 --- a/src/Refitter.Core/CSharpClientGeneratorFactory.cs +++ b/src/Refitter.Core/CSharpClientGeneratorFactory.cs @@ -65,11 +65,8 @@ public CustomCSharpClientGenerator Create() private void ApplyCustomIntegerType() { - var customIntegerType = settings.CodeGeneratorSettings?.IntegerType; - if (string.IsNullOrEmpty(customIntegerType) || customIntegerType == "int") - return; - - if (customIntegerType != "long") + var customIntegerType = settings.CodeGeneratorSettings?.IntegerType ?? IntegerType.Int32; + if (customIntegerType == IntegerType.Int32) return; ProcessSchemasForIntegerType(document.Components.Schemas); diff --git a/src/Refitter.Core/IntegerType.cs b/src/Refitter.Core/IntegerType.cs new file mode 100644 index 000000000..caca60299 --- /dev/null +++ b/src/Refitter.Core/IntegerType.cs @@ -0,0 +1,18 @@ +namespace Refitter.Core; + +/// +/// Specifies the .NET type to use for OpenAPI integer types +/// without a format specifier +/// +public enum IntegerType +{ + /// + /// Use System.Int32 (int) for integers without format + /// + Int32, + + /// + /// Use System.Int64 (long) for integers without format + /// + Int64 +} diff --git a/src/Refitter.Core/Settings/CodeGeneratorSettings.cs b/src/Refitter.Core/Settings/CodeGeneratorSettings.cs index 4d2b8ab7e..412a18634 100644 --- a/src/Refitter.Core/Settings/CodeGeneratorSettings.cs +++ b/src/Refitter.Core/Settings/CodeGeneratorSettings.cs @@ -59,11 +59,10 @@ Gets or sets a value indicating whether a required property must be defined in J public string TimeSpanType { get; set; } = "System.TimeSpan"; /// - /// Gets or sets the .NET type for OpenAPI integers without a format specifier (default: 'int'). - /// Common values: 'int' (default), 'long'. + /// Gets or sets the .NET type for OpenAPI integers without a format specifier (default: Int32). /// - [Description("Gets or sets the .NET type for OpenAPI integers without a format specifier (default: 'int'). Common values: 'int' (default), 'long'.")] - public string IntegerType { get; set; } = "int"; + [Description("Gets or sets the .NET type for OpenAPI integers without a format specifier (default: Int32).")] + public IntegerType IntegerType { get; set; } = IntegerType.Int32; /// /// Gets or sets the generic array .NET type (default: 'ICollection'). diff --git a/src/Refitter.Tests/Examples/IntegerFormatTests.cs b/src/Refitter.Tests/Examples/IntegerFormatTests.cs index d274bf748..cf22d53d0 100644 --- a/src/Refitter.Tests/Examples/IntegerFormatTests.cs +++ b/src/Refitter.Tests/Examples/IntegerFormatTests.cs @@ -128,7 +128,7 @@ private static async Task GenerateCodeWithLongIntegers() OpenApiPath = swaggerFile, CodeGeneratorSettings = new CodeGeneratorSettings { - IntegerType = "long" + IntegerType = IntegerType.Int64 } }; diff --git a/src/Refitter/GenerateCommand.cs b/src/Refitter/GenerateCommand.cs index 26e2041ab..a3baa3518 100644 --- a/src/Refitter/GenerateCommand.cs +++ b/src/Refitter/GenerateCommand.cs @@ -312,7 +312,7 @@ private static RefitGeneratorSettings CreateRefitGeneratorSettings(Settings sett CodeGeneratorSettings = new CodeGeneratorSettings { InlineJsonConverters = !settings.NoInlineJsonConverters, - IntegerType = settings.IntegerType ?? "int" + IntegerType = settings.IntegerType } }; } diff --git a/src/Refitter/Settings.cs b/src/Refitter/Settings.cs index ad222be68..1886d6387 100644 --- a/src/Refitter/Settings.cs +++ b/src/Refitter/Settings.cs @@ -242,7 +242,7 @@ payloads with (yet) unknown types are offered by newer versions of an API - Csv: Comma-separated values. (?param=value1,value2) - Ssv: Space-separated values. (?param=value1 value2) - Tsv: Tab-separated values. (?param=value1\tvalue2) - - Pipes: Pipe-separated values. (?param=value1|value2) + - Pipes: Pipe-separated values. (?param=value1|value2) See https://swagger.io/docs/specification/v2_0/describing-parameters/#array-and-multi-value-parameters for more information. """)] [CommandOption("--collection-format")] @@ -259,8 +259,8 @@ payloads with (yet) unknown types are offered by newer versions of an API [DefaultValue(false)] public bool NoInlineJsonConverters { get; set; } - [Description("The .NET type to use for OpenAPI integer types without a format specifier. Common values: 'int' (default), 'long'")] + [Description("The .NET type to use for OpenAPI integers without a format specifier. May be one of: Int32, Int64")] [CommandOption("--integer-type")] - [DefaultValue("int")] - public string? IntegerType { get; set; } + [DefaultValue(IntegerType.Int32)] + public IntegerType IntegerType { get; set; } = IntegerType.Int32; } From a4741d769342aedae2cfa697504514fa794c6fa2 Mon Sep 17 00:00:00 2001 From: Christian Helle Date: Sun, 19 Oct 2025 22:38:22 +0200 Subject: [PATCH 15/20] Add enum constraint to `integerType` in JSON Schema and update description --- docs/json-schema.json | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/docs/json-schema.json b/docs/json-schema.json index f0cfdd9a6..0ea7abd18 100644 --- a/docs/json-schema.json +++ b/docs/json-schema.json @@ -104,8 +104,9 @@ "type": "string" }, "integerType": { - "description": "Gets or sets the .NET type for OpenAPI integers without a format specifier (default: 'int'). Common values: 'int' (default), 'long'.", - "type": "string" + "description": "Gets or sets the .NET type for OpenAPI integers without a format specifier (default: Int32). Possible values: Int32, Int64.", + "type": "string", + "enum": ["Int32", "Int64"] }, "propertySetterAccessModifier": { "description": "Gets the access modifier of property setters (default: '').", From 4d4957c86a2696ebc766bdb7e150c2b29b459eae Mon Sep 17 00:00:00 2001 From: Christian Helle Date: Sun, 19 Oct 2025 22:38:38 +0200 Subject: [PATCH 16/20] Update documentation to reflect `integerType` default as `Int32` and valid values as `Int32`/`Int64` --- README.md | 4 ++-- docs/docfx_project/articles/refitter-file-format.md | 4 ++-- src/Refitter.MSBuild/README.md | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 9fe1a0f31..8e624d0c9 100644 --- a/README.md +++ b/README.md @@ -292,7 +292,7 @@ The following is an example `.refitter` file "dictionaryInstanceType": "System.Collections.Generic.Dictionary", "arrayBaseType": "System.Collections.ObjectModel.Collection", "dictionaryBaseType": "System.Collections.Generic.Dictionary", - "integerType": "int", // Optional. Default="int". The .NET type for OpenAPI integers without a format. Common values: "int", "long" + "integerType": "Int32", // Optional. Default="Int32". The .NET type for OpenAPI integers without a format. Possible values: "Int32", "Int64" "propertySetterAccessModifier": "", "generateImmutableArrayProperties": false, "generateImmutableDictionaryProperties": false, @@ -380,7 +380,7 @@ The following is an example `.refitter` file - `dictionaryInstanceType` - Default is `System.Collections.Generic.Dictionary`, - `arrayBaseType` - Default is `System.Collections.ObjectModel.Collection`, - `dictionaryBaseType` - Default is `System.Collections.Generic.Dictionary`, - - `integerType` - Default is `int`. The .NET type to use for OpenAPI integer types without a format specifier. Common values: `int`, `long` + - `integerType` - Default is `Int32`. The .NET type to use for OpenAPI integer types without a format specifier. Possible values: `Int32`, `Int64` - `propertySetterAccessModifier` - Default is ``, - `generateImmutableArrayProperties` - Default is false, - `generateImmutableDictionaryProperties` - Default is false, diff --git a/docs/docfx_project/articles/refitter-file-format.md b/docs/docfx_project/articles/refitter-file-format.md index 67992e425..7159490c1 100644 --- a/docs/docfx_project/articles/refitter-file-format.md +++ b/docs/docfx_project/articles/refitter-file-format.md @@ -99,7 +99,7 @@ The following is an example `.refitter` file "dictionaryInstanceType": "System.Collections.Generic.Dictionary", "arrayBaseType": "System.Collections.ObjectModel.Collection", "dictionaryBaseType": "System.Collections.Generic.Dictionary", - "integerType": "int", // Optional. Default="int". The .NET type for OpenAPI integers without a format. Common values: "int", "long" + "integerType": "Int32", // Optional. Default="Int32". The .NET type for OpenAPI integers without a format. Possible values: "Int32", "Int64" "propertySetterAccessModifier": "", "generateImmutableArrayProperties": false, "generateImmutableDictionaryProperties": false, @@ -193,7 +193,7 @@ The following is an example `.refitter` file - `dictionaryInstanceType` - Default is `System.Collections.Generic.Dictionary`, - `arrayBaseType` - Default is `System.Collections.ObjectModel.Collection`, - `dictionaryBaseType` - Default is `System.Collections.Generic.Dictionary`, - - `integerType` - Default is `int`. The .NET type to use for OpenAPI integer types without a format specifier. Common values: `int`, `long` + - `integerType` - Default is `Int32`. The .NET type to use for OpenAPI integer types without a format specifier. Possible values: `Int32`, `Int64` - `propertySetterAccessModifier` - Default is ``, - `generateImmutableArrayProperties` - Default is false, - `generateImmutableDictionaryProperties` - Default is false, diff --git a/src/Refitter.MSBuild/README.md b/src/Refitter.MSBuild/README.md index c23d825c7..a9e1a7dfd 100644 --- a/src/Refitter.MSBuild/README.md +++ b/src/Refitter.MSBuild/README.md @@ -105,7 +105,7 @@ The following is an example `.refitter` file "dictionaryInstanceType": "System.Collections.Generic.Dictionary", "arrayBaseType": "System.Collections.ObjectModel.Collection", "dictionaryBaseType": "System.Collections.Generic.Dictionary", - "integerType": "int", // Optional. Default="int". The .NET type for OpenAPI integers without a format. Common values: "int", "long" + "integerType": "Int32", // Optional. Default="Int32". The .NET type for OpenAPI integers without a format. Possible values: "Int32", "Int64" "propertySetterAccessModifier": "", "generateImmutableArrayProperties": false, "generateImmutableDictionaryProperties": false, @@ -182,7 +182,7 @@ The following is an example `.refitter` file - `dictionaryInstanceType` - Default is `System.Collections.Generic.Dictionary`, - `arrayBaseType` - Default is `System.Collections.ObjectModel.Collection`, - `dictionaryBaseType` - Default is `System.Collections.Generic.Dictionary`, - - `integerType` - Default is `int`. The .NET type to use for OpenAPI integer types without a format specifier. Common values: `int`, `long` + - `integerType` - Default is `Int32`. The .NET type to use for OpenAPI integer types without a format specifier. Possible values: `Int32`, `Int64` - `propertySetterAccessModifier` - Default is ``, - `generateImmutableArrayProperties` - Default is false, - `generateImmutableDictionaryProperties` - Default is false, From 364fad49846eda4dd293c0cd51938ff44c5c245b Mon Sep 17 00:00:00 2001 From: Christian Helle Date: Sun, 19 Oct 2025 22:39:04 +0200 Subject: [PATCH 17/20] Document `--integer-type` CLI argument in Copilot instructions --- .github/copilot-instructions.md | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index 5565a864a..d64d2753f 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -151,6 +151,7 @@ dotnet run --project src/Refitter --configuration Release --framework net9.0 -- - `--disposable`: Generate IDisposable clients - `--collection-format`: Control query parameter collection formatting (Multi/Csv/Ssv/Tsv/Pipes) - `--no-banner`: Hide donation banner in CLI output +- `--integer-type`: Set the .NET type for OpenAPI integers without a format specifier (Int32/Int64) ### Working with OpenAPI Specifications - Test resources are located in `src/Refitter.Tests/Resources/V2/` and `src/Refitter.Tests/Resources/V3/` From cbcdbd8b9e1640249c66134cfa8292f9de4d3836 Mon Sep 17 00:00:00 2001 From: Christian Helle Date: Sun, 19 Oct 2025 23:27:57 +0200 Subject: [PATCH 18/20] Combine the collections into a single array/list before iteration to reduce allocations. --- src/Refitter.Core/CSharpClientGeneratorFactory.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/Refitter.Core/CSharpClientGeneratorFactory.cs b/src/Refitter.Core/CSharpClientGeneratorFactory.cs index e580afd34..adfe4085f 100644 --- a/src/Refitter.Core/CSharpClientGeneratorFactory.cs +++ b/src/Refitter.Core/CSharpClientGeneratorFactory.cs @@ -143,7 +143,12 @@ private void ProcessSchemaForIntegerType(JsonSchema? schema) ProcessSchemaForIntegerType(actualSchema.AdditionalPropertiesSchema); } - foreach (var subSchema in actualSchema.AllOf.Concat(actualSchema.OneOf).Concat(actualSchema.AnyOf)) + var subSchemas = actualSchema.AllOf + .Concat(actualSchema.OneOf) + .Concat(actualSchema.AnyOf) + .ToArray(); + + foreach (var subSchema in subSchemas) { ProcessSchemaForIntegerType(subSchema); } From 5feb7c151f640017c3795abecbc5781038cc565d Mon Sep 17 00:00:00 2001 From: Christian Helle Date: Mon, 20 Oct 2025 10:16:21 +0200 Subject: [PATCH 19/20] Extend `IntegerFormatTests` to cover integer formats in request parameters and body --- .../Examples/IntegerFormatTests.cs | 58 ++++++++++++++++++- 1 file changed, 56 insertions(+), 2 deletions(-) diff --git a/src/Refitter.Tests/Examples/IntegerFormatTests.cs b/src/Refitter.Tests/Examples/IntegerFormatTests.cs index cf22d53d0..85518fe3b 100644 --- a/src/Refitter.Tests/Examples/IntegerFormatTests.cs +++ b/src/Refitter.Tests/Examples/IntegerFormatTests.cs @@ -20,13 +20,50 @@ public class IntegerFormatTests "/test": { "get": { "operationId": "TestEndpoint", + "parameters": [ + { + "name": "integerWithoutFormat", + "in": "query", + "required": false, + "schema": { + "type": "integer" + } + }, + { + "name": "integerWithInt32Format", + "in": "query", + "required": false, + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "integerWithInt64Format", + "in": "query", + "required": false, + "schema": { + "type": "integer", + "format": "int64" + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RequestModel" + } + } + } + }, "responses": { "200": { "description": "Success", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/TestModel" + "$ref": "#/components/schemas/ResponseModel" } } } @@ -37,7 +74,24 @@ public class IntegerFormatTests }, "components": { "schemas": { - "TestModel": { + "RequestModel": { + "type": "object", + "properties": { + "integerWithoutFormat": { + "type": "integer", + "description": "Integer without format - should default to int but optionally be long" + }, + "integerWithInt32Format": { + "type": "integer", + "format": "int32" + }, + "integerWithInt64Format": { + "type": "integer", + "format": "int64" + } + } + } + "ResponseModel": { "type": "object", "properties": { "integerWithoutFormat": { From 2b3eac5af13c43c14ef7f64ca0c4bacf369ed99a Mon Sep 17 00:00:00 2001 From: Christian Helle Date: Mon, 20 Oct 2025 13:31:44 +0200 Subject: [PATCH 20/20] Extend `IntegerFormatTests` to include complex object and schema combinations in request/response scenarios --- .../Examples/IntegerFormatTests.cs | 78 ++++++++++++++++++- 1 file changed, 77 insertions(+), 1 deletion(-) diff --git a/src/Refitter.Tests/Examples/IntegerFormatTests.cs b/src/Refitter.Tests/Examples/IntegerFormatTests.cs index 85518fe3b..1b4d5cf25 100644 --- a/src/Refitter.Tests/Examples/IntegerFormatTests.cs +++ b/src/Refitter.Tests/Examples/IntegerFormatTests.cs @@ -88,9 +88,38 @@ public class IntegerFormatTests "integerWithInt64Format": { "type": "integer", "format": "int64" + }, + "additionalPropsObject": { + "type": "object", + "additionalProperties": { + "type": "string" + } + }, + "allOfExample": { + "allOf": [ + { "$ref": "#/components/schemas/BaseType" }, + { + "type": "object", + "properties": { + "extra": { "type": "string" } + } + } + ] + }, + "oneOfExample": { + "oneOf": [ + { "$ref": "#/components/schemas/TypeA" }, + { "$ref": "#/components/schemas/TypeB" } + ] + }, + "anyOfExample": { + "anyOf": [ + { "$ref": "#/components/schemas/TypeA" }, + { "$ref": "#/components/schemas/TypeB" } + ] } } - } + }, "ResponseModel": { "type": "object", "properties": { @@ -105,8 +134,55 @@ public class IntegerFormatTests "integerWithInt64Format": { "type": "integer", "format": "int64" + }, + "additionalPropsObject": { + "type": "object", + "additionalProperties": { + "type": "integer" + } + }, + "allOfExample": { + "allOf": [ + { "$ref": "#/components/schemas/BaseType" }, + { + "type": "object", + "properties": { + "extra": { "type": "integer" } + } + } + ] + }, + "oneOfExample": { + "oneOf": [ + { "$ref": "#/components/schemas/TypeA" }, + { "$ref": "#/components/schemas/TypeB" } + ] + }, + "anyOfExample": { + "anyOf": [ + { "$ref": "#/components/schemas/TypeA" }, + { "$ref": "#/components/schemas/TypeB" } + ] } } + }, + "BaseType": { + "type": "object", + "properties": { + "baseProp": { "type": "integer" } + } + }, + "TypeA": { + "type": "object", + "properties": { + "typeAProp": { "type": "integer" } + } + }, + "TypeB": { + "type": "object", + "properties": { + "typeBProp": { "type": "integer" } + } } } }