Skip to content

Commit

Permalink
Merge pull request #65 from christianhelle/file-upload
Browse files Browse the repository at this point in the history
Add support for multipart/form-data file uploads
  • Loading branch information
christianhelle authored Jun 12, 2023
2 parents 8d1a303 + 73c03c6 commit 3ccc3bd
Show file tree
Hide file tree
Showing 6 changed files with 131 additions and 16 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -269,4 +269,5 @@ test/**/*Petstore*.cs
test/**/*Uber.cs
test/**/*Uspto.cs
test/**/*Ingram-micro.cs
test/**/*Hubspot*.cs
*.lutconfig
3 changes: 3 additions & 0 deletions src/.vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"dotnet.defaultSolution": "Refitter.sln"
}
10 changes: 3 additions & 7 deletions src/Refitter.Core/OperationNameGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ public class OperationNameGenerator : IOperationNameGenerator

public OperationNameGenerator(OpenApiDocument document)
{
if (this.CheckForDuplicateOperationIds(document))
if (CheckForDuplicateOperationIds(document))
defaultGenerator = new MultipleClientsFromFirstTagAndPathSegmentsOperationNameGenerator();
}

Expand All @@ -33,12 +33,8 @@ public string GetOperationName(
.CapitalizeFirstCharacter()
.ConvertKebabCaseToPascalCase()
.ConvertRouteToCamelCase();
}

public static class IOperationNameGeneratorExtensions
{
public static bool CheckForDuplicateOperationIds(
this IOperationNameGenerator generator,
public bool CheckForDuplicateOperationIds(
OpenApiDocument document)
{
List<string> operationNames = new();
Expand All @@ -48,7 +44,7 @@ public static bool CheckForDuplicateOperationIds(
{
var operation = operations.Value;
operationNames.Add(
generator.GetOperationName(
GetOperationName(
document,
kv.Key,
operations.Key,
Expand Down
14 changes: 6 additions & 8 deletions src/Refitter.Core/ParameterExtractor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,10 @@ namespace Refitter.Core;
public static class ParameterExtractor
{
public static IEnumerable<string> GetParameters(
CustomCSharpClientGenerator generator,
CSharpOperationModel operationModel,
OpenApiOperation operation,
RefitGeneratorSettings settings)
{
var operationModel = generator.CreateOperationModel(operation);

var routeParameters = operationModel.Parameters
.Where(p => p.Kind == OpenApiParameterKind.Path)
.Select(p => $"{JoinAttributes(GetAliasAsAttribute(p))}{p.Type} {p.VariableName}")
Expand All @@ -32,25 +30,25 @@ public static IEnumerable<string> GetParameters(

var headerParameters = new List<string>();

if(settings.GenerateOperationHeaders)
if (settings.GenerateOperationHeaders)
{
headerParameters = operationModel.Parameters
.Where(p => p.Kind == OpenApiParameterKind.Header && p.IsHeader)
.Select(p => $"{JoinAttributes($"Header(\"{p.Name}\")")}{GetBodyParameterType(p)} {p.VariableName}")
.ToList();
}

var multipartFormParameters = operationModel.Parameters
.Where(p => p.Kind == OpenApiParameterKind.Body && p.IsBinaryBodyParameter)
.Select(p => $"{JoinAttributes("Body(BodySerializationMethod.UrlEncoded)", GetAliasAsAttribute(p))}Dictionary<string, object> {p.VariableName}")
var binaryBodyParameters = operationModel.Parameters
.Where(p => p.Kind == OpenApiParameterKind.Body && p.IsBinaryBodyParameter || p.IsFile)
.Select(p => $"{GetAliasAsAttribute(p)}StreamPart {p.VariableName}")
.ToList();

var parameters = new List<string>();
parameters.AddRange(routeParameters);
parameters.AddRange(queryParameters);
parameters.AddRange(bodyParameters);
parameters.AddRange(headerParameters);
parameters.AddRange(multipartFormParameters);
parameters.AddRange(binaryBodyParameters);

if (settings.UseCancellationTokens)
parameters.Add("CancellationToken cancellationToken = default");
Expand Down
8 changes: 7 additions & 1 deletion src/Refitter.Core/RefitInterfaceGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -51,11 +51,17 @@ private string GenerateInterfaceBody()
var name = generator.BaseSettings.OperationNameGenerator
.GetOperationName(document, kv.Key, verb, operation);

var parameters = ParameterExtractor.GetParameters(generator, operation, settings);
var operationModel = generator.CreateOperationModel(operation);
var parameters = ParameterExtractor.GetParameters(operationModel, operation, settings);
var parametersString = string.Join(", ", parameters);

GenerateMethodXmlDocComments(operation, code);

if (operationModel.Consumes.Contains("multipart/form-data"))
{
code.AppendLine($"{Separator}{Separator}[Multipart]");
}

code.AppendLine($"{Separator}{Separator}[{verb}(\"{kv.Key}\")]")
.AppendLine($"{Separator}{Separator}{returnType} {name}({parametersString});")
.AppendLine();
Expand Down
111 changes: 111 additions & 0 deletions src/Refitter.Tests/MultiPartFormDataTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
using FluentAssertions;
using Refitter.Core;
using Refitter.Tests.Build;
using Xunit;

namespace Refitter.Tests;

public class MultiPartFormDataTests
{
private const string OpenApiSpec = @"
{
""openapi"":""3.0.2"",
""paths"":{
""/foo/{id}/files"":{
""post"":{
""summary"":""uploads a file"",
""operationId"":""uploadFile"",
""parameters"":[
{
""name"":""id"",
""in"":""path"",
""description"":""Id of the foo resource"",
""required"":true,
""schema"":{
""type"":""integer"",
""format"":""int64""
}
}
],
""requestBody"":{
""content"":{
""multipart/form-data"":{
""schema"":{
""type"":""object"",
""properties"":{
""formFile"":{
""type"":""string"",
""format"":""binary""
}
}
},
""encoding"":{
""formFile"":{
""style"":""form""
}
}
}
}
},
""responses"":{
""200"":{
""description"":""successful operation""
}
}
}
}
}
}
";

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

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

[Fact]
public async Task Generated_Code_Contains_MultiPart_Attribute()
{
string generateCode = await GenerateCode();
generateCode.Should().Contain("[Multipart]");
}

[Fact]
public async Task Generated_Code_Contains_StreamPart_Parameter()
{
string generateCode = await GenerateCode();
generateCode.Should().Contain("StreamPart");
}

private static async Task<string> GenerateCode()
{
var swaggerFile = await CreateSwaggerFile(OpenApiSpec);
var settings = new RefitGeneratorSettings { OpenApiPath = swaggerFile };

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;
}
}

0 comments on commit 3ccc3bd

Please sign in to comment.