Skip to content

Commit cc39f62

Browse files
authored
[kotlin][jvm-retrofit2] Use @http with hasBody for DELETE operations with a request body (#25469)
Retrofit rejects @Body, @FormUrlEncoded and @multipart on @delete, so a DELETE operation with a requestBody failed on its first call. Emit @http(method, path, hasBody = true) only when the method does not allow a body and the operation has body or form params. Other operations keep the short annotation, so existing output does not change. Fixes #25454
1 parent 295f6c2 commit cc39f62

3 files changed

Lines changed: 97 additions & 0 deletions

File tree

‎modules/openapi-generator/src/main/resources/kotlin-client/libraries/jvm-retrofit2/api.mustache‎

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -155,7 +155,17 @@ import okhttp3.MultipartBody
155155
{{/-first}}
156156
{{/prioritizedContentTypes}}
157157
{{/formParams}}
158+
{{^isBodyAllowed}}
159+
{{#hasBodyOrFormParams}}
160+
@HTTP(method = "{{httpMethod}}", path = "{{{path}}}", hasBody = true)
161+
{{/hasBodyOrFormParams}}
162+
{{^hasBodyOrFormParams}}
158163
@{{httpMethod}}("{{{path}}}")
164+
{{/hasBodyOrFormParams}}
165+
{{/isBodyAllowed}}
166+
{{#isBodyAllowed}}
167+
@{{httpMethod}}("{{{path}}}")
168+
{{/isBodyAllowed}}
159169
{{^nonPublicApi}}{{#explicitApi}}public {{/explicitApi}}{{/nonPublicApi}}{{^doNotUseRxAndCoroutines}}{{#useCoroutines}}suspend {{/useCoroutines}}{{/doNotUseRxAndCoroutines}}fun {{operationId}}({{^allParams}}){{/allParams}}{{#allParams}}{{>queryParams}}{{>pathParams}}{{>headerParams}}{{>bodyParams}}{{>formParams}}{{^-last}}, {{/-last}}{{#-last}}){{/-last}}{{/allParams}}{{#returnType}}: {{/returnType}}{{^returnType}}{{#useResponseAsReturnType}}: {{/useResponseAsReturnType}}{{/returnType}}{{^doNotUseRxAndCoroutines}}{{#useRxJava}}Observable<{{#isResponseFile}}ResponseBody{{/isResponseFile}}{{^isResponseFile}}{{{returnType}}}{{^returnType}}Unit{{/returnType}}{{/isResponseFile}}>{{/useRxJava}}{{#useRxJava2}}{{#returnType}}Single<{{#isResponseFile}}ResponseBody{{/isResponseFile}}{{^isResponseFile}}{{{returnType}}}{{/isResponseFile}}>{{/returnType}}{{^returnType}}Completable{{/returnType}}{{/useRxJava2}}{{#useRxJava3}}{{#returnType}}Single<{{#isResponseFile}}ResponseBody{{/isResponseFile}}{{^isResponseFile}}{{{returnType}}}{{/isResponseFile}}>{{/returnType}}{{^returnType}}Completable{{/returnType}}{{/useRxJava3}}{{#useCoroutines}}{{#useResponseAsReturnType}}Response<{{/useResponseAsReturnType}}{{#isResponseFile}}ResponseBody{{/isResponseFile}}{{^isResponseFile}}{{{returnType}}}{{^returnType}}{{#useResponseAsReturnType}}Unit{{/useResponseAsReturnType}}{{/returnType}}{{/isResponseFile}}{{#useResponseAsReturnType}}>{{/useResponseAsReturnType}}{{/useCoroutines}}{{/doNotUseRxAndCoroutines}}{{#doNotUseRxAndCoroutines}}Call<{{#isResponseFile}}ResponseBody{{/isResponseFile}}{{^isResponseFile}}{{{returnType}}}{{^returnType}}Unit{{/returnType}}{{/isResponseFile}}>{{/doNotUseRxAndCoroutines}}
160170

161171
{{/operation}}

‎modules/openapi-generator/src/test/java/org/openapitools/codegen/kotlin/KotlinClientCodegenApiTest.java‎

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,29 @@ public void testUseResponseAsReturnType(Object useResponseAsReturnType, String e
103103
assertFileContainsLine(lines, "suspend fun deletePet(@Path(\"petId\") petId: kotlin.Long, @Header(\"api_key\") apiKey: kotlin.String? = null)" + expectedUnitResponse);
104104
}
105105

106+
@Test
107+
public void testRetrofit2DeleteWithBodyUsesHttpAnnotation_25454() throws IOException {
108+
OpenAPI openAPI = readOpenAPI("src/test/resources/3_0/kotlin/issue25454-delete-with-body.yaml");
109+
110+
KotlinClientCodegen codegen = createCodegen(ClientLibrary.JVM_RETROFIT2);
111+
112+
ClientOptInput input = createClientOptInput(openAPI, codegen);
113+
114+
DefaultGenerator generator = new DefaultGenerator();
115+
enableOnlyApiGeneration(generator);
116+
117+
List<File> files = generator.opts(input).generate();
118+
File defaultApi = files.stream().filter(file -> file.getName().equals("DefaultApi.kt")).findAny().orElseThrow();
119+
List<String> lines = Files.readAllLines(defaultApi.toPath()).stream().map(String::trim).collect(Collectors.toList());
120+
121+
// Retrofit rejects @Body, @FormUrlEncoded and @Multipart on @DELETE, so these need @HTTP with hasBody
122+
assertFileContainsLine(lines, "@HTTP(method = \"DELETE\", path = \"items\", hasBody = true)");
123+
assertFileContainsLine(lines, "@HTTP(method = \"DELETE\", path = \"items/form\", hasBody = true)");
124+
// operations without a body, and methods that allow a body, keep the short annotation
125+
assertFileContainsLine(lines, "@DELETE(\"items/{id}\")");
126+
assertFileContainsLine(lines, "@POST(\"items\")");
127+
}
128+
106129
@Test
107130
public void testOptionalParamsHaveDefaultNullJvmKtor() throws IOException {
108131
OpenAPI openAPI = readOpenAPI("3_0/kotlin/petstore.yaml");
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
openapi: 3.0.3
2+
info:
3+
title: Repro
4+
version: 1.0.0
5+
paths:
6+
/items:
7+
delete:
8+
operationId: deleteItems
9+
requestBody:
10+
required: true
11+
content:
12+
application/json:
13+
schema:
14+
$ref: "#/components/schemas/DeleteItemsRequest"
15+
responses:
16+
"204":
17+
description: Deleted
18+
post:
19+
operationId: createItems
20+
requestBody:
21+
required: true
22+
content:
23+
application/json:
24+
schema:
25+
$ref: "#/components/schemas/DeleteItemsRequest"
26+
responses:
27+
"204":
28+
description: Created
29+
/items/form:
30+
delete:
31+
operationId: deleteItemsForm
32+
requestBody:
33+
content:
34+
application/x-www-form-urlencoded:
35+
schema:
36+
type: object
37+
properties:
38+
id:
39+
type: string
40+
responses:
41+
"204":
42+
description: Deleted
43+
/items/{id}:
44+
delete:
45+
operationId: deleteItem
46+
parameters:
47+
- name: id
48+
in: path
49+
required: true
50+
schema:
51+
type: string
52+
responses:
53+
"204":
54+
description: Deleted
55+
components:
56+
schemas:
57+
DeleteItemsRequest:
58+
type: object
59+
required: [ids]
60+
properties:
61+
ids:
62+
type: array
63+
items:
64+
type: string

0 commit comments

Comments
 (0)