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..d0696ef2 100644 --- a/Tests/OpenAPIRuntimeTests/Conversion/Test_Converter+Client.swift +++ b/Tests/OpenAPIRuntimeTests/Conversion/Test_Converter+Client.swift @@ -73,6 +73,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] = [] let data = try converter.bodyAddOptional( @@ -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..d8855615 100644 --- a/Tests/OpenAPIRuntimeTests/Conversion/Test_Converter+Server.swift +++ b/Tests/OpenAPIRuntimeTests/Conversion/Test_Converter+Server.swift @@ -538,6 +538,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( TestPet.self, @@ -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..451b07ec 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") }