diff --git a/Sources/OpenAPIRuntime/Conversion/Converter+Client.swift b/Sources/OpenAPIRuntime/Conversion/Converter+Client.swift index b9dc0667..91324316 100644 --- a/Sources/OpenAPIRuntime/Conversion/Converter+Client.swift +++ b/Sources/OpenAPIRuntime/Conversion/Converter+Client.swift @@ -90,19 +90,27 @@ extension Converter { from data: Data, transforming transform: (T) -> C ) throws -> C { - 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(try decoder.decode(type, from: data)) + } + + /// Gets a deserialized value from body data. + /// - Parameters: + /// - type: Type used to decode the data. + /// - data: Encoded body data. + /// - transform: Closure for transforming the Decodable type into a final type. + /// - Returns: Deserialized body value. + public func bodyGet( + _ type: T.Type, + from data: Data, + transforming transform: (T) -> C + ) throws -> C { + guard + let stringValue = String(data: data, encoding: .utf8), + let decodedValue = T(stringValue) + else { + throw RuntimeError.failedToDecodePrimitiveBodyFromData } - return transform(decoded) + return transform(decodedValue) } /// Provides an optional serialized value for the body value. @@ -126,6 +134,27 @@ extension Converter { ) } + /// Provides an optional serialized value for the body value. + /// - Parameters: + /// - value: Encodable value to turn into data. + /// - headerFields: Headers container where to add the Content-Type header. + /// - transform: Closure for transforming the Encodable value into body content. + /// - Returns: Data for the serialized body value, or nil if `value` was nil. + public func bodyAddOptional( + _ value: C?, + headerFields: inout [HeaderField], + transforming transform: (C) -> EncodableBodyContent + ) throws -> Data? { + guard let value else { + return nil + } + return try bodyAddRequired( + value, + headerFields: &headerFields, + transforming: transform + ) + } + /// Provides a required serialized value for the body value. /// - Parameters: /// - value: Encodable value to turn into data. @@ -139,15 +168,28 @@ 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) } + /// Provides a required serialized value for the body value. + /// - Parameters: + /// - value: Encodable value to turn into data. + /// - headerFields: Headers container where to add the Content-Type header. + /// - transform: Closure for transforming the Encodable value into body content. + /// - Returns: Data for the serialized body value. + public func bodyAddRequired( + _ value: C, + headerFields: inout [HeaderField], + transforming transform: (C) -> EncodableBodyContent + ) throws -> Data where C: _StringParameterConvertible { + let body = transform(value) + headerFields.add(name: "content-type", value: body.contentType) + guard let data = body.value.description.data(using: .utf8) else { + throw RuntimeError.failedToEncodePrimitiveBodyIntoData + } + return data + } + // MARK: Body - Primivite - Data /// Gets a deserialized value from body data. diff --git a/Sources/OpenAPIRuntime/Conversion/Converter+Common.swift b/Sources/OpenAPIRuntime/Conversion/Converter+Common.swift index 2df14252..176c1154 100644 --- a/Sources/OpenAPIRuntime/Conversion/Converter+Common.swift +++ b/Sources/OpenAPIRuntime/Conversion/Converter+Common.swift @@ -162,10 +162,6 @@ public extension Converter { guard let value else { return } - if let value = value as? _StringParameterConvertible { - headerFields.add(name: name, value: value.description) - return - } let data = try headerFieldEncoder.encode(value) guard let stringValue = String(data: data, encoding: .utf8) else { throw RuntimeError.failedToEncodeJSONHeaderIntoString(name: name) @@ -173,6 +169,24 @@ public extension Converter { headerFields.add(name: name, value: stringValue) } + /// Adds a header field with the provided name and encodable value. + /// + /// Encodes the value into minimized JSON. + /// - Parameters: + /// - headerFields: Collection of header fields to add to. + /// - name: Header name. + /// - value: Encodable header value. + func headerFieldAdd( + in headerFields: inout [HeaderField], + name: String, + value: T? + ) throws { + guard let value else { + return + } + headerFields.add(name: name, value: value.description) + } + /// Returns the value of the first header field for the given name. /// /// Decodes the value from JSON. @@ -189,11 +203,26 @@ public extension Converter { guard let stringValue = headerFields.firstValue(name: name) else { return nil } - if let myType = T.self as? _StringParameterConvertible.Type { - return myType.init(stringValue).map { $0 as! T } + return try decoder.decode(T.self, from: Data(stringValue.utf8)) + } + + /// Returns the value of the first header field for the given name. + /// + /// Decodes the value from JSON. + /// - Parameters: + /// - headerFields: Collection of header fields to retrieve the field from. + /// - name: Header name (case-insensitive). + /// - type: Date type. + /// - Returns: First value for the given name, if one exists. + func headerFieldGetOptional( + in headerFields: [HeaderField], + name: String, + as type: T.Type + ) throws -> T? { + guard let stringValue = headerFields.firstValue(name: name) else { + return nil } - let data = Data(stringValue.utf8) - return try decoder.decode(T.self, from: data) + return T(stringValue) } /// Returns the first header value for the given (case-insensitive) name. @@ -220,4 +249,29 @@ public extension Converter { } return value } + + /// Returns the first header value for the given (case-insensitive) name. + /// + /// Decodes the value from JSON. + /// - Parameters: + /// - headerFields: Collection of header fields to retrieve the field from. + /// - name: Header name (case-insensitive). + /// - type: Date type. + /// - Returns: First value for the given name. + func headerFieldGetRequired( + in headerFields: [HeaderField], + name: String, + as type: T.Type + ) throws -> T { + guard + let value = try headerFieldGetOptional( + in: headerFields, + name: name, + as: type + ) + else { + throw RuntimeError.missingRequiredHeader(name) + } + return value + } } diff --git a/Sources/OpenAPIRuntime/Conversion/Converter+Server.swift b/Sources/OpenAPIRuntime/Conversion/Converter+Server.swift index d2467fde..353cc12f 100644 --- a/Sources/OpenAPIRuntime/Conversion/Converter+Server.swift +++ b/Sources/OpenAPIRuntime/Conversion/Converter+Server.swift @@ -262,19 +262,30 @@ public extension Converter { guard let data else { return nil } - 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(try decoder.decode(type, from: data)) + } + + /// Gets a deserialized value from body data, if present. + /// - Parameters: + /// - type: Type used to decode the data. + /// - data: Encoded body data. + /// - transform: Closure for transforming the Decodable type into a final type. + /// - Returns: Deserialized body value, if present. + func bodyGetOptional( + _ type: T.Type, + from data: Data?, + transforming transform: (T) -> C + ) throws -> C? { + guard let data else { + return nil } - return transform(decoded) + guard + let stringValue = String(data: data, encoding: .utf8), + let decodedValue = T(stringValue) + else { + throw RuntimeError.failedToDecodePrimitiveBodyFromData + } + return transform(decodedValue) } /// Gets a deserialized value from body data. @@ -308,14 +319,26 @@ public extension Converter { ) throws -> Data { let body = transform(value) headerFields.add(name: "content-type", value: body.contentType) - 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(body.value) + } + + /// Provides a serialized value for the provided body value. + /// - Parameters: + /// - value: Encodable value to turn into data. + /// - headerFields: Header fields container where to add the Content-Type header. + /// - transform: Closure for transforming the Encodable value into body content. + /// - Returns: Data for the serialized body value. + func bodyAdd( + _ value: C, + headerFields: inout [HeaderField], + transforming transform: (C) -> EncodableBodyContent + ) throws -> Data { + let body = transform(value) + headerFields.add(name: "content-type", value: body.contentType) + guard let data = body.value.description.data(using: .utf8) else { + throw RuntimeError.failedToEncodePrimitiveBodyIntoData } - return try encoder.encode(bodyValue) + return data } // MARK: Body - Data