Skip to content
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
172 changes: 87 additions & 85 deletions README.md

Large diffs are not rendered by default.

11 changes: 6 additions & 5 deletions src/Refitter.Core/ParameterExtractor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ public static IEnumerable<string> GetParameters(
CSharpOperationModel operationModel,
OpenApiOperation operation,
RefitGeneratorSettings settings,
string dynamicQuerystringParameterType,
string dynamicQuerystringParameterType,
out string? dynamicQuerystringParameters)
{
var routeParameters = operationModel.Parameters
Expand Down Expand Up @@ -46,7 +46,7 @@ public static IEnumerable<string> GetParameters(
var generatedAliasAsAttribute = string.IsNullOrWhiteSpace(GetAliasAsAttribute(p))
? string.Empty
: $"[{GetAliasAsAttribute(p)}]";

return $"{generatedAliasAsAttribute}StreamPart {p.VariableName}";
})
.ToList();
Expand Down Expand Up @@ -90,7 +90,8 @@ private static string GetQueryAttribute(CSharpParameterModel parameter, RefitGen
return (parameter, settings) switch
{
{ parameter.IsArray: true } => "Query(CollectionFormat.Multi)",
{ parameter.IsDate: true, settings.UseIsoDateFormat: true } => "Query(Format = \"yyyy-MM-dd\")",
{ parameter.IsDateOrDateTime: true, settings.UseIsoDateFormat: true } => "Query(Format = \"yyyy-MM-dd\")",
{ parameter.IsDateOrDateTime: true, settings.CodeGeneratorSettings: not null } => $"Query(Format = \"{settings.CodeGeneratorSettings?.DateFormat}\")",
_ => "Query",
};
}
Expand Down Expand Up @@ -211,7 +212,7 @@ private static List<string> GetQueryParameters(CSharpOperationModel operationMod
}

dynamicQuerystringParametersCodeBuilder.AppendLine(
$$"""
$$"""
{{modifier}} {{classStyle}} {{dynamicQuerystringParameterType}}
{
""");
Expand All @@ -224,7 +225,7 @@ private static List<string> GetQueryParameters(CSharpOperationModel operationMod
{
{{initializedParametersCodeBuilder}}
}
""");
""");
}

dynamicQuerystringParametersCodeBuilder.AppendLine(
Expand Down
2 changes: 2 additions & 0 deletions src/Refitter.Core/Settings/CodeGeneratorSettings.cs
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,8 @@ public class CodeGeneratorSettings
/// </summary>
public string[] ExcludedTypeNames { get; set; } = Array.Empty<string>();

public string DateFormat { get; set; } = "yyyy-MM-dd";

/// <summary>
/// Gets or sets a custom <see cref="IPropertyNameGenerator"/>.
/// </summary>
Expand Down
12 changes: 7 additions & 5 deletions src/Refitter.SourceGenerator/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

Refitter is available as a C# Source Generator that uses the [Refitter.Core](https://github.com/christianhelle/refitter/tree/main/src/Refitter.Core) library for generating a REST API Client using the [Refit](https://github.com/reactiveui/refit) library. Refitter can generate the Refit interface from OpenAPI specifications. Refitter could format the generated Refit interface to be managed by [Apizr](https://www.apizr.net) (v6+) and generate some registration helpers too.

The Refitter source generator is a bit untraditional in a sense that it creates a folder called `Generated` in the same location as the `.refitter` file and generates files to disk under the `Generated` folder (can be changed with `--outputFolder`). The source generator output should be included in the project and committed to source control. This is done because there is no other way to trigger the Refit source generator to pickup the Refitter generated code
The Refitter source generator is a bit untraditional in a sense that it creates a folder called `Generated` in the same location as the `.refitter` file and generates files to disk under the `Generated` folder (can be changed with `--outputFolder`). The source generator output should be included in the project and committed to source control. This is done because there is no other way to trigger the Refit source generator to pickup the Refitter generated code

***(Translation: I couldn't for the life of me figure how to get that to work, sorry)***

Expand Down Expand Up @@ -71,9 +71,9 @@ The following is an example `.refitter` file
"dependencyInjectionSettings": { // Optional
"baseUrl": "https://petstore3.swagger.io/api/v3", // Optional. Leave this blank to set the base address manually
"httpMessageHandlers": [ // Optional
"AuthorizationMessageHandler",
"TelemetryMessageHandler"
],
"AuthorizationMessageHandler",
"TelemetryMessageHandler"
],
"transientErrorHandler": "Polly", // Optional. Value=None|Polly|HttpResilience
"maxRetryCount": 3, // Optional. Default=6
"firstBackoffRetryInSeconds": 0.5 // Optional. Default=1.0
Expand Down Expand Up @@ -116,6 +116,7 @@ The following is an example `.refitter` file
"generateNativeRecords": false,
"generateDefaultValues": true,
"inlineNamedAny": false,
"dateFormat": "yyyy-MM-dd",
"excludedTypeNames": [
"ExcludedTypeFoo",
"ExcludedTypeBar"
Expand Down Expand Up @@ -151,7 +152,7 @@ The following is an example `.refitter` file
- `dependencyInjectionSettings` - Setting this will generated extension methods to `IServiceCollection` for configuring Refit clients. See https://github.com/reactiveui/refit?tab=readme-ov-file#dynamic-querystring-parameters for more information.
- `baseUrl` - Used as the HttpClient base address. Leave this blank to manually set the base URL
- `httpMessageHandlers` - A collection of `HttpMessageHandler` that is added to the HttpClient pipeline
- `transientErrorHandler` - This is the transient error handler to use. Possible values are `None`, `Polly`, and `HttpResilience`. Default is `None`
- `transientErrorHandler` - This is the transient error handler to use. Possible values are `None`, `Polly`, and `HttpResilience`. Default is `None`
- `maxRetryCount` - This is the max retry count used in the Polly retry policy. Default is 6
- `firstBackoffRetryInSeconds` - This is the duration of the initial retry backoff. Default is 1 second
- `apizrSettings` - Setting this will format Refit interface to be managed by Apizr. See https://www.apizr.net for more information
Expand Down Expand Up @@ -191,4 +192,5 @@ The following is an example `.refitter` file
- `generateNativeRecords` - Default is false
- `generateDefaultValues` - Default is true
- `inlineNamedAny` - Default is false
- `dateFormat` - Default is `yyyy-MM-dd`
- `excludedTypeNames` - Default is empty
132 changes: 132 additions & 0 deletions src/Refitter.Tests/Examples/CustomDateFormatTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
using FluentAssertions;
using Refitter.Core;
using Refitter.Tests.Build;
using Xunit;

namespace Refitter.Tests.Examples;

public class CustomDateFormatTests
{

private const string OpenApiSpec = @"
{
""swagger"": ""2.0"",
""info"": {
""title"": ""XX"",
""version"": ""0.0.0""
},
""host"": ""x.io"",
""basePath"": ""/"",
""schemes"": [
""https""
],
""paths"": {
""/t/dummy/{employee_id}"": {
""get"": {
""summary"": ""X"",
""description"": ""X"",
""operationId"": ""dummy"",
""parameters"": [
{
""name"": ""employee_id"",
""in"": ""path"",
""description"": ""the specific employee"",
""required"": true,
""format"": ""int64"",
""type"": ""integer""
},
{
""name"": ""valid_from"",
""in"": ""query"",
""description"": ""the start of the period"",
""required"": true,
""format"": ""date"",
""type"": ""string""
},
{
""name"": ""valid_to"",
""in"": ""query"",
""description"": ""the end of the period"",
""required"": true,
""format"": ""date"",
""type"": ""string""
},
{
""name"": ""test_time"",
""in"": ""query"",
""description"": ""test parameter"",
""required"": true,
""format"": ""time"",
""type"": ""string""
}
],
""responses"": {
""200"": {
""description"": ""No response was specified""
}
}
}
},
}
}
";


[Fact]
public async Task Can_Generate_Code()
{
string generateCode = await GenerateCode();
generateCode.Should().NotBeNullOrWhiteSpace();
}

[Fact]
public async Task GeneratedCode_Contains_Date_Format_String()
{
string generateCode = await GenerateCode();
generateCode.Should().Contain(@"[Query(Format = ""yyyy-MM-ddThh:mm:ssZ"")] ");
}

[Fact]
public async Task GeneratedCode_Contains_TimeSpan_Parameter()
{
string generateCode = await GenerateCode();
generateCode.Should().Contain("[Query] System.TimeSpan");
}

[Fact]
public async Task Can_Build_Generated_Code()
{
string generateCode = await GenerateCode();
BuildHelper
.BuildCSharp(generateCode)
.Should()
.BeTrue();
}

private static async Task<string> GenerateCode()
{
var swaggerFile = await CreateSwaggerFile(OpenApiSpec);
var settings = new RefitGeneratorSettings
{
OpenApiPath = swaggerFile,
CodeGeneratorSettings = new CodeGeneratorSettings
{
DateFormat = "yyyy-MM-ddThh:mm:ssZ"
}
};

var sut = await RefitGenerator.CreateAsync(settings);
var generateCode = sut.Generate();
return generateCode;
}

private static async Task<string> CreateSwaggerFile(string contents)
{
var filename = $"{Guid.NewGuid()}.json";
var folder = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString());
Directory.CreateDirectory(folder);
var swaggerFile = Path.Combine(folder, filename);
await File.WriteAllTextAsync(swaggerFile, contents);
return swaggerFile;
}
}
Loading