From 59026c45d07f7b9117c2a18ab4d463ce0c9fae0a Mon Sep 17 00:00:00 2001 From: Honza Dvorsky Date: Fri, 26 May 2023 13:47:54 +0200 Subject: [PATCH 1/2] Fix encoding of plain text bodies --- .../Conversion/Converter+Client.swift | 19 ++++- .../Conversion/Converter+Server.swift | 22 ++++- .../OpenAPIRuntime/Errors/RuntimeError.swift | 6 ++ .../Conversion/Test_Converter+Client.swift | 82 +++++++++++++++++++ .../Conversion/Test_Converter+Server.swift | 68 +++++++++++++++ Tests/OpenAPIRuntimeTests/Test_Runtime.swift | 8 ++ 6 files changed, 202 insertions(+), 3 deletions(-) diff --git a/Sources/OpenAPIRuntime/Conversion/Converter+Client.swift b/Sources/OpenAPIRuntime/Conversion/Converter+Client.swift index fa4e2c51..b9dc0667 100644 --- a/Sources/OpenAPIRuntime/Conversion/Converter+Client.swift +++ b/Sources/OpenAPIRuntime/Conversion/Converter+Client.swift @@ -90,7 +90,18 @@ extension Converter { from data: Data, transforming transform: (T) -> C ) throws -> C { - let decoded = try decoder.decode(type, from: data) + let decoded: T + if let myType = T.self as? _StringParameterConvertible.Type { + guard + let stringValue = String(data: data, encoding: .utf8), + let decodedValue = myType.init(stringValue) + else { + throw RuntimeError.failedToDecodePrimitiveBodyFromData + } + decoded = decodedValue as! T + } else { + decoded = try decoder.decode(type, from: data) + } return transform(decoded) } @@ -128,6 +139,12 @@ extension Converter { ) throws -> Data { let body = transform(value) headerFields.add(name: "content-type", value: body.contentType) + if let value = value as? _StringParameterConvertible { + guard let data = value.description.data(using: .utf8) else { + throw RuntimeError.failedToEncodePrimitiveBodyIntoData + } + return data + } return try encoder.encode(body.value) } diff --git a/Sources/OpenAPIRuntime/Conversion/Converter+Server.swift b/Sources/OpenAPIRuntime/Conversion/Converter+Server.swift index e5762a7a..d2467fde 100644 --- a/Sources/OpenAPIRuntime/Conversion/Converter+Server.swift +++ b/Sources/OpenAPIRuntime/Conversion/Converter+Server.swift @@ -262,7 +262,18 @@ public extension Converter { guard let data else { return nil } - let decoded = try decoder.decode(type, from: data) + let decoded: T + if let myType = T.self as? _StringParameterConvertible.Type { + guard + let stringValue = String(data: data, encoding: .utf8), + let decodedValue = myType.init(stringValue) + else { + throw RuntimeError.failedToDecodePrimitiveBodyFromData + } + decoded = decodedValue as! T + } else { + decoded = try decoder.decode(type, from: data) + } return transform(decoded) } @@ -297,7 +308,14 @@ public extension Converter { ) throws -> Data { let body = transform(value) headerFields.add(name: "content-type", value: body.contentType) - return try encoder.encode(body.value) + let bodyValue = body.value + if let value = bodyValue as? _StringParameterConvertible { + guard let data = value.description.data(using: .utf8) else { + throw RuntimeError.failedToEncodePrimitiveBodyIntoData + } + return data + } + return try encoder.encode(bodyValue) } // MARK: Body - Data diff --git a/Sources/OpenAPIRuntime/Errors/RuntimeError.swift b/Sources/OpenAPIRuntime/Errors/RuntimeError.swift index 4c9b5ec4..67b90ed7 100644 --- a/Sources/OpenAPIRuntime/Errors/RuntimeError.swift +++ b/Sources/OpenAPIRuntime/Errors/RuntimeError.swift @@ -36,6 +36,8 @@ internal enum RuntimeError: Error, CustomStringConvertible, LocalizedError, Pret // Body case missingRequiredRequestBody + case failedToEncodePrimitiveBodyIntoData + case failedToDecodePrimitiveBodyFromData // Transport/Handler case transportFailed(Error) @@ -69,6 +71,10 @@ internal enum RuntimeError: Error, CustomStringConvertible, LocalizedError, Pret return "Failed to decode query parameter named '\(name)' to type \(type)." case .missingRequiredRequestBody: return "Missing required request body" + case .failedToEncodePrimitiveBodyIntoData: + return "Failed to encode a primitive body into data" + case .failedToDecodePrimitiveBodyFromData: + return "Failed to decode a primitive body from data" case .transportFailed(let underlyingError): return "Transport failed with error: \(underlyingError.localizedDescription)" case .handlerFailed(let underlyingError): diff --git a/Tests/OpenAPIRuntimeTests/Conversion/Test_Converter+Client.swift b/Tests/OpenAPIRuntimeTests/Conversion/Test_Converter+Client.swift index 80c0e1e4..afe6664c 100644 --- a/Tests/OpenAPIRuntimeTests/Conversion/Test_Converter+Client.swift +++ b/Tests/OpenAPIRuntimeTests/Conversion/Test_Converter+Client.swift @@ -72,6 +72,24 @@ final class Test_ClientConverterExtensions: Test_Runtime { ) XCTAssertEqual(body, testStruct) } + + func testBodyGetData_success() throws { + let body = try converter.bodyGet( + Data.self, + from: testStructData, + transforming: { $0 } + ) + XCTAssertEqual(body, testStructData) + } + + func testBodyGetString_success() throws { + let body = try converter.bodyGet( + String.self, + from: testStringData, + transforming: { $0 } + ) + XCTAssertEqual(body, testString) + } func testBodyAddComplexOptional_success() throws { var headerFields: [HeaderField] = [] @@ -116,4 +134,68 @@ final class Test_ClientConverterExtensions: Test_Runtime { ] ) } + + func testBodyAddDataOptional_success() throws { + var headerFields: [HeaderField] = [] + let data = try converter.bodyAddOptional( + testStructPrettyData, + headerFields: &headerFields, + transforming: { .init(value: $0, contentType: "application/octet-stream") } + ) + XCTAssertEqual(data, testStructPrettyData) + XCTAssertEqual( + headerFields, + [ + .init(name: "content-type", value: "application/octet-stream") + ] + ) + } + + func testBodyAddDataRequired_success() throws { + var headerFields: [HeaderField] = [] + let data = try converter.bodyAddRequired( + testStructPrettyData, + headerFields: &headerFields, + transforming: { .init(value: $0, contentType: "application/octet-stream") } + ) + XCTAssertEqual(data, testStructPrettyData) + XCTAssertEqual( + headerFields, + [ + .init(name: "content-type", value: "application/octet-stream") + ] + ) + } + + func testBodyAddStringOptional_success() throws { + var headerFields: [HeaderField] = [] + let data = try converter.bodyAddOptional( + testString, + headerFields: &headerFields, + transforming: { .init(value: $0, contentType: "text/plain") } + ) + XCTAssertEqual(data, testStringData) + XCTAssertEqual( + headerFields, + [ + .init(name: "content-type", value: "text/plain") + ] + ) + } + + func testBodyAddStringRequired_success() throws { + var headerFields: [HeaderField] = [] + let data = try converter.bodyAddRequired( + testString, + headerFields: &headerFields, + transforming: { .init(value: $0, contentType: "text/plain") } + ) + XCTAssertEqual(data, testStringData) + XCTAssertEqual( + headerFields, + [ + .init(name: "content-type", value: "text/plain") + ] + ) + } } diff --git a/Tests/OpenAPIRuntimeTests/Conversion/Test_Converter+Server.swift b/Tests/OpenAPIRuntimeTests/Conversion/Test_Converter+Server.swift index 92ef4c15..1cd4c879 100644 --- a/Tests/OpenAPIRuntimeTests/Conversion/Test_Converter+Server.swift +++ b/Tests/OpenAPIRuntimeTests/Conversion/Test_Converter+Server.swift @@ -537,6 +537,38 @@ final class Test_ServerConverterExtensions: Test_Runtime { ] ) } + + func testBodyAddString() throws { + var headers: [HeaderField] = [] + let data = try converter.bodyAdd( + testString, + headerFields: &headers, + transforming: { .init(value: $0, contentType: "text/plain") } + ) + XCTAssertEqual(String(data: data, encoding: .utf8)!, testString) + XCTAssertEqual( + headers, + [ + .init(name: "content-type", value: "text/plain") + ] + ) + } + + func testBodyAddData() throws { + var headers: [HeaderField] = [] + let data = try converter.bodyAdd( + testStructPrettyData, + headerFields: &headers, + transforming: { .init(value: $0, contentType: "application/octet-stream") } + ) + XCTAssertEqual(data, testStructPrettyData) + XCTAssertEqual( + headers, + [ + .init(name: "content-type", value: "application/octet-stream") + ] + ) + } func testBodyGetComplexOptional_success() throws { let body = try converter.bodyGetOptional( @@ -584,4 +616,40 @@ final class Test_ServerConverterExtensions: Test_Runtime { } ) } + + func testBodyGetDataOptional_success() throws { + let body = try converter.bodyGetOptional( + Data.self, + from: testStructPrettyData, + transforming: { $0 } + ) + XCTAssertEqual(body, testStructPrettyData) + } + + func testBodyGetDataRequired_success() throws { + let body = try converter.bodyGetOptional( + Data.self, + from: testStructPrettyData, + transforming: { $0 } + ) + XCTAssertEqual(body, testStructPrettyData) + } + + func testBodyGetStringOptional_success() throws { + let body = try converter.bodyGetOptional( + String.self, + from: testStringData, + transforming: { $0 } + ) + XCTAssertEqual(body, testString) + } + + func testBodyGetStringRequired_success() throws { + let body = try converter.bodyGetOptional( + String.self, + from: testStringData, + transforming: { $0 } + ) + XCTAssertEqual(body, testString) + } } diff --git a/Tests/OpenAPIRuntimeTests/Test_Runtime.swift b/Tests/OpenAPIRuntimeTests/Test_Runtime.swift index 37ae2b05..b338deaa 100644 --- a/Tests/OpenAPIRuntimeTests/Test_Runtime.swift +++ b/Tests/OpenAPIRuntimeTests/Test_Runtime.swift @@ -54,6 +54,14 @@ class Test_Runtime: XCTestCase { "2023-01-18T10:04:11Z" } + var testString: String { + "hello" + } + + var testStringData: Data { + "hello".data(using: .utf8)! + } + var testStruct: TestPet { .init(name: "Fluffz") } From 0cf839e521a2adddb5a81385bfb9033b40ddb4e1 Mon Sep 17 00:00:00 2001 From: Honza Dvorsky Date: Fri, 26 May 2023 14:06:01 +0200 Subject: [PATCH 2/2] Formatting fixes --- .../Conversion/Test_Converter+Client.swift | 6 +++--- .../Conversion/Test_Converter+Server.swift | 6 +++--- Tests/OpenAPIRuntimeTests/Test_Runtime.swift | 2 +- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/Tests/OpenAPIRuntimeTests/Conversion/Test_Converter+Client.swift b/Tests/OpenAPIRuntimeTests/Conversion/Test_Converter+Client.swift index afe6664c..d0696ef2 100644 --- a/Tests/OpenAPIRuntimeTests/Conversion/Test_Converter+Client.swift +++ b/Tests/OpenAPIRuntimeTests/Conversion/Test_Converter+Client.swift @@ -72,7 +72,7 @@ final class Test_ClientConverterExtensions: Test_Runtime { ) XCTAssertEqual(body, testStruct) } - + func testBodyGetData_success() throws { let body = try converter.bodyGet( Data.self, @@ -134,7 +134,7 @@ final class Test_ClientConverterExtensions: Test_Runtime { ] ) } - + func testBodyAddDataOptional_success() throws { var headerFields: [HeaderField] = [] let data = try converter.bodyAddOptional( @@ -166,7 +166,7 @@ final class Test_ClientConverterExtensions: Test_Runtime { ] ) } - + func testBodyAddStringOptional_success() throws { var headerFields: [HeaderField] = [] let data = try converter.bodyAddOptional( diff --git a/Tests/OpenAPIRuntimeTests/Conversion/Test_Converter+Server.swift b/Tests/OpenAPIRuntimeTests/Conversion/Test_Converter+Server.swift index 1cd4c879..d8855615 100644 --- a/Tests/OpenAPIRuntimeTests/Conversion/Test_Converter+Server.swift +++ b/Tests/OpenAPIRuntimeTests/Conversion/Test_Converter+Server.swift @@ -537,7 +537,7 @@ final class Test_ServerConverterExtensions: Test_Runtime { ] ) } - + func testBodyAddString() throws { var headers: [HeaderField] = [] let data = try converter.bodyAdd( @@ -553,7 +553,7 @@ final class Test_ServerConverterExtensions: Test_Runtime { ] ) } - + func testBodyAddData() throws { var headers: [HeaderField] = [] let data = try converter.bodyAdd( @@ -616,7 +616,7 @@ final class Test_ServerConverterExtensions: Test_Runtime { } ) } - + func testBodyGetDataOptional_success() throws { let body = try converter.bodyGetOptional( Data.self, diff --git a/Tests/OpenAPIRuntimeTests/Test_Runtime.swift b/Tests/OpenAPIRuntimeTests/Test_Runtime.swift index b338deaa..451b07ec 100644 --- a/Tests/OpenAPIRuntimeTests/Test_Runtime.swift +++ b/Tests/OpenAPIRuntimeTests/Test_Runtime.swift @@ -61,7 +61,7 @@ class Test_Runtime: XCTestCase { var testStringData: Data { "hello".data(using: .utf8)! } - + var testStruct: TestPet { .init(name: "Fluffz") }