diff --git a/src/Refitter.Core/ParameterExtractor.cs b/src/Refitter.Core/ParameterExtractor.cs index 6dfd4f97f..003d0ef9b 100644 --- a/src/Refitter.Core/ParameterExtractor.cs +++ b/src/Refitter.Core/ParameterExtractor.cs @@ -86,7 +86,7 @@ public static IEnumerable GetParameters( parameters.AddRange(formParameters); parameters.AddRange(binaryBodyParameters); - parameters = ReOrderNullableParameters(parameters, settings); + parameters = ReOrderNullableParameters(parameters, settings, operationModel.Parameters); if (settings.ApizrSettings?.WithRequestOptions == true) parameters.Add("[RequestOptions] IApizrRequestOptions options"); @@ -116,7 +116,8 @@ private static string ReplaceUnsafeCharacters( private static List ReOrderNullableParameters( List parameters, - RefitGeneratorSettings settings) + RefitGeneratorSettings settings, + ICollection parameterModels) { if (!settings.OptionalParameters || settings.ApizrSettings?.WithRequestOptions == true) return parameters; @@ -125,12 +126,54 @@ private static List ReOrderNullableParameters( for (int index = 0; index < parameters.Count; index++) { if (parameters[index].Contains("?")) - parameters[index] += " = default"; + { + var parameterString = parameters[index]; + var defaultValue = GetDefaultValueForParameter(parameterString, parameterModels); + parameters[index] = parameterString + " = " + defaultValue; + } } return parameters; } + private static string GetDefaultValueForParameter(string parameterString, ICollection parameterModels) + { + var parts = parameterString.Split([' '], StringSplitOptions.RemoveEmptyEntries); + if (parts.Length == 0) + return "default"; + + var variableName = parts[parts.Length - 1].TrimEnd(';', ','); + + var parameterModel = parameterModels.FirstOrDefault(p => p.VariableName == variableName); + return parameterModel?.Schema?.Default != null + ? FormatDefaultValue(parameterModel.Schema.Default, parameterModel.Type) + : "default"; + } + + private static string FormatDefaultValue(object? defaultValue, string parameterType) + { + if (defaultValue == null) + return "default"; + + var type = parameterType.TrimEnd('?').Trim(); + + return type switch + { + "bool" => defaultValue.ToString()?.ToLowerInvariant() ?? "default", + "string" => $"\"{defaultValue}\"", + _ when IsNumericType(type) => defaultValue.ToString() ?? "default", + _ => "default" + }; + } + + private static bool IsNumericType(string type) + { + return type is "int" or "Int32" or "long" or "Int64" or "short" or "Int16" + or "byte" or "Byte" or "decimal" or "Decimal" or "float" or "Single" + or "double" or "Double" or "sbyte" or "SByte" or "uint" or "UInt32" + or "ulong" or "UInt64" or "ushort" or "UInt16"; + } + private static string GetQueryAttribute(CSharpParameterModel parameter, RefitGeneratorSettings settings) { return (parameter, settings) switch @@ -156,9 +199,11 @@ private static string GetAliasAsAttribute(CSharpParameterModel parameterModel) = private static string JoinAttributes(params string[] attributes) { - var filteredAttributes = attributes.Where(a => !string.IsNullOrWhiteSpace(a)); + var filteredAttributes = attributes + .Where(a => !string.IsNullOrWhiteSpace(a)) + .ToList(); - if (!filteredAttributes.Any()) + if (filteredAttributes.Count == 0) return string.Empty; return "[" + string.Join(", ", filteredAttributes) + "] "; @@ -267,6 +312,12 @@ private static List GetQueryParameters(CSharpOperationModel operationMod {{attributes}} {{modifier}} {{propertyType}} {{propertyName}} { get; {{setterStyle}}; } """); + var defaultValue = operationParameter.Schema.Default; + if (defaultValue != null) + { + var formattedDefaultValue = FormatDefaultValue(defaultValue, propertyType); + propertiesCodeBuilder.Append($" = {formattedDefaultValue};"); + } propertiesCodeBuilder.AppendLine(); operationModel.Parameters.Remove(operationParameter); } @@ -316,12 +367,12 @@ private static List GetQueryParameters(CSharpOperationModel operationMod private static void AppendXmlDocComment(string description, StringBuilder codeBuilder) { codeBuilder.Append( -$$""" +""" /// """); var lines = description.Split( - new[] { "\r\n", "\r", "\n" }, + ["\r\n", "\r", "\n"], StringSplitOptions.None); foreach (var line in lines) @@ -335,7 +386,7 @@ private static void AppendXmlDocComment(string description, StringBuilder codeBu codeBuilder.AppendLine(); codeBuilder.Append( -$$""" +""" /// """); codeBuilder.AppendLine(); diff --git a/src/Refitter.Tests/Examples/DynamicQueryStringParametersWithDefaultValuesTests.cs b/src/Refitter.Tests/Examples/DynamicQueryStringParametersWithDefaultValuesTests.cs new file mode 100644 index 000000000..ac838a9eb --- /dev/null +++ b/src/Refitter.Tests/Examples/DynamicQueryStringParametersWithDefaultValuesTests.cs @@ -0,0 +1,140 @@ +using FluentAssertions; +using Refitter.Core; +using Refitter.Tests.Build; +using Refitter.Tests.TestUtilities; +using Xunit; + +namespace Refitter.Tests.Examples; + +public class DynamicQueryStringParametersWithDefaultValuesTests +{ + private const string OpenApiSpec = + """ + { + "openapi": "3.0.1", + "info": { + "title": "Test API", + "version": "v1" + }, + "paths": { + "/api/schedule/list": { + "get": { + "tags": ["Schedule"], + "operationId": "List", + "parameters": [ + { + "name": "start", + "in": "query", + "required": true, + "schema": { + "type": "string", + "format": "date" + } + }, + { + "name": "end", + "in": "query", + "required": true, + "schema": { + "type": "string", + "format": "date" + } + }, + { + "name": "includeCancelled", + "in": "query", + "required": false, + "schema": { + "type": "boolean", + "default": true + } + }, + { + "name": "pageSize", + "in": "query", + "required": false, + "schema": { + "type": "integer", + "format": "int32", + "default": 10 + } + }, + { + "name": "filter", + "in": "query", + "required": false, + "schema": { + "type": "string", + "default": "active" + } + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "type": "object" + } + } + } + } + } + } + } + } + } + } + """; + + [Fact] + public async Task Can_Generate_Code() + { + string generatedCode = await GenerateCode(); + generatedCode.Should().NotBeNullOrWhiteSpace(); + } + + [Fact] + public async Task Generated_Code_Should_Have_Optional_Parameters_With_Default_Values() + { + string generatedCode = await GenerateCode(); + generatedCode.Should().Contain("bool? IncludeCancelled { get; set; } = true"); + generatedCode.Should().Contain("int? PageSize { get; set; } = 10"); + generatedCode.Should().Contain("string? Filter { get; set; } = \"active\""); + } + + [Fact] + public async Task Generated_Code_Should_Have_Required_Parameters_Without_Defaults() + { + string generatedCode = await GenerateCode(); + generatedCode.Should().Contain("System.DateTimeOffset Start { get; set; }"); + generatedCode.Should().Contain("System.DateTimeOffset End { get; set; }"); + generatedCode.Should().NotContain("System.DateTimeOffset Start { get; set; } ="); + generatedCode.Should().NotContain("System.DateTimeOffset End { get; set; } ="); + } + + [Fact] + public async Task Can_Build_Generated_Code() + { + string generatedCode = await GenerateCode(); + BuildHelper.BuildCSharp(generatedCode).Should().BeTrue(); + } + + private static async Task GenerateCode() + { + var swaggerFile = await SwaggerFileHelper.CreateSwaggerFile(OpenApiSpec); + var settings = new RefitGeneratorSettings + { + OpenApiPath = swaggerFile, + OptionalParameters = true, + UseDynamicQuerystringParameters = true, + }; + + var sut = await RefitGenerator.CreateAsync(settings); + var code = sut.Generate(); + return code; + } +} diff --git a/src/Refitter.Tests/Examples/OptionalParametersWithDefaultValuesTests.cs b/src/Refitter.Tests/Examples/OptionalParametersWithDefaultValuesTests.cs new file mode 100644 index 000000000..4f34c2f0c --- /dev/null +++ b/src/Refitter.Tests/Examples/OptionalParametersWithDefaultValuesTests.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 OptionalParametersWithDefaultValuesTests +{ + private const string OpenApiSpec = + """ + { + "openapi": "3.0.1", + "info": { + "title": "Test API", + "version": "v1" + }, + "paths": { + "/api/schedule/list": { + "get": { + "tags": ["Schedule"], + "operationId": "List", + "parameters": [ + { + "name": "start", + "in": "query", + "required": true, + "schema": { + "type": "string", + "format": "date" + } + }, + { + "name": "end", + "in": "query", + "required": true, + "schema": { + "type": "string", + "format": "date" + } + }, + { + "name": "includeCancelled", + "in": "query", + "required": false, + "schema": { + "type": "boolean", + "default": true + } + }, + { + "name": "pageSize", + "in": "query", + "required": false, + "schema": { + "type": "integer", + "format": "int32", + "default": 10 + } + }, + { + "name": "filter", + "in": "query", + "required": false, + "schema": { + "type": "string", + "default": "active" + } + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "type": "object" + } + } + } + } + } + } + } + } + } + } + """; + + [Fact] + public async Task Can_Generate_Code() + { + string generatedCode = await GenerateCode(); + generatedCode.Should().NotBeNullOrWhiteSpace(); + } + + [Fact] + public async Task Generated_Code_Should_Have_Optional_Parameters_With_Default_Values() + { + string generatedCode = await GenerateCode(); + generatedCode.Should().Contain("bool? includeCancelled = true"); + generatedCode.Should().Contain("int? pageSize = 10"); + generatedCode.Should().Contain("string? filter = \"active\""); + } + + [Fact] + public async Task Generated_Code_Should_Have_Required_Parameters_Without_Defaults() + { + string generatedCode = await GenerateCode(); + generatedCode.Should().Contain("[Query] System.DateTimeOffset start"); + generatedCode.Should().Contain("[Query] System.DateTimeOffset end"); + generatedCode.Should().NotContain("System.DateTimeOffset start ="); + generatedCode.Should().NotContain("System.DateTimeOffset end ="); + } + + [Fact] + public async Task Can_Build_Generated_Code() + { + string generatedCode = await GenerateCode(); + BuildHelper.BuildCSharp(generatedCode).Should().BeTrue(); + } + + private static async Task GenerateCode() + { + var swaggerFile = await SwaggerFileHelper.CreateSwaggerFile(OpenApiSpec); + var settings = new RefitGeneratorSettings + { + OpenApiPath = swaggerFile, + OptionalParameters = true + }; + + var sut = await RefitGenerator.CreateAsync(settings); + var code = sut.Generate(); + return code; + } +}