Skip to content

Commit 6d4fde1

Browse files
authored
Merge pull request #4 from genusP/feature/multipart
Fix generation for multipart (#3) + Some small fixes
2 parents f4da580 + a4f66ae commit 6d4fde1

11 files changed

+148
-50
lines changed

ApiCodeGenerator.OpenApi.Refit.Tests/ApiCodeGenerator.OpenApi.Refit.Tests.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
</ItemGroup>
2020

2121
<ItemGroup>
22-
<PackageReference Include="ApiCodeGenerator.OpenApi.Sdk" Version="3.0.0-rc.10" />
22+
<PackageReference Include="ApiCodeGenerator.OpenApi.Sdk" Version="3.0.0-rc.18" />
2323
<PackageReference Include="NSwag.Core.Yaml" Version="14.0.2" />
2424
<PackageReference Include="NSwag.CodeGeneration.CSharp" Version="14.0.2" />
2525
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.3.1" />

ApiCodeGenerator.OpenApi.Refit.Tests/FormDataTests.cs

Lines changed: 52 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -13,31 +13,34 @@ public class FormDataTests
1313
[Test]
1414
public async Task FormData()
1515
{
16-
var json = "{" + OpenApiDocumentDeclaration + @"
17-
""paths"": {
18-
""/test"": {
19-
""post"": {
20-
""operationId"": ""GetTest"",
21-
""requestBody"": {
22-
""content"":{
23-
""multipart/form-data"":{
24-
""schema"":{
25-
""type"": ""object"",
26-
""properties"": {
27-
""testProp"":{""type"":""string""}
28-
},
29-
""additionalProperties"": false
30-
},
31-
""encoding"":{""testProp"": {""style"":""form""}}
16+
var json = $$"""
17+
{
18+
{{OpenApiDocumentDeclaration}}
19+
"paths": {
20+
"/test": {
21+
"post": {
22+
"operationId": "GetTest",
23+
"requestBody": {
24+
"content":{
25+
"application/x-www-form-urlencoded":{
26+
"schema":{
27+
"type": "object",
28+
"properties": {
29+
"testProp":{"type":"string"}
30+
},
31+
"additionalProperties": false
32+
}
33+
}
34+
}
35+
},
36+
"responses": {
37+
"200": {"description": "valid input"}
38+
}
39+
}
40+
}
3241
}
3342
}
34-
},
35-
""responses"": {
36-
""200"": {""description"": ""valid input""}
37-
}
38-
}
39-
}
40-
}}";
43+
""";
4144
var document = await OpenApiDocument.FromJsonAsync(json);
4245
RefitCodeGeneratorSettings settings = new()
4346
{
@@ -53,7 +56,7 @@ public async Task FormData()
5356
" /// <returns>valid input</returns>\n" +
5457
" /// <exception cref=\"Refit.ApiException\">A server side error occurred.</exception>\n" +
5558
" [Post(\"/test\")]\n" +
56-
$" System.Threading.Tasks.Task GetTest([Body(BodySerializationMethod.UrlEncoded)]GetTestFormData getTestFormData);\n" +
59+
$" System.Threading.Tasks.Task GetTest([Body(BodySerializationMethod.UrlEncoded)]GetTestFormData body);\n" +
5760
"\n" +
5861
" }\n";
5962
var expectedClassCode =
@@ -66,5 +69,30 @@ public async Task FormData()
6669
" }\n";
6770
RunTest(settings, expectedInterfaceCode, document, expectedClassCode);
6871
}
72+
73+
[Test]
74+
public void FileUpload()
75+
{
76+
//Arrange
77+
var settings = new RefitCodeGeneratorSettings
78+
{
79+
GenerateClientInterfaces = true,
80+
CSharpGeneratorSettings = { Namespace = "TestNS" },
81+
};
82+
83+
var expected =
84+
" public partial interface IClient\n" +
85+
" {\n" +
86+
" /// <returns>OK</returns>\n" +
87+
" /// <exception cref=\"Refit.ApiException\">A server side error occurred.</exception>\n" +
88+
" [Multipart]\n" +
89+
" [Post(\"/file\")]\n" +
90+
" System.Threading.Tasks.Task Upload(int? id, StreamPart file, System.Collections.Generic.ICollection<StreamPart> fileArr);\n" +
91+
"\n" +
92+
" }\n";
93+
94+
//Act & Assert
95+
RunTest(settings, expected, "multipart.yml", " \n");
96+
}
6997
}
7098
}

ApiCodeGenerator.OpenApi.Refit.Tests/FunctionalTests.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ public void GenerateClientInterface_Query()
6363
" [Get(\"/testService/testOper\")]\n" +
6464
" System.Threading.Tasks.Task<TestOperResponse> GetTestOper([Header(\"headerParametr\")]string headerParametr, [Query(CollectionFormat.Multi)]System.Collections.Generic.IEnumerable<string> queryParametr, [Query][AliasAs(\"doted.queryParametr\")]string doted_queryParametr);\n" +
6565
"\n" +
66+
" /// <returns>OK</returns>\n" +
6667
" /// <exception cref=\"Refit.ApiException\">A server side error occurred.</exception>\n" +
6768
" [Post(\"/testService/testOper\")]\n" +
6869
" System.Threading.Tasks.Task CreateTestOper([Body]string a);\n" +
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
openapi: 3.0.0
2+
info:
3+
title: multipart test schema
4+
version: "1"
5+
paths:
6+
/file:
7+
post:
8+
operationId: upload
9+
requestBody:
10+
content:
11+
"multipart/form-data":
12+
schema:
13+
type: object
14+
properties:
15+
id:
16+
type: integer
17+
file:
18+
type: string
19+
format: binary
20+
fileArr:
21+
type: array
22+
items:
23+
type: string
24+
format: binary
25+
responses:
26+
"200":
27+
description: "OK"

ApiCodeGenerator.OpenApi.Refit.Tests/swagger/testSchema2.json

Lines changed: 24 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,16 @@
1313
}
1414
],
1515
"paths": {
16-
"testService/testOper": {
16+
"/testService/testOper": {
1717
"get": {
18-
"tags": [ "testOper" ],
18+
"tags": [
19+
"testOper"
20+
],
1921
"summary": "Test operation",
2022
"operationId": "getTestOperUsingGET",
21-
"produces": [ "application/json" ],
23+
"produces": [
24+
"application/json"
25+
],
2226
"parameters": [
2327
{
2428
"name": "headerParametr",
@@ -33,7 +37,9 @@
3337
"description": "Параметр передаваемый в строке запроса",
3438
"required": false,
3539
"type": "array",
36-
"items": { "type": "string" },
40+
"items": {
41+
"type": "string"
42+
},
3743
"collectionFormat": "multi",
3844
"allowEmptyValue": false
3945
},
@@ -56,17 +62,27 @@
5662
"deprecated": false
5763
},
5864
"post": {
59-
"tags": [ "testOper" ],
65+
"tags": [
66+
"testOper"
67+
],
6068
"operationId": "createTestOperUsingPOST",
6169
"parameters": [
6270
{
6371
"name": "a",
6472
"in": "body",
65-
"type": "string"
73+
"schema": {
74+
"type": "string"
75+
}
6676
}
6777
],
68-
"produces": [ "application/json" ],
69-
"responses": { "200": {} }
78+
"produces": [
79+
"application/json"
80+
],
81+
"responses": {
82+
"200": {
83+
"description": "OK"
84+
}
85+
}
7086
}
7187
}
7288
},

ApiCodeGenerator.OpenApi.Refit/ApiCodeGenerator.OpenApi.Refit.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
<Project Sdk="ApiCodeGenerator.OpenApi.Sdk/3.0.0-rc.10">
1+
<Project Sdk="ApiCodeGenerator.OpenApi.Sdk/3.0.0-rc.18">
22

33
<PropertyGroup>
44
<Description>Generates code for the Refit library from an OpenApi document.</Description>

ApiCodeGenerator.OpenApi.Refit/Model/RefitOperationModel.cs

Lines changed: 29 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ public RefitOperationModel(OpenApiOperation operation, RefitCodeGeneratorSetting
3636
_generator = generator;
3737
_resolver = resolver;
3838

39-
IEnumerable<OpenApiParameter> parameters = operation.ActualParameters;
39+
IEnumerable<OpenApiParameter> parameters = GetActualParameters();
4040

4141
if (Settings.AuthorizationHeaderParameter && RequiresAuthentication)
4242
{
@@ -58,14 +58,7 @@ public RefitOperationModel(OpenApiOperation operation, RefitCodeGeneratorSetting
5858

5959
Parameters = parameters
6060
.Select(parameter =>
61-
{
62-
if (operation.RequestBody?.Content?.ContainsKey("multipart/form-data") == true)
63-
{
64-
parameter.Style = OpenApiParameterStyle.Form;
65-
parameter.Name = ConversionUtilities.ConvertToLowerCamelCase($"{Id}FormData", true);
66-
}
67-
68-
return new RefitParameterModel(
61+
new RefitParameterModel(
6962
parameter.Name,
7063
GetParameterVariableName(parameter, operation.Parameters),
7164
GetParameterVariableIdentifier(parameter, operation.Parameters),
@@ -74,8 +67,7 @@ public RefitOperationModel(OpenApiOperation operation, RefitCodeGeneratorSetting
7467
operation.Parameters,
7568
Settings.CodeGeneratorSettings,
7669
generator,
77-
resolver);
78-
})
70+
resolver))
7971
.ToList<CSharpParameterModel>();
8072
}
8173

@@ -179,6 +171,32 @@ internal void InitWrappedQueryParameters()
179171
}
180172
}
181173

174+
protected override string ResolveParameterType(OpenApiParameter parameter)
175+
{
176+
if (ConsumesFormUrlEncoded && parameter.Kind == OpenApiParameterKind.Body)
177+
{
178+
var isNullable = parameter.IsRequired == false || parameter.IsNullable(Settings.CodeGeneratorSettings.SchemaType);
179+
var typeNameHint = $"{Id}FormData";
180+
return _resolver?.Resolve(parameter.ActualSchema, isNullable, typeNameHint);
181+
}
182+
183+
if (_resolver is not null && HasFormParameters)
184+
{
185+
var typeName = Settings.BinaryPartType;
186+
if (parameter.IsBinaryBodyParameter || parameter.ActualSchema.IsBinary)
187+
{
188+
return typeName;
189+
}
190+
191+
if (parameter.ActualSchema.IsArray && parameter.ActualSchema.Item.IsBinary)
192+
{
193+
return $"{Settings.CSharpGeneratorSettings.ArrayType}<{typeName}>";
194+
}
195+
}
196+
197+
return base.ResolveParameterType(parameter);
198+
}
199+
182200
private string GetOperationPath()
183201
{
184202
return $"{Settings.GenerateControllerName(ControllerName)}.{ActualOperationName}";

ApiCodeGenerator.OpenApi.Refit/RefitCodeGenerator.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,9 +30,9 @@ public RefitCodeGenerator(OpenApiDocument openApiDocument, RefitCodeGeneratorSet
3030
{
3131
OpenApiDocument = openApiDocument;
3232
_settings = settings;
33+
_settings.CSharpGeneratorSettings.ExcludedTypeNames = ["FileParameter", .. _settings.CSharpGeneratorSettings.ExcludedTypeNames];
3334
}
3435

35-
[System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1600:Elements should be documented", Justification = "internal")]
3636
internal RefitCodeGenerator(OpenApiDocument openApiDocument, RefitCodeGeneratorSettings settings)
3737
: this(openApiDocument, settings, CreateResolverWithExceptionSchema(settings.CSharpGeneratorSettings, openApiDocument))
3838
{

ApiCodeGenerator.OpenApi.Refit/RefitCodeGeneratorSettings.cs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,11 @@ public RefitCodeGeneratorSettings()
6060
/// <summary>
6161
/// Включает генерацию параметра для передачи заголовка авторизации.
6262
/// </summary>
63-
public bool AuthorizationHeaderParameter { get; internal set; }
63+
public bool AuthorizationHeaderParameter { get; set; }
64+
65+
/// <summary>
66+
/// Тип используемый для двоичного содержиого.
67+
/// </summary>
68+
public string BinaryPartType { get; set; } = "StreamPart";
6469
}
6570
}
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{% if parameter.IsHeader -%}
22
[Header("{{ parameter.Name }}")]{%- elsif parameter.IsQuery -%}
33
[Query{% if parameter.CollectionFormat %}(CollectionFormat.{{ parameter.CollectionFormat }}){% endif %}]{%- elsif parameter.IsBody -%}
4-
[Body{% if parameter.IsForm %}(BodySerializationMethod.UrlEncoded){% endif %}]{%- endif -%}
4+
[Body{% if operation.ConsumesFormUrlEncoded %}(BodySerializationMethod.UrlEncoded){% endif %}]{%- endif -%}
55
{%- if parameter.IsAliased -%}
66
[AliasAs("{{parameter.Name}}")]{% endif -%}
7-
{{- parameter.Type }} {{ parameter.VariableName }}{% if GenerateOptionalParameters and parameter.IsOptional %} = null{% endif %}
7+
{{- parameter.Type }} {{ parameter.VariableName }}{% if GenerateOptionalParameters and parameter.IsOptional %} = null{% endif %}
Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
11
{%- for errorResponse in operation.ErrorResponses -%}
22
[ErrorResponse(typeof({{ errorResponse.Type }}), {{ errorResponse.StatusCode }})]
33
{%- endfor -%}
4-
[{{ operation.HttpMethodUpper }}("/{{ operation.Path }}")]
4+
{%- if operation.HasFormParameters and operation.ConsumesFormUrlEncoded == false -%}
5+
[Multipart]
6+
{% endif -%}
7+
[{{ operation.HttpMethodUpper }}("/{{ operation.Path }}")]

0 commit comments

Comments
 (0)