Skip to content

Commit 8a007c7

Browse files
Enable the generated server-side code to validate the content-type. (#140)
### Motivation I am interested this tool and have selected something that I can work on. Resolve #16 ### Modifications To validate content-type header string in server program, I modify `traslateServerDesirializer` method . ### Result For example you will define below opeapi yaml ``` /greet: post: operationId: postGreeting requestBody: required: true content: text/plain: schema: type: string ``` when you try the request, runtime error happen. ``` $ curl localhost:8080/api/greet -X POST \ > -H "Content-Type: application/json" \ > -d '{ "name": "foo" }' {"error":true,"reason":"Server error - operationID: postGreeting, request: path: \/api\/greet, query: <nil>, method: HTTPMethod(value: OpenAPIRuntime.HTTPMethod.(unknown context at $106e9fd78).OpenAPIHTTPMethod.POST), header fields: [Host: localhost:8080, User-Agent: curl\/7.88.1, Accept: *\/*, Content-Type: application\/json, Content-Length: 64], body (prefix): { \"name\": \"foo\"}, metadata: path parameters: [:], query parameters: [], operationInput: <nil>, operationOutput: <nil>, underlying error: Unexpected Content-Type header: application\/json"} ``` ### Test Plan I have made the necessary changes to adapt to the newly generated code on the reference server side and have confirmed that it passes the tests locally. --------- Co-authored-by: Honza Dvorsky <[email protected]> Co-authored-by: Honza Dvorsky <[email protected]>
1 parent b6de8a1 commit 8a007c7

File tree

3 files changed

+72
-5
lines changed

3 files changed

+72
-5
lines changed

Sources/_OpenAPIGeneratorCore/Translator/ServerTranslator/translateServerMethod.swift

Lines changed: 29 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,29 @@ extension ServerFileTranslator {
2121
func translateServerDeserializer(
2222
_ operation: OperationDescription
2323
) throws -> Expression {
24+
var closureBody: [CodeBlock] = []
25+
26+
let typedRequestBody = try typedRequestBody(in: operation)
27+
28+
if let headerValueForValidation = typedRequestBody?
29+
.content
30+
.content
31+
.contentType
32+
.headerValueForValidation
33+
{
34+
let validateContentTypeExpr: Expression = .try(
35+
.identifier("converter").dot("validateContentTypeIfPresent")
36+
.call([
37+
.init(label: "in", expression: .identifier("request").dot("headerFields")),
38+
.init(
39+
label: "substring",
40+
expression: .literal(headerValueForValidation)
41+
),
42+
])
43+
)
44+
closureBody.append(.expression(validateContentTypeExpr))
45+
}
46+
2447
let inputTypeName = operation.inputTypeName
2548

2649
func locationSpecificInputDecl(
@@ -67,7 +90,7 @@ extension ServerFileTranslator {
6790
.map(locationSpecificInputDecl(locatedIn:fromParameters:))
6891

6992
let bodyDecl = try translateRequestBodyInServer(
70-
typedRequestBody(in: operation),
93+
typedRequestBody,
7194
requestVariableName: "request",
7295
bodyVariableName: "body",
7396
inputTypeName: inputTypeName
@@ -94,11 +117,13 @@ extension ServerFileTranslator {
94117
])
95118
)
96119

120+
closureBody.append(
121+
contentsOf: inputMemberDecls.map(CodeBlock.declaration) + [.expression(returnExpr)]
122+
)
123+
97124
return .closureInvocation(
98125
argumentNames: ["request", "metadata"],
99-
body: inputMemberDecls.map(CodeBlock.declaration) + [
100-
.expression(returnExpr)
101-
]
126+
body: closureBody
102127
)
103128
}
104129

Tests/OpenAPIGeneratorReferenceTests/Resources/ReferenceSources/Petstore/Server.swift

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -180,7 +180,12 @@ fileprivate extension UniversalServer where APIHandler: APIProtocol {
180180
with: metadata,
181181
forOperation: Operations.createPet.id,
182182
using: APIHandler.createPet,
183-
deserializer: { request, metadata in let path: Operations.createPet.Input.Path = .init()
183+
deserializer: { request, metadata in
184+
try converter.validateContentTypeIfPresent(
185+
in: request.headerFields,
186+
substring: "application/json"
187+
)
188+
let path: Operations.createPet.Input.Path = .init()
184189
let query: Operations.createPet.Input.Query = .init()
185190
let headers: Operations.createPet.Input.Headers = .init(
186191
X_Extra_Arguments: try converter.getOptionalHeaderFieldAsJSON(
@@ -309,6 +314,10 @@ fileprivate extension UniversalServer where APIHandler: APIProtocol {
309314
forOperation: Operations.updatePet.id,
310315
using: APIHandler.updatePet,
311316
deserializer: { request, metadata in
317+
try converter.validateContentTypeIfPresent(
318+
in: request.headerFields,
319+
substring: "application/json"
320+
)
312321
let path: Operations.updatePet.Input.Path = .init(
313322
petId: try converter.getPathParameterAsText(
314323
in: metadata.pathParameters,
@@ -380,6 +389,10 @@ fileprivate extension UniversalServer where APIHandler: APIProtocol {
380389
forOperation: Operations.uploadAvatarForPet.id,
381390
using: APIHandler.uploadAvatarForPet,
382391
deserializer: { request, metadata in
392+
try converter.validateContentTypeIfPresent(
393+
in: request.headerFields,
394+
substring: "application/octet-stream"
395+
)
383396
let path: Operations.uploadAvatarForPet.Input.Path = .init(
384397
petId: try converter.getPathParameterAsText(
385398
in: metadata.pathParameters,

Tests/PetstoreConsumerTests/Test_Server.swift

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -231,6 +231,35 @@ final class Test_Server: XCTestCase {
231231
)
232232
}
233233

234+
func testCreatePet_withIncorrectContentType() async throws {
235+
client = .init(
236+
createPetBlock: { input in
237+
XCTFail("The handler should not have been called")
238+
fatalError("Unreachable")
239+
}
240+
)
241+
242+
do {
243+
_ = try await server.createPet(
244+
.init(
245+
path: "/api/pets",
246+
method: .post,
247+
headerFields: [
248+
.init(name: "x-extra-arguments", value: #"{"code":1}"#),
249+
.init(name: "content-type", value: "text/plain; charset=utf-8"),
250+
],
251+
encodedBody: #"""
252+
{
253+
"name" : "Fluffz"
254+
}
255+
"""#
256+
),
257+
.init()
258+
)
259+
XCTFail("The method should have thrown an error.")
260+
} catch {}
261+
}
262+
234263
func testUpdatePet_204_withBody() async throws {
235264
client = .init(
236265
updatePetBlock: { input in

0 commit comments

Comments
 (0)