Skip to content

Commit

Permalink
Generating IObservable type response
Browse files Browse the repository at this point in the history
  • Loading branch information
Jan Folbrecht committed Feb 21, 2024
1 parent 9f1a9fc commit 0b3e451
Show file tree
Hide file tree
Showing 6 changed files with 79 additions and 44 deletions.
21 changes: 15 additions & 6 deletions src/Refitter.Core/RefitInterfaceGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -79,9 +79,11 @@ private string GenerateInterfaceBody()
protected string GetTypeName(OpenApiOperation operation)
{
if (settings.ResponseTypeOverride.TryGetValue(operation.OperationId, out var type))
return type is null or "void" ? "Task" : $"Task<{WellKnownNamesspaces.TrimImportedNamespaces(type)}>";
{
return type is null or "void" ? GetAsyncOperationType(true) : $"{GetAsyncOperationType(false)}<{WellKnownNamesspaces.TrimImportedNamespaces(type)}>";
}

var returnTypeParameter =
var returnTypeParameter =
(new[] { "200", "201", "203", "206" })
.Where(operation.Responses.ContainsKey)
.Select(code => GetTypeName(code, operation))
Expand Down Expand Up @@ -172,9 +174,10 @@ protected string GetReturnType(string? returnTypeParameter)

private string GetDefaultReturnType()
{
var asyncType = GetAsyncOperationType(true);
return settings.ReturnIApiResponse
? "Task<IApiResponse>"
: "Task";
? $"{asyncType}<IApiResponse>"
: asyncType;
}

/// <summary>
Expand All @@ -193,11 +196,17 @@ protected static bool IsApiResponseType(string typeName)

private string GetConfiguredReturnType(string returnTypeParameter)
{
var asyncType = GetAsyncOperationType(false);
return settings.ReturnIApiResponse
? $"Task<IApiResponse<{WellKnownNamesspaces.TrimImportedNamespaces(returnTypeParameter)}>>"
: $"Task<{WellKnownNamesspaces.TrimImportedNamespaces(returnTypeParameter)}>";
? $"{asyncType}<IApiResponse<{WellKnownNamesspaces.TrimImportedNamespaces(returnTypeParameter)}>>"
: $"{asyncType}<{WellKnownNamesspaces.TrimImportedNamespaces(returnTypeParameter)}>";
}

private string GetAsyncOperationType(bool withVoidReturnType) =>
settings.ReturnIObservable
? "IObservable" + (withVoidReturnType ? "<Unit>" : string.Empty)
: "Task";

protected void GenerateObsoleteAttribute(OpenApiOperation operation, StringBuilder code)
{
if (operation.IsDeprecated)
Expand Down
58 changes: 27 additions & 31 deletions src/Refitter.Core/RefitInterfaceImports.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,41 +4,37 @@ namespace Refitter.Core;

internal static class RefitInterfaceImports
{
public static string[] GetImportedNamespaces(RefitGeneratorSettings settings) =>
settings.UseCancellationTokens
? new[]
{
"Refit",
"System.Collections.Generic",
"System.Text.Json.Serialization",
"System.Threading",
"System.Threading.Tasks"
}
: new[]
{
"Refit",
"System.Collections.Generic",
"System.Text.Json.Serialization",
"System.Threading.Tasks"
};
private static string[] defaultNamespases = new[]
{
"Refit",
"System.Collections.Generic",
"System.Text.Json.Serialization",
};
public static string[] GetImportedNamespaces(RefitGeneratorSettings settings)
{
var namespaces = new List<string>(defaultNamespases);
if (settings.UseCancellationTokens)
{
namespaces.Add("System.Threading");
}

if (settings.ReturnIObservable)
{
namespaces.Add("System.Reactive");
}
else
{
namespaces.Add("System.Threading.Tasks");
}
return namespaces.ToArray();
}

[SuppressMessage(
"MicrosoftCodeAnalysisCorrectness",
"RS1035:Do not use APIs banned for analyzers",
Justification = "This tool is cross platform")]
public static string GenerateNamespaceImports(RefitGeneratorSettings settings) =>
settings.UseCancellationTokens
? string.Join(
Environment.NewLine,
"using Refit;",
"using System.Collections.Generic;",
"using System.Text.Json.Serialization;",
"using System.Threading;",
"using System.Threading.Tasks;")
: string.Join(
Environment.NewLine,
"using Refit;",
"using System.Collections.Generic;",
"using System.Text.Json.Serialization;",
"using System.Threading.Tasks;");
GetImportedNamespaces(settings)
.Select(ns => $"using {ns};")
.Aggregate((a, b) => $"{a}{Environment.NewLine}{b}");
}
9 changes: 7 additions & 2 deletions src/Refitter.Core/Settings/RefitGeneratorSettings.cs
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,11 @@ public class RefitGeneratorSettings
/// </summary>
public bool ReturnIApiResponse { get; set; }

/// <summary>
/// Gets or sets a value indicating whether to return IObservable or Task
/// </summary>
public bool ReturnIObservable { get; set; }

/// <summary>
/// Gets or sets a dictionary of operation ids and a specific response type that they should use. The type is
/// wrapped in a task, but otherwise unmodified (so make sure that the namespaces are imported or specified).
Expand Down Expand Up @@ -143,13 +148,13 @@ public class RefitGeneratorSettings
/// Gets or sets the settings describing how to generate types using NSwag
/// </summary>
public CodeGeneratorSettings? CodeGeneratorSettings { get; set; }

/// <summary>
/// Set to <c>true</c> to apply tree-shaking to the OpenApi schema.
/// This works in conjunction with <see cref="IncludeTags"/> and <see cref="IncludePathMatches"/>.
/// </summary>
public bool TrimUnusedSchema { get; set; }

/// <summary>
/// Array of regular expressions that determine if a schema needs to be kept.
/// This works in conjunction with <see cref="TrimUnusedSchema"/>.
Expand Down
19 changes: 19 additions & 0 deletions src/Refitter.Tests/SwaggerPetstoreTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -414,6 +414,25 @@ public async Task Can_Build_Generated_Code_With_IApiResponse(SampleOpenSpecifica
.BeTrue();
}

[Theory]
[InlineData(SampleOpenSpecifications.SwaggerPetstoreJsonV3, "SwaggerPetstore.json")]
public async Task Can_Build_Generated_Code_With_IObservableResponse(SampleOpenSpecifications version, string filename)
{
var settings = new RefitGeneratorSettings();
settings.ReturnIObservable = true;
var generateCode = await GenerateCode(version, filename, settings);
//cannot build without it because System.Reactive package has to be installed first
generateCode += @"
namespace System.Reactive
{
public class Unit{}
}";
BuildHelper
.BuildCSharp(generateCode)
.Should()
.BeTrue();
}

[Theory]
[InlineData(SampleOpenSpecifications.SwaggerPetstoreJsonV3, "SwaggerPetstore.json")]
#if !DEBUG
Expand Down
5 changes: 3 additions & 2 deletions src/Refitter/GenerateCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ public override async Task<int> ExecuteAsync(CommandContext context, Settings se
AddAcceptHeaders = !settings.NoAcceptHeaders,
GenerateContracts = !settings.InterfaceOnly,
ReturnIApiResponse = settings.ReturnIApiResponse,
ReturnIObservable = settings.ReturnIObservable,
UseCancellationTokens = settings.UseCancellationTokens,
GenerateOperationHeaders = !settings.NoOperationHeaders,
UseIsoDateFormat = settings.UseIsoDateFormat,
Expand Down Expand Up @@ -70,7 +71,7 @@ public override async Task<int> ExecuteAsync(CommandContext context, Settings se
var generator = await RefitGenerator.CreateAsync(refitGeneratorSettings);
if (!settings.SkipValidation)
await ValidateOpenApiSpec(refitGeneratorSettings.OpenApiPath);

var code = generator.Generate().ReplaceLineEndings();
AnsiConsole.MarkupLine($"[green]Length: {code.Length} bytes[/]");

Expand Down Expand Up @@ -99,7 +100,7 @@ public override async Task<int> ExecuteAsync(CommandContext context, Settings se
AnsiConsole.MarkupLine($"[red]Exception:{Crlf}{exception.GetType()}[/]");
AnsiConsole.MarkupLine($"[yellow]Stack Trace:{Crlf}{exception.StackTrace}[/]");
}

AnsiConsole.MarkupLine("[yellow]#############################################################################[/]");
AnsiConsole.MarkupLine("[yellow]# Consider reporting the problem if you are unable to resolve it yourself #[/]");
AnsiConsole.MarkupLine("[yellow]# https://github.com/christianhelle/refitter/issues #[/]");
Expand Down
11 changes: 8 additions & 3 deletions src/Refitter/Settings.cs
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,11 @@ public sealed class Settings : CommandSettings
[DefaultValue(false)]
public bool ReturnIApiResponse { get; set; }

[Description("Return IObservable instead of Task")]
[CommandOption("--use-observable-response")]
[DefaultValue(false)]
public bool ReturnIObservable { get; set; }

[Description("Set the accessibility of the generated types to 'internal'")]
[CommandOption("--internal")]
[DefaultValue(false)]
Expand Down Expand Up @@ -109,7 +114,7 @@ public sealed class Settings : CommandSettings
[CommandOption("--operation-name-template")]
[DefaultValue(null)]
public string? OperationNameTemplate { get; internal set; }

[Description("Generate nullable parameters as optional parameters")]
[CommandOption("--optional-nullable-parameters")]
[DefaultValue(false)]
Expand All @@ -119,7 +124,7 @@ public sealed class Settings : CommandSettings
[CommandOption("--trim-unused-schema")]
[DefaultValue(false)]
public bool TrimUnusedSchema { get; set; }

[Description("Force to keep matching schema, uses regular expressions. Use together with \"--trim-unused-schema\". Can be set multiple times.")]
[CommandOption("--keep-schema")]
[DefaultValue(new string[0])]
Expand All @@ -134,7 +139,7 @@ public sealed class Settings : CommandSettings
[CommandOption("--skip-default-additional-properties")]
[DefaultValue(false)]
public bool SkipDefaultAdditionalProperties { get; set; }

[Description("""
The NSwag IOperationNameGenerator implementation to use.
May be one of:
Expand Down

0 comments on commit 0b3e451

Please sign in to comment.