From e6526b0181eceadee9da5ae97fc5ae7062bc73fd Mon Sep 17 00:00:00 2001 From: Honza Dvorsky Date: Thu, 24 Aug 2023 13:24:33 +0200 Subject: [PATCH 01/11] WIP on integrating the experimental URI coder --- .../Base/_AutoLosslessStringConvertible.swift | 2 + .../Base/_StringConvertible.swift | 14 +++++ .../Conversion/Converter+Client.swift | 6 ++ .../Conversion/Converter+Common.swift | 58 ++++++++++--------- .../Conversion/Converter+Server.swift | 8 +++ .../Conversion/CurrencyExtensions.swift | 4 ++ .../Deprecated/Deprecated.swift | 32 ++++++++++ 7 files changed, 98 insertions(+), 26 deletions(-) diff --git a/Sources/OpenAPIRuntime/Base/_AutoLosslessStringConvertible.swift b/Sources/OpenAPIRuntime/Base/_AutoLosslessStringConvertible.swift index 38116c73..c7588d8e 100644 --- a/Sources/OpenAPIRuntime/Base/_AutoLosslessStringConvertible.swift +++ b/Sources/OpenAPIRuntime/Base/_AutoLosslessStringConvertible.swift @@ -19,10 +19,12 @@ /// /// Cannot be marked as SPI, as it's added on public types, but should be /// considered an internal implementation detail of the generator. +@available(*, deprecated) public protocol _AutoLosslessStringConvertible: RawRepresentable, LosslessStringConvertible, _StringConvertible where RawValue == String {} +@available(*, deprecated) extension _AutoLosslessStringConvertible { public init?(_ description: String) { self.init(rawValue: description) diff --git a/Sources/OpenAPIRuntime/Base/_StringConvertible.swift b/Sources/OpenAPIRuntime/Base/_StringConvertible.swift index e680e822..69302270 100644 --- a/Sources/OpenAPIRuntime/Base/_StringConvertible.swift +++ b/Sources/OpenAPIRuntime/Base/_StringConvertible.swift @@ -18,12 +18,26 @@ import Foundation /// /// Cannot be marked as SPI, as it's added on public types, but should be /// considered an internal implementation detail of the generator. +@available(*, deprecated) public protocol _StringConvertible: LosslessStringConvertible {} +@available(*, deprecated) extension String: _StringConvertible {} + +@available(*, deprecated) extension Bool: _StringConvertible {} + +@available(*, deprecated) extension Int: _StringConvertible {} + +@available(*, deprecated) extension Int64: _StringConvertible {} + +@available(*, deprecated) extension Int32: _StringConvertible {} + +@available(*, deprecated) extension Float: _StringConvertible {} + +@available(*, deprecated) extension Double: _StringConvertible {} diff --git a/Sources/OpenAPIRuntime/Conversion/Converter+Client.swift b/Sources/OpenAPIRuntime/Conversion/Converter+Client.swift index 0e6ed7a2..6099a6df 100644 --- a/Sources/OpenAPIRuntime/Conversion/Converter+Client.swift +++ b/Sources/OpenAPIRuntime/Conversion/Converter+Client.swift @@ -16,6 +16,7 @@ import Foundation extension Converter { // | client | set | request path | text | string-convertible | required | renderedRequestPath | + @available(*, deprecated) public func renderedRequestPath( template: String, parameters: [any _StringConvertible] @@ -34,6 +35,7 @@ extension Converter { } // | client | set | request query | text | string-convertible | both | setQueryItemAsText | + @available(*, deprecated) public func setQueryItemAsText( in request: inout Request, style: ParameterStyle?, @@ -52,6 +54,7 @@ extension Converter { } // | client | set | request query | text | array of string-convertibles | both | setQueryItemAsText | + @available(*, deprecated) public func setQueryItemAsText( in request: inout Request, style: ParameterStyle?, @@ -106,6 +109,7 @@ extension Converter { } // | client | set | request body | text | string-convertible | optional | setOptionalRequestBodyAsText | + @available(*, deprecated) public func setOptionalRequestBodyAsText( _ value: T?, headerFields: inout [HeaderField], @@ -120,6 +124,7 @@ extension Converter { } // | client | set | request body | text | string-convertible | required | setRequiredRequestBodyAsText | + @available(*, deprecated) public func setRequiredRequestBodyAsText( _ value: T, headerFields: inout [HeaderField], @@ -218,6 +223,7 @@ extension Converter { } // | client | get | response body | text | string-convertible | required | getResponseBodyAsText | + @available(*, deprecated) public func getResponseBodyAsText( _ type: T.Type, from data: Data, diff --git a/Sources/OpenAPIRuntime/Conversion/Converter+Common.swift b/Sources/OpenAPIRuntime/Conversion/Converter+Common.swift index 373b8105..fc4f931d 100644 --- a/Sources/OpenAPIRuntime/Conversion/Converter+Common.swift +++ b/Sources/OpenAPIRuntime/Conversion/Converter+Common.swift @@ -70,33 +70,35 @@ extension Converter { // MARK: - Converter helper methods - // | common | set | header field | text | string-convertible | both | setHeaderFieldAsText | - public func setHeaderFieldAsText( - in headerFields: inout [HeaderField], - name: String, - value: T? - ) throws { - try setHeaderField( - in: &headerFields, - name: name, - value: value, - convert: convertStringConvertibleToText - ) - } + // // | common | set | header field | uri | codable | both | setHeaderFieldAsText | + // @available(*, deprecated) + // public func setHeaderFieldAsURI( + // in headerFields: inout [HeaderField], + // name: String, + // value: T? + // ) throws { + // try setHeaderField( + // in: &headerFields, + // name: name, + // value: value, + // convert: convertStringConvertibleToText + // ) + // } - // | common | set | header field | text | array of string-convertibles | both | setHeaderFieldAsText | - public func setHeaderFieldAsText( - in headerFields: inout [HeaderField], - name: String, - value values: [T]? - ) throws { - try setHeaderFields( - in: &headerFields, - name: name, - values: values, - convert: convertStringConvertibleToText - ) - } + // // | common | set | header field | uri | array of string-convertibles | both | setHeaderFieldAsText | + // @available(*, deprecated) + // public func setHeaderFieldAsURI( + // in headerFields: inout [HeaderField], + // name: String, + // value values: [T]? + // ) throws { + // try setHeaderFields( + // in: &headerFields, + // name: name, + // values: values, + // convert: convertStringConvertibleToText + // ) + // } // | common | set | header field | text | date | both | setHeaderFieldAsText | public func setHeaderFieldAsText( @@ -141,6 +143,7 @@ extension Converter { } // | common | get | header field | text | string-convertible | optional | getOptionalHeaderFieldAsText | + @available(*, deprecated) public func getOptionalHeaderFieldAsText( in headerFields: [HeaderField], name: String, @@ -155,6 +158,7 @@ extension Converter { } // | common | get | header field | text | string-convertible | required | getRequiredHeaderFieldAsText | + @available(*, deprecated) public func getRequiredHeaderFieldAsText( in headerFields: [HeaderField], name: String, @@ -169,6 +173,7 @@ extension Converter { } // | common | get | header field | text | array of string-convertibles | optional | getOptionalHeaderFieldAsText | + @available(*, deprecated) public func getOptionalHeaderFieldAsText( in headerFields: [HeaderField], name: String, @@ -183,6 +188,7 @@ extension Converter { } // | common | get | header field | text | array of string-convertibles | required | getRequiredHeaderFieldAsText | + @available(*, deprecated) public func getRequiredHeaderFieldAsText( in headerFields: [HeaderField], name: String, diff --git a/Sources/OpenAPIRuntime/Conversion/Converter+Server.swift b/Sources/OpenAPIRuntime/Conversion/Converter+Server.swift index 0a0ac42b..b132f445 100644 --- a/Sources/OpenAPIRuntime/Conversion/Converter+Server.swift +++ b/Sources/OpenAPIRuntime/Conversion/Converter+Server.swift @@ -58,6 +58,7 @@ public extension Converter { } // | server | get | request path | text | string-convertible | required | getPathParameterAsText | + @available(*, deprecated) func getPathParameterAsText( in pathParameters: [String: String], name: String, @@ -72,6 +73,7 @@ public extension Converter { } // | server | get | request query | text | string-convertible | optional | getOptionalQueryItemAsText | + @available(*, deprecated) func getOptionalQueryItemAsText( in queryParameters: [URLQueryItem], style: ParameterStyle?, @@ -90,6 +92,7 @@ public extension Converter { } // | server | get | request query | text | string-convertible | required | getRequiredQueryItemAsText | + @available(*, deprecated) func getRequiredQueryItemAsText( in queryParameters: [URLQueryItem], style: ParameterStyle?, @@ -108,6 +111,7 @@ public extension Converter { } // | server | get | request query | text | array of string-convertibles | optional | getOptionalQueryItemAsText | + @available(*, deprecated) func getOptionalQueryItemAsText( in queryParameters: [URLQueryItem], style: ParameterStyle?, @@ -126,6 +130,7 @@ public extension Converter { } // | server | get | request query | text | array of string-convertibles | required | getRequiredQueryItemAsText | + @available(*, deprecated) func getRequiredQueryItemAsText( in queryParameters: [URLQueryItem], style: ParameterStyle?, @@ -216,6 +221,7 @@ public extension Converter { } // | server | get | request body | text | string-convertible | optional | getOptionalRequestBodyAsText | + @available(*, deprecated) func getOptionalRequestBodyAsText( _ type: T.Type, from data: Data?, @@ -230,6 +236,7 @@ public extension Converter { } // | server | get | request body | text | string-convertible | required | getRequiredRequestBodyAsText | + @available(*, deprecated) func getRequiredRequestBodyAsText( _ type: T.Type, from data: Data?, @@ -328,6 +335,7 @@ public extension Converter { } // | server | set | response body | text | string-convertible | required | setResponseBodyAsText | + @available(*, deprecated) func setResponseBodyAsText( _ value: T, headerFields: inout [HeaderField], diff --git a/Sources/OpenAPIRuntime/Conversion/CurrencyExtensions.swift b/Sources/OpenAPIRuntime/Conversion/CurrencyExtensions.swift index 881aca62..2426f4cd 100644 --- a/Sources/OpenAPIRuntime/Conversion/CurrencyExtensions.swift +++ b/Sources/OpenAPIRuntime/Conversion/CurrencyExtensions.swift @@ -124,12 +124,14 @@ extension Converter { // MARK: Common functions for Converter's SPI helper methods + @available(*, deprecated) func convertStringConvertibleToText( _ value: T ) throws -> String { value.description } + @available(*, deprecated) func convertStringConvertibleToTextData( _ value: T ) throws -> Data { @@ -184,6 +186,7 @@ extension Converter { return try decoder.decode(T.self, from: data) } + @available(*, deprecated) func convertTextToStringConvertible( _ stringValue: String ) throws -> T { @@ -195,6 +198,7 @@ extension Converter { return value } + @available(*, deprecated) func convertTextDataToStringConvertible( _ data: Data ) throws -> T { diff --git a/Sources/OpenAPIRuntime/Deprecated/Deprecated.swift b/Sources/OpenAPIRuntime/Deprecated/Deprecated.swift index 351347c8..4fa22ec2 100644 --- a/Sources/OpenAPIRuntime/Deprecated/Deprecated.swift +++ b/Sources/OpenAPIRuntime/Deprecated/Deprecated.swift @@ -1179,4 +1179,36 @@ extension Converter { convert: convertTextToDate ) } + + // MARK: - Deprecated as part of moving to URI coder + + // | common | set | header field | text | string-convertible | both | setHeaderFieldAsText | + @available(*, deprecated) + public func setHeaderFieldAsText( + in headerFields: inout [HeaderField], + name: String, + value: T? + ) throws { + try setHeaderField( + in: &headerFields, + name: name, + value: value, + convert: convertStringConvertibleToText + ) + } + + // | common | set | header field | text | array of string-convertibles | both | setHeaderFieldAsText | + @available(*, deprecated) + public func setHeaderFieldAsText( + in headerFields: inout [HeaderField], + name: String, + value values: [T]? + ) throws { + try setHeaderFields( + in: &headerFields, + name: name, + values: values, + convert: convertStringConvertibleToText + ) + } } From 697a62b4298c5b7cdd26d934ce06cbf90932e67e Mon Sep 17 00:00:00 2001 From: Honza Dvorsky Date: Thu, 24 Aug 2023 17:53:54 +0200 Subject: [PATCH 02/11] Add a StringEncoder and StringDecoder, to handle the remaining responsibilities of _StringConvertible --- .../StringCoder/StringDecoder.swift | 396 ++++++++++++++++++ .../StringCoder/StringEncoder.swift | 377 +++++++++++++++++ .../Test_StringCodingRoundtrip.swift | 126 ++++++ 3 files changed, 899 insertions(+) create mode 100644 Sources/OpenAPIRuntime/StringCoder/StringDecoder.swift create mode 100644 Sources/OpenAPIRuntime/StringCoder/StringEncoder.swift create mode 100644 Tests/OpenAPIRuntimeTests/StringCoder/Test_StringCodingRoundtrip.swift diff --git a/Sources/OpenAPIRuntime/StringCoder/StringDecoder.swift b/Sources/OpenAPIRuntime/StringCoder/StringDecoder.swift new file mode 100644 index 00000000..56a1efdb --- /dev/null +++ b/Sources/OpenAPIRuntime/StringCoder/StringDecoder.swift @@ -0,0 +1,396 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the SwiftOpenAPIGenerator open source project +// +// Copyright (c) 2023 Apple Inc. and the SwiftOpenAPIGenerator project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of SwiftOpenAPIGenerator project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +import Foundation + +/// A type that decodes a `Decodable` objects from a string +/// using `LosslessStringConvertible`. +struct StringDecoder: Sendable {} + +extension StringDecoder { + + /// Attempt to decode an object from a string. + /// + /// - Parameters: + /// - type: The type to decode. + /// - data: The encoded string. + /// - Returns: The decoded value. + func decode( + _ type: T.Type = T.self, + from data: String + ) throws -> T { + let decoder = LosslessStringConvertibleDecoder(encodedString: data) + let value = try T.init(from: decoder) + return value + } +} + +private struct LosslessStringConvertibleDecoder { + let encodedString: String +} + +extension LosslessStringConvertibleDecoder { + enum DecoderError: Swift.Error { + case failedToDecodeValue + case containersNotSupported + } +} + +extension LosslessStringConvertibleDecoder: Decoder { + + var codingPath: [any CodingKey] { + [] + } + + var userInfo: [CodingUserInfoKey: Any] { + [:] + } + + func container( + keyedBy type: Key.Type + ) throws -> KeyedDecodingContainer where Key: CodingKey { + KeyedDecodingContainer(KeyedContainer(decoder: self)) + } + + func unkeyedContainer() throws -> any UnkeyedDecodingContainer { + UnkeyedContainer(decoder: self) + } + + func singleValueContainer() throws -> any SingleValueDecodingContainer { + SingleValueContainer(decoder: self) + } +} + +extension LosslessStringConvertibleDecoder { + + struct KeyedContainer: KeyedDecodingContainerProtocol { + let decoder: LosslessStringConvertibleDecoder + + var codingPath: [any CodingKey] { + [] + } + + var allKeys: [Key] { + [] + } + + func contains(_ key: Key) -> Bool { + false + } + + func decodeNil(forKey key: Key) throws -> Bool { + throw LosslessStringConvertibleDecoder.DecoderError.containersNotSupported + } + + func decode(_ type: Bool.Type, forKey key: Key) throws -> Bool { + throw LosslessStringConvertibleDecoder.DecoderError.containersNotSupported + } + + func decode(_ type: String.Type, forKey key: Key) throws -> String { + throw LosslessStringConvertibleDecoder.DecoderError.containersNotSupported + } + + func decode(_ type: Double.Type, forKey key: Key) throws -> Double { + throw LosslessStringConvertibleDecoder.DecoderError.containersNotSupported + } + + func decode(_ type: Float.Type, forKey key: Key) throws -> Float { + throw LosslessStringConvertibleDecoder.DecoderError.containersNotSupported + } + + func decode(_ type: Int.Type, forKey key: Key) throws -> Int { + throw LosslessStringConvertibleDecoder.DecoderError.containersNotSupported + } + + func decode(_ type: Int8.Type, forKey key: Key) throws -> Int8 { + throw LosslessStringConvertibleDecoder.DecoderError.containersNotSupported + } + + func decode(_ type: Int16.Type, forKey key: Key) throws -> Int16 { + throw LosslessStringConvertibleDecoder.DecoderError.containersNotSupported + } + + func decode(_ type: Int32.Type, forKey key: Key) throws -> Int32 { + throw LosslessStringConvertibleDecoder.DecoderError.containersNotSupported + } + + func decode(_ type: Int64.Type, forKey key: Key) throws -> Int64 { + throw LosslessStringConvertibleDecoder.DecoderError.containersNotSupported + } + + func decode(_ type: UInt.Type, forKey key: Key) throws -> UInt { + throw LosslessStringConvertibleDecoder.DecoderError.containersNotSupported + } + + func decode(_ type: UInt8.Type, forKey key: Key) throws -> UInt8 { + throw LosslessStringConvertibleDecoder.DecoderError.containersNotSupported + } + + func decode(_ type: UInt16.Type, forKey key: Key) throws -> UInt16 { + throw LosslessStringConvertibleDecoder.DecoderError.containersNotSupported + } + + func decode(_ type: UInt32.Type, forKey key: Key) throws -> UInt32 { + throw LosslessStringConvertibleDecoder.DecoderError.containersNotSupported + } + + func decode(_ type: UInt64.Type, forKey key: Key) throws -> UInt64 { + throw LosslessStringConvertibleDecoder.DecoderError.containersNotSupported + } + + func decode(_ type: T.Type, forKey key: Key) throws -> T where T: Decodable { + throw LosslessStringConvertibleDecoder.DecoderError.containersNotSupported + } + + func nestedContainer( + keyedBy type: NestedKey.Type, + forKey key: Key + ) throws -> KeyedDecodingContainer where NestedKey: CodingKey { + throw LosslessStringConvertibleDecoder.DecoderError.containersNotSupported + } + + func nestedUnkeyedContainer(forKey key: Key) throws -> any UnkeyedDecodingContainer { + throw LosslessStringConvertibleDecoder.DecoderError.containersNotSupported + } + + func superDecoder() throws -> any Decoder { + decoder + } + + func superDecoder(forKey key: Key) throws -> any Decoder { + decoder + } + } + + struct UnkeyedContainer: UnkeyedDecodingContainer { + + let decoder: LosslessStringConvertibleDecoder + + var codingPath: [any CodingKey] { + [] + } + + var count: Int? { + nil + } + + var isAtEnd: Bool { + true + } + + var currentIndex: Int { + 0 + } + + mutating func decodeNil() throws -> Bool { + false + } + + mutating func decode(_ type: Bool.Type) throws -> Bool { + throw LosslessStringConvertibleDecoder.DecoderError.containersNotSupported + } + + mutating func decode(_ type: String.Type) throws -> String { + throw LosslessStringConvertibleDecoder.DecoderError.containersNotSupported + } + + mutating func decode(_ type: Double.Type) throws -> Double { + throw LosslessStringConvertibleDecoder.DecoderError.containersNotSupported + } + + mutating func decode(_ type: Float.Type) throws -> Float { + throw LosslessStringConvertibleDecoder.DecoderError.containersNotSupported + } + + mutating func decode(_ type: Int.Type) throws -> Int { + throw LosslessStringConvertibleDecoder.DecoderError.containersNotSupported + } + + mutating func decode(_ type: Int8.Type) throws -> Int8 { + throw LosslessStringConvertibleDecoder.DecoderError.containersNotSupported + } + + mutating func decode(_ type: Int16.Type) throws -> Int16 { + throw LosslessStringConvertibleDecoder.DecoderError.containersNotSupported + } + + mutating func decode(_ type: Int32.Type) throws -> Int32 { + throw LosslessStringConvertibleDecoder.DecoderError.containersNotSupported + } + + mutating func decode(_ type: Int64.Type) throws -> Int64 { + throw LosslessStringConvertibleDecoder.DecoderError.containersNotSupported + } + + mutating func decode(_ type: UInt.Type) throws -> UInt { + throw LosslessStringConvertibleDecoder.DecoderError.containersNotSupported + } + + mutating func decode(_ type: UInt8.Type) throws -> UInt8 { + throw LosslessStringConvertibleDecoder.DecoderError.containersNotSupported + } + + mutating func decode(_ type: UInt16.Type) throws -> UInt16 { + throw LosslessStringConvertibleDecoder.DecoderError.containersNotSupported + } + + mutating func decode(_ type: UInt32.Type) throws -> UInt32 { + throw LosslessStringConvertibleDecoder.DecoderError.containersNotSupported + } + + mutating func decode(_ type: UInt64.Type) throws -> UInt64 { + throw LosslessStringConvertibleDecoder.DecoderError.containersNotSupported + } + + mutating func decode(_ type: T.Type) throws -> T where T: Decodable { + throw LosslessStringConvertibleDecoder.DecoderError.containersNotSupported + } + + mutating func nestedContainer( + keyedBy type: NestedKey.Type + ) throws -> KeyedDecodingContainer where NestedKey: CodingKey { + throw LosslessStringConvertibleDecoder.DecoderError.containersNotSupported + } + + mutating func nestedUnkeyedContainer() throws -> any UnkeyedDecodingContainer { + throw LosslessStringConvertibleDecoder.DecoderError.containersNotSupported + } + + mutating func superDecoder() throws -> any Decoder { + decoder + } + + } + + struct SingleValueContainer: SingleValueDecodingContainer { + let decoder: LosslessStringConvertibleDecoder + + var codingPath: [any CodingKey] { + [] + } + + func decodeNil() -> Bool { + false + } + + private func _decodeLosslessStringConvertible( + _: T.Type = T.self + ) throws -> T { + guard let parsedValue = T(String(decoder.encodedString)) else { + throw DecodingError.typeMismatch( + T.self, + .init( + codingPath: codingPath, + debugDescription: "Failed to convert to the requested type." + ) + ) + } + return parsedValue + } + + func decode(_ type: Bool.Type) throws -> Bool { + try _decodeLosslessStringConvertible() + } + + func decode(_ type: String.Type) throws -> String { + try _decodeLosslessStringConvertible() + } + + func decode(_ type: Double.Type) throws -> Double { + try _decodeLosslessStringConvertible() + } + + func decode(_ type: Float.Type) throws -> Float { + try _decodeLosslessStringConvertible() + } + + func decode(_ type: Int.Type) throws -> Int { + try _decodeLosslessStringConvertible() + } + + func decode(_ type: Int8.Type) throws -> Int8 { + try _decodeLosslessStringConvertible() + } + + func decode(_ type: Int16.Type) throws -> Int16 { + try _decodeLosslessStringConvertible() + } + + func decode(_ type: Int32.Type) throws -> Int32 { + try _decodeLosslessStringConvertible() + } + + func decode(_ type: Int64.Type) throws -> Int64 { + try _decodeLosslessStringConvertible() + } + + func decode(_ type: UInt.Type) throws -> UInt { + try _decodeLosslessStringConvertible() + } + + func decode(_ type: UInt8.Type) throws -> UInt8 { + try _decodeLosslessStringConvertible() + } + + func decode(_ type: UInt16.Type) throws -> UInt16 { + try _decodeLosslessStringConvertible() + } + + func decode(_ type: UInt32.Type) throws -> UInt32 { + try _decodeLosslessStringConvertible() + } + + func decode(_ type: UInt64.Type) throws -> UInt64 { + try _decodeLosslessStringConvertible() + } + + func decode(_ type: T.Type) throws -> T where T: Decodable { + switch type { + case is Bool.Type: + return try decode(Bool.self) as! T + case is String.Type: + return try decode(String.self) as! T + case is Double.Type: + return try decode(Double.self) as! T + case is Float.Type: + return try decode(Float.self) as! T + case is Int.Type: + return try decode(Int.self) as! T + case is Int8.Type: + return try decode(Int8.self) as! T + case is Int16.Type: + return try decode(Int16.self) as! T + case is Int32.Type: + return try decode(Int32.self) as! T + case is Int64.Type: + return try decode(Int64.self) as! T + case is UInt.Type: + return try decode(UInt.self) as! T + case is UInt8.Type: + return try decode(UInt8.self) as! T + case is UInt16.Type: + return try decode(UInt16.self) as! T + case is UInt32.Type: + return try decode(UInt32.self) as! T + case is UInt64.Type: + return try decode(UInt64.self) as! T + default: + guard let convertileType = T.self as? any LosslessStringConvertible.Type else { + throw LosslessStringConvertibleDecoder.DecoderError.containersNotSupported + } + return try _decodeLosslessStringConvertible(convertileType) as! T + } + } + } +} diff --git a/Sources/OpenAPIRuntime/StringCoder/StringEncoder.swift b/Sources/OpenAPIRuntime/StringCoder/StringEncoder.swift new file mode 100644 index 00000000..5b3d60c8 --- /dev/null +++ b/Sources/OpenAPIRuntime/StringCoder/StringEncoder.swift @@ -0,0 +1,377 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the SwiftOpenAPIGenerator open source project +// +// Copyright (c) 2023 Apple Inc. and the SwiftOpenAPIGenerator project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of SwiftOpenAPIGenerator project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +import Foundation + +/// A type that encodes an `Encodable` objects to a string, if it conforms +/// to `CustomStringConvertible`. +struct StringEncoder: Sendable {} + +extension StringEncoder { + + /// Attempt to encode a value into a string using `CustomStringConvertible`. + /// + /// - Parameters: + /// - value: The value to encode. + /// - Returns: The string. + func encode(_ value: some Encodable) throws -> String { + let encoder = CustomStringConvertibleEncoder() + try value.encode(to: encoder) + return try encoder.nonNilEncodedString() + } +} + +private final class CustomStringConvertibleEncoder { + private(set) var encodedString: String? +} + +extension CustomStringConvertibleEncoder { + enum EncoderError: Swift.Error { + case valueNotSet + case nilNotSupported + case containersNotSupported + case cannotEncodeMultipleValues + } + + func setEncodedString(_ value: String) throws { + guard encodedString == nil else { + throw EncoderError.cannotEncodeMultipleValues + } + encodedString = value + } + + func nonNilEncodedString() throws -> String { + guard let encodedString else { + throw EncoderError.valueNotSet + } + return encodedString + } +} + +extension CustomStringConvertibleEncoder: Encoder { + + var codingPath: [any CodingKey] { + [] + } + + var userInfo: [CodingUserInfoKey: Any] { + [:] + } + + func container(keyedBy type: Key.Type) -> KeyedEncodingContainer where Key: CodingKey { + KeyedEncodingContainer(CustomStringConvertibleEncoder.KeyedContainer(encoder: self)) + } + + func unkeyedContainer() -> any UnkeyedEncodingContainer { + CustomStringConvertibleEncoder.UnkeyedContainer(encoder: self) + } + + func singleValueContainer() -> any SingleValueEncodingContainer { + SingleValueContainer(encoder: self) + } +} + +extension CustomStringConvertibleEncoder { + + struct SingleValueContainer: SingleValueEncodingContainer { + let encoder: CustomStringConvertibleEncoder + + var codingPath: [any CodingKey] { + [] + } + + mutating func encodeNil() throws { + throw CustomStringConvertibleEncoder.EncoderError.nilNotSupported + } + + mutating func _encodeCustomStringConvertible(_ value: some CustomStringConvertible) throws { + try encoder.setEncodedString(value.description) + } + + mutating func encode(_ value: Bool) throws { + try _encodeCustomStringConvertible(value) + } + + mutating func encode(_ value: String) throws { + try _encodeCustomStringConvertible(value) + } + + mutating func encode(_ value: Double) throws { + try _encodeCustomStringConvertible(value) + } + + mutating func encode(_ value: Float) throws { + try _encodeCustomStringConvertible(value) + } + + mutating func encode(_ value: Int) throws { + try _encodeCustomStringConvertible(value) + } + + mutating func encode(_ value: Int8) throws { + try _encodeCustomStringConvertible(value) + } + + mutating func encode(_ value: Int16) throws { + try _encodeCustomStringConvertible(value) + } + + mutating func encode(_ value: Int32) throws { + try _encodeCustomStringConvertible(value) + } + + mutating func encode(_ value: Int64) throws { + try _encodeCustomStringConvertible(value) + } + + mutating func encode(_ value: UInt) throws { + try _encodeCustomStringConvertible(value) + } + + mutating func encode(_ value: UInt8) throws { + try _encodeCustomStringConvertible(value) + } + + mutating func encode(_ value: UInt16) throws { + try _encodeCustomStringConvertible(value) + } + + mutating func encode(_ value: UInt32) throws { + try _encodeCustomStringConvertible(value) + } + + mutating func encode(_ value: UInt64) throws { + try _encodeCustomStringConvertible(value) + } + + mutating func encode(_ value: T) throws where T: Encodable { + switch value { + case let value as UInt8: + try encode(value) + case let value as Int8: + try encode(value) + case let value as UInt16: + try encode(value) + case let value as Int16: + try encode(value) + case let value as UInt32: + try encode(value) + case let value as Int32: + try encode(value) + case let value as UInt64: + try encode(value) + case let value as Int64: + try encode(value) + case let value as Int: + try encode(value) + case let value as UInt: + try encode(value) + case let value as Float: + try encode(value) + case let value as Double: + try encode(value) + case let value as String: + try encode(value) + case let value as Bool: + try encode(value) + default: + guard let customStringConvertible = value as? any CustomStringConvertible else { + throw CustomStringConvertibleEncoder.EncoderError.containersNotSupported + } + try _encodeCustomStringConvertible(customStringConvertible) + } + } + } + + struct KeyedContainer: KeyedEncodingContainerProtocol { + let encoder: CustomStringConvertibleEncoder + + var codingPath: [any CodingKey] { + [] + } + + mutating func superEncoder() -> any Encoder { + encoder + } + + mutating func encodeNil(forKey key: Key) throws { + throw CustomStringConvertibleEncoder.EncoderError.containersNotSupported + } + + mutating func encode(_ value: Bool, forKey key: Key) throws { + throw CustomStringConvertibleEncoder.EncoderError.containersNotSupported + } + + mutating func encode(_ value: String, forKey key: Key) throws { + throw CustomStringConvertibleEncoder.EncoderError.containersNotSupported + } + + mutating func encode(_ value: Double, forKey key: Key) throws { + throw CustomStringConvertibleEncoder.EncoderError.containersNotSupported + } + + mutating func encode(_ value: Float, forKey key: Key) throws { + throw CustomStringConvertibleEncoder.EncoderError.containersNotSupported + } + + mutating func encode(_ value: Int, forKey key: Key) throws { + throw CustomStringConvertibleEncoder.EncoderError.containersNotSupported + } + + mutating func encode(_ value: Int8, forKey key: Key) throws { + throw CustomStringConvertibleEncoder.EncoderError.containersNotSupported + } + + mutating func encode(_ value: Int16, forKey key: Key) throws { + throw CustomStringConvertibleEncoder.EncoderError.containersNotSupported + } + + mutating func encode(_ value: Int32, forKey key: Key) throws { + throw CustomStringConvertibleEncoder.EncoderError.containersNotSupported + } + + mutating func encode(_ value: Int64, forKey key: Key) throws { + throw CustomStringConvertibleEncoder.EncoderError.containersNotSupported + } + + mutating func encode(_ value: UInt, forKey key: Key) throws { + throw CustomStringConvertibleEncoder.EncoderError.containersNotSupported + } + + mutating func encode(_ value: UInt8, forKey key: Key) throws { + throw CustomStringConvertibleEncoder.EncoderError.containersNotSupported + } + + mutating func encode(_ value: UInt16, forKey key: Key) throws { + throw CustomStringConvertibleEncoder.EncoderError.containersNotSupported + } + + mutating func encode(_ value: UInt32, forKey key: Key) throws { + throw CustomStringConvertibleEncoder.EncoderError.containersNotSupported + } + + mutating func encode(_ value: UInt64, forKey key: Key) throws { + throw CustomStringConvertibleEncoder.EncoderError.containersNotSupported + } + + mutating func encode(_ value: T, forKey key: Key) throws where T: Encodable { + throw CustomStringConvertibleEncoder.EncoderError.containersNotSupported + } + + mutating func nestedContainer( + keyedBy keyType: NestedKey.Type, + forKey key: Key + ) -> KeyedEncodingContainer where NestedKey: CodingKey { + encoder.container(keyedBy: NestedKey.self) + } + + mutating func nestedUnkeyedContainer(forKey key: Key) -> any UnkeyedEncodingContainer { + encoder.unkeyedContainer() + } + + mutating func superEncoder(forKey key: Key) -> any Encoder { + encoder + } + } + + struct UnkeyedContainer: UnkeyedEncodingContainer { + + let encoder: CustomStringConvertibleEncoder + + var codingPath: [any CodingKey] { + [] + } + + var count: Int { + 0 + } + + mutating func encodeNil() throws { + throw CustomStringConvertibleEncoder.EncoderError.containersNotSupported + } + + mutating func encode(_ value: Bool) throws { + throw CustomStringConvertibleEncoder.EncoderError.containersNotSupported + } + + mutating func encode(_ value: String) throws { + throw CustomStringConvertibleEncoder.EncoderError.containersNotSupported + } + + mutating func encode(_ value: Double) throws { + throw CustomStringConvertibleEncoder.EncoderError.containersNotSupported + } + + mutating func encode(_ value: Float) throws { + throw CustomStringConvertibleEncoder.EncoderError.containersNotSupported + } + + mutating func encode(_ value: Int) throws { + throw CustomStringConvertibleEncoder.EncoderError.containersNotSupported + } + + mutating func encode(_ value: Int8) throws { + throw CustomStringConvertibleEncoder.EncoderError.containersNotSupported + } + + mutating func encode(_ value: Int16) throws { + throw CustomStringConvertibleEncoder.EncoderError.containersNotSupported + } + + mutating func encode(_ value: Int32) throws { + throw CustomStringConvertibleEncoder.EncoderError.containersNotSupported + } + + mutating func encode(_ value: Int64) throws { + throw CustomStringConvertibleEncoder.EncoderError.containersNotSupported + } + + mutating func encode(_ value: UInt) throws { + throw CustomStringConvertibleEncoder.EncoderError.containersNotSupported + } + + mutating func encode(_ value: UInt8) throws { + throw CustomStringConvertibleEncoder.EncoderError.containersNotSupported + } + + mutating func encode(_ value: UInt16) throws { + throw CustomStringConvertibleEncoder.EncoderError.containersNotSupported + } + + mutating func encode(_ value: UInt32) throws { + throw CustomStringConvertibleEncoder.EncoderError.containersNotSupported + } + + mutating func encode(_ value: UInt64) throws { + throw CustomStringConvertibleEncoder.EncoderError.containersNotSupported + } + + mutating func encode(_ value: T) throws where T: Encodable { + throw CustomStringConvertibleEncoder.EncoderError.containersNotSupported + } + + mutating func nestedContainer(keyedBy keyType: NestedKey.Type) -> KeyedEncodingContainer + where NestedKey: CodingKey { + encoder.container(keyedBy: NestedKey.self) + } + + mutating func nestedUnkeyedContainer() -> any UnkeyedEncodingContainer { + encoder.unkeyedContainer() + } + + mutating func superEncoder() -> any Encoder { + encoder + } + } +} diff --git a/Tests/OpenAPIRuntimeTests/StringCoder/Test_StringCodingRoundtrip.swift b/Tests/OpenAPIRuntimeTests/StringCoder/Test_StringCodingRoundtrip.swift new file mode 100644 index 00000000..daf3f089 --- /dev/null +++ b/Tests/OpenAPIRuntimeTests/StringCoder/Test_StringCodingRoundtrip.swift @@ -0,0 +1,126 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the SwiftOpenAPIGenerator open source project +// +// Copyright (c) 2023 Apple Inc. and the SwiftOpenAPIGenerator project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of SwiftOpenAPIGenerator project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// +import XCTest +@testable import OpenAPIRuntime + +final class Test_StringCodingRoundtrip: Test_Runtime { + + func testRoundtrip() throws { + + enum SimpleEnum: String, Codable, Equatable { + case red + case green + case blue + } + + struct CustomValue: LosslessStringConvertible, Codable, Equatable { + var innerString: String + + init(innerString: String) { + self.innerString = innerString + } + + init?(_ description: String) { + self.init(innerString: description) + } + + var description: String { + innerString + } + + func encode(to encoder: any Encoder) throws { + var container = encoder.singleValueContainer() + try container.encode(innerString) + } + + enum CodingKeys: CodingKey { + case innerString + } + + init(from decoder: any Decoder) throws { + let container = try decoder.singleValueContainer() + self.innerString = try container.decode(String.self) + } + } + + // An empty string. + try _test( + "", + "" + ) + + // An string with a space. + try _test( + "Hello World!", + "Hello World!" + ) + + // An enum. + try _test( + SimpleEnum.red, + "red" + ) + + // A custom value. + try _test( + CustomValue(innerString: "hello"), + "hello" + ) + + // An integer. + try _test( + 1234, + "1234" + ) + + // A float. + try _test( + 12.34, + "12.34" + ) + + // A bool. + try _test( + true, + "true" + ) + } + + func _test( + _ value: T, + _ expectedString: String, + file: StaticString = #file, + line: UInt = #line + ) throws { + let encoder = StringEncoder() + let encodedString = try encoder.encode(value) + XCTAssertEqual( + encodedString, + expectedString, + file: file, + line: line + ) + let decoder = StringDecoder() + let decodedValue = try decoder.decode( + T.self, + from: encodedString + ) + XCTAssertEqual( + decodedValue, + value, + file: file, + line: line + ) + } +} From 1d84e8131684de33f5f4d8d7c5ed6ca9e0eb0fb1 Mon Sep 17 00:00:00 2001 From: Honza Dvorsky Date: Thu, 24 Aug 2023 18:20:02 +0200 Subject: [PATCH 03/11] Prepare new URI-based converter variants for headers --- .../Conversion/Converter+Common.swift | 179 ++++-------------- .../Conversion/CurrencyExtensions.swift | 59 ++++++ .../Deprecated/Deprecated.swift | 151 +++++++++++++++ .../Common/URICoderConfiguration.swift | 6 - 4 files changed, 247 insertions(+), 148 deletions(-) diff --git a/Sources/OpenAPIRuntime/Conversion/Converter+Common.swift b/Sources/OpenAPIRuntime/Conversion/Converter+Common.swift index fc4f931d..94c8525b 100644 --- a/Sources/OpenAPIRuntime/Conversion/Converter+Common.swift +++ b/Sources/OpenAPIRuntime/Conversion/Converter+Common.swift @@ -70,61 +70,28 @@ extension Converter { // MARK: - Converter helper methods - // // | common | set | header field | uri | codable | both | setHeaderFieldAsText | - // @available(*, deprecated) - // public func setHeaderFieldAsURI( - // in headerFields: inout [HeaderField], - // name: String, - // value: T? - // ) throws { - // try setHeaderField( - // in: &headerFields, - // name: name, - // value: value, - // convert: convertStringConvertibleToText - // ) - // } - - // // | common | set | header field | uri | array of string-convertibles | both | setHeaderFieldAsText | - // @available(*, deprecated) - // public func setHeaderFieldAsURI( - // in headerFields: inout [HeaderField], - // name: String, - // value values: [T]? - // ) throws { - // try setHeaderFields( - // in: &headerFields, - // name: name, - // values: values, - // convert: convertStringConvertibleToText - // ) - // } - - // | common | set | header field | text | date | both | setHeaderFieldAsText | - public func setHeaderFieldAsText( + // | common | set | header field | uri | codable | both | setHeaderFieldAsURI | + public func setHeaderFieldAsURI( in headerFields: inout [HeaderField], name: String, - value: Date? + value: T? ) throws { + guard let value else { + return + } try setHeaderField( in: &headerFields, name: name, value: value, - convert: convertDateToText - ) - } - - // | common | set | header field | text | array of dates | both | setHeaderFieldAsText | - public func setHeaderFieldAsText( - in headerFields: inout [HeaderField], - name: String, - value values: [Date]? - ) throws { - try setHeaderFields( - in: &headerFields, - name: name, - values: values, - convert: convertDateToText + convert: { value in + try convertToURI( + style: .simple, + explode: false, + inBody: false, + key: "", + value: value + ) + } ) } @@ -142,9 +109,8 @@ extension Converter { ) } - // | common | get | header field | text | string-convertible | optional | getOptionalHeaderFieldAsText | - @available(*, deprecated) - public func getOptionalHeaderFieldAsText( + // | common | get | header field | text | codable | optional | getOptionalHeaderFieldAsURI | + public func getOptionalHeaderFieldAsURI( in headerFields: [HeaderField], name: String, as type: T.Type @@ -153,13 +119,20 @@ extension Converter { in: headerFields, name: name, as: type, - convert: convertTextToStringConvertible + convert: { encodedValue in + try convertFromURI( + style: .simple, + explode: false, + inBody: false, + key: "", + encodedValue: encodedValue + ) + } ) } - // | common | get | header field | text | string-convertible | required | getRequiredHeaderFieldAsText | - @available(*, deprecated) - public func getRequiredHeaderFieldAsText( + // | common | get | header field | text | codable | required | getRequiredHeaderFieldAsURI | + public func getRequiredHeaderFieldAsURI( in headerFields: [HeaderField], name: String, as type: T.Type @@ -168,93 +141,15 @@ extension Converter { in: headerFields, name: name, as: type, - convert: convertTextToStringConvertible - ) - } - - // | common | get | header field | text | array of string-convertibles | optional | getOptionalHeaderFieldAsText | - @available(*, deprecated) - public func getOptionalHeaderFieldAsText( - in headerFields: [HeaderField], - name: String, - as type: [T].Type - ) throws -> [T]? { - try getOptionalHeaderFields( - in: headerFields, - name: name, - as: type, - convert: convertTextToStringConvertible - ) - } - - // | common | get | header field | text | array of string-convertibles | required | getRequiredHeaderFieldAsText | - @available(*, deprecated) - public func getRequiredHeaderFieldAsText( - in headerFields: [HeaderField], - name: String, - as type: [T].Type - ) throws -> [T] { - try getRequiredHeaderFields( - in: headerFields, - name: name, - as: type, - convert: convertTextToStringConvertible - ) - } - - // | common | get | header field | text | date | optional | getOptionalHeaderFieldAsText | - public func getOptionalHeaderFieldAsText( - in headerFields: [HeaderField], - name: String, - as type: Date.Type - ) throws -> Date? { - try getOptionalHeaderField( - in: headerFields, - name: name, - as: type, - convert: convertHeaderFieldTextToDate - ) - } - - // | common | get | header field | text | date | required | getRequiredHeaderFieldAsText | - public func getRequiredHeaderFieldAsText( - in headerFields: [HeaderField], - name: String, - as type: Date.Type - ) throws -> Date { - try getRequiredHeaderField( - in: headerFields, - name: name, - as: type, - convert: convertHeaderFieldTextToDate - ) - } - - // | common | get | header field | text | array of dates | optional | getOptionalHeaderFieldAsText | - public func getOptionalHeaderFieldAsText( - in headerFields: [HeaderField], - name: String, - as type: [Date].Type - ) throws -> [Date]? { - try getOptionalHeaderFields( - in: headerFields, - name: name, - as: type, - convert: convertHeaderFieldTextToDate - ) - } - - // | common | get | header field | text | array of dates | required | getRequiredHeaderFieldAsText | - public func getRequiredHeaderFieldAsText( - in headerFields: [HeaderField], - name: String, - as type: [Date].Type - ) throws -> [Date] { - try getRequiredHeaderFields( - in: headerFields, - name: name, - as: type, - convert: convertHeaderFieldTextToDate + convert: { encodedValue in + try convertFromURI( + style: .simple, + explode: false, + inBody: false, + key: "", + encodedValue: encodedValue + ) + } ) } diff --git a/Sources/OpenAPIRuntime/Conversion/CurrencyExtensions.swift b/Sources/OpenAPIRuntime/Conversion/CurrencyExtensions.swift index 2426f4cd..106b866f 100644 --- a/Sources/OpenAPIRuntime/Conversion/CurrencyExtensions.swift +++ b/Sources/OpenAPIRuntime/Conversion/CurrencyExtensions.swift @@ -138,6 +138,43 @@ extension Converter { try Data(convertStringConvertibleToText(value).utf8) } + func uriCoderConfiguration( + style: ParameterStyle, + explode: Bool, + inBody: Bool + ) -> URICoderConfiguration { + let configStyle: URICoderConfiguration.Style + switch style { + case .form: + configStyle = .form + case .simple: + configStyle = .simple + } + return .init( + style: configStyle, + explode: explode, + spaceEscapingCharacter: inBody ? .plus : .percentEncoded + ) + } + + func convertToURI( + style: ParameterStyle, + explode: Bool, + inBody: Bool, + key: String, + value: T + ) throws -> String { + let encoder = URIEncoder( + configuration: uriCoderConfiguration( + style: style, + explode: explode, + inBody: inBody + ) + ) + let encodedString = try encoder.encode(value, forKey: key) + return encodedString + } + func convertDateToText(_ value: Date) throws -> String { try configuration.dateTranscoder.encode(value) } @@ -198,6 +235,28 @@ extension Converter { return value } + func convertFromURI( + style: ParameterStyle, + explode: Bool, + inBody: Bool, + key: String, + encodedValue: String + ) throws -> T { + let decoder = URIDecoder( + configuration: uriCoderConfiguration( + style: style, + explode: explode, + inBody: inBody + ) + ) + let value = try decoder.decode( + T.self, + forKey: key, + from: encodedValue + ) + return value + } + @available(*, deprecated) func convertTextDataToStringConvertible( _ data: Data diff --git a/Sources/OpenAPIRuntime/Deprecated/Deprecated.swift b/Sources/OpenAPIRuntime/Deprecated/Deprecated.swift index 4fa22ec2..f1fc0517 100644 --- a/Sources/OpenAPIRuntime/Deprecated/Deprecated.swift +++ b/Sources/OpenAPIRuntime/Deprecated/Deprecated.swift @@ -1211,4 +1211,155 @@ extension Converter { convert: convertStringConvertibleToText ) } + + // | common | set | header field | text | date | both | setHeaderFieldAsText | + @available(*, deprecated) + public func setHeaderFieldAsText( + in headerFields: inout [HeaderField], + name: String, + value: Date? + ) throws { + try setHeaderField( + in: &headerFields, + name: name, + value: value, + convert: convertDateToText + ) + } + + // | common | set | header field | text | array of dates | both | setHeaderFieldAsText | + @available(*, deprecated) + public func setHeaderFieldAsText( + in headerFields: inout [HeaderField], + name: String, + value values: [Date]? + ) throws { + try setHeaderFields( + in: &headerFields, + name: name, + values: values, + convert: convertDateToText + ) + } + + // | common | get | header field | text | string-convertible | optional | getOptionalHeaderFieldAsText | + @available(*, deprecated) + public func getOptionalHeaderFieldAsText( + in headerFields: [HeaderField], + name: String, + as type: T.Type + ) throws -> T? { + try getOptionalHeaderField( + in: headerFields, + name: name, + as: type, + convert: convertTextToStringConvertible + ) + } + + // | common | get | header field | text | string-convertible | required | getRequiredHeaderFieldAsText | + @available(*, deprecated) + public func getRequiredHeaderFieldAsText( + in headerFields: [HeaderField], + name: String, + as type: T.Type + ) throws -> T { + try getRequiredHeaderField( + in: headerFields, + name: name, + as: type, + convert: convertTextToStringConvertible + ) + } + + // | common | get | header field | text | array of string-convertibles | optional | getOptionalHeaderFieldAsText | + @available(*, deprecated) + public func getOptionalHeaderFieldAsText( + in headerFields: [HeaderField], + name: String, + as type: [T].Type + ) throws -> [T]? { + try getOptionalHeaderFields( + in: headerFields, + name: name, + as: type, + convert: convertTextToStringConvertible + ) + } + + // | common | get | header field | text | array of string-convertibles | required | getRequiredHeaderFieldAsText | + @available(*, deprecated) + public func getRequiredHeaderFieldAsText( + in headerFields: [HeaderField], + name: String, + as type: [T].Type + ) throws -> [T] { + try getRequiredHeaderFields( + in: headerFields, + name: name, + as: type, + convert: convertTextToStringConvertible + ) + } + + // | common | get | header field | text | date | optional | getOptionalHeaderFieldAsText | + @available(*, deprecated) + public func getOptionalHeaderFieldAsText( + in headerFields: [HeaderField], + name: String, + as type: Date.Type + ) throws -> Date? { + try getOptionalHeaderField( + in: headerFields, + name: name, + as: type, + convert: convertHeaderFieldTextToDate + ) + } + + // | common | get | header field | text | date | required | getRequiredHeaderFieldAsText | + @available(*, deprecated) + public func getRequiredHeaderFieldAsText( + in headerFields: [HeaderField], + name: String, + as type: Date.Type + ) throws -> Date { + try getRequiredHeaderField( + in: headerFields, + name: name, + as: type, + convert: convertHeaderFieldTextToDate + ) + } + + // | common | get | header field | text | array of dates | optional | getOptionalHeaderFieldAsText | + @available(*, deprecated) + public func getOptionalHeaderFieldAsText( + in headerFields: [HeaderField], + name: String, + as type: [Date].Type + ) throws -> [Date]? { + try getOptionalHeaderFields( + in: headerFields, + name: name, + as: type, + convert: convertHeaderFieldTextToDate + ) + } + + // | common | get | header field | text | array of dates | required | getRequiredHeaderFieldAsText | + @available(*, deprecated) + public func getRequiredHeaderFieldAsText( + in headerFields: [HeaderField], + name: String, + as type: [Date].Type + ) throws -> [Date] { + try getRequiredHeaderFields( + in: headerFields, + name: name, + as: type, + convert: convertHeaderFieldTextToDate + ) + } + } diff --git a/Sources/OpenAPIRuntime/URICoder/Common/URICoderConfiguration.swift b/Sources/OpenAPIRuntime/URICoder/Common/URICoderConfiguration.swift index f3eae2f7..dc1c26a9 100644 --- a/Sources/OpenAPIRuntime/URICoder/Common/URICoderConfiguration.swift +++ b/Sources/OpenAPIRuntime/URICoder/Common/URICoderConfiguration.swift @@ -31,12 +31,6 @@ struct URICoderConfiguration { var explode: Bool var spaceEscapingCharacter: SpaceEscapingCharacter - private init(style: Style, explode: Bool, spaceEscapingCharacter: SpaceEscapingCharacter) { - self.style = style - self.explode = explode - self.spaceEscapingCharacter = spaceEscapingCharacter - } - static let formExplode: Self = .init( style: .form, explode: true, From a153358c1203c47a7c1ac86efe6d49164b3b2e7b Mon Sep 17 00:00:00 2001 From: Honza Dvorsky Date: Fri, 25 Aug 2023 09:59:45 +0200 Subject: [PATCH 04/11] WIP --- .../Conversion/Converter+Client.swift | 146 ++----- .../Conversion/Converter+Common.swift | 4 +- .../Conversion/Converter+Server.swift | 4 +- .../Conversion/CurrencyExtensions.swift | 257 +++--------- .../Deprecated/Deprecated.swift | 374 ++++++++++++++++++ 5 files changed, 456 insertions(+), 329 deletions(-) diff --git a/Sources/OpenAPIRuntime/Conversion/Converter+Client.swift b/Sources/OpenAPIRuntime/Conversion/Converter+Client.swift index 66c064fd..a361cfab 100644 --- a/Sources/OpenAPIRuntime/Conversion/Converter+Client.swift +++ b/Sources/OpenAPIRuntime/Conversion/Converter+Client.swift @@ -31,18 +31,19 @@ extension Converter { ) } - // | client | set | request path | text | string-convertible | required | renderedRequestPath | - @available(*, deprecated) - public func renderedRequestPath( + // | client | set | request path | uri | codable | required | renderedPath | + public func renderedPath( template: String, - parameters: [any _StringConvertible] + parameters: [any Encodable] ) throws -> String { var renderedString = template + let encoder = URIEncoder(configuration: .simpleUnexplode) for parameter in parameters { + let value = try encoder.encode(parameter, forKey: "") if let range = renderedString.range(of: "{}") { renderedString = renderedString.replacingOccurrences( of: "{}", - with: parameter.description, + with: value, range: range ) } @@ -50,9 +51,8 @@ extension Converter { return renderedString } - // | client | set | request query | text | string-convertible | both | setQueryItemAsText | - @available(*, deprecated) - public func setQueryItemAsText( + // | client | set | request query | uri | codable | both | setQueryItemAsURI | + public func setQueryItemAsURI( in request: inout Request, style: ParameterStyle?, explode: Bool?, @@ -65,68 +65,20 @@ extension Converter { explode: explode, name: name, value: value, - convert: convertStringConvertibleToText - ) - } - - // | client | set | request query | text | array of string-convertibles | both | setQueryItemAsText | - @available(*, deprecated) - public func setQueryItemAsText( - in request: inout Request, - style: ParameterStyle?, - explode: Bool?, - name: String, - value: [T]? - ) throws { - try setQueryItems( - in: &request, - style: style, - explode: explode, - name: name, - values: value, - convert: convertStringConvertibleToText - ) - } - - // | client | set | request query | text | date | both | setQueryItemAsText | - public func setQueryItemAsText( - in request: inout Request, - style: ParameterStyle?, - explode: Bool?, - name: String, - value: Date? - ) throws { - try setQueryItem( - in: &request, - style: style, - explode: explode, - name: name, - value: value, - convert: convertDateToText - ) - } - - // | client | set | request query | text | array of dates | both | setQueryItemAsText | - public func setQueryItemAsText( - in request: inout Request, - style: ParameterStyle?, - explode: Bool?, - name: String, - value: [Date]? - ) throws { - try setQueryItems( - in: &request, - style: style, - explode: explode, - name: name, - values: value, - convert: convertDateToText + convert: { value in + try convertToURI( + style: .simple, + explode: false, + inBody: false, + key: "", + value: value + ) + } ) } - // | client | set | request body | text | string-convertible | optional | setOptionalRequestBodyAsText | - @available(*, deprecated) - public func setOptionalRequestBodyAsText( + // | client | set | request body | string | codable | optional | setOptionalRequestBodyAsString | + public func setOptionalRequestBodyAsString( _ value: T?, headerFields: inout [HeaderField], contentType: String @@ -135,13 +87,12 @@ extension Converter { value, headerFields: &headerFields, contentType: contentType, - convert: convertStringConvertibleToTextData + convert: convertToStringData ) } - // | client | set | request body | text | string-convertible | required | setRequiredRequestBodyAsText | - @available(*, deprecated) - public func setRequiredRequestBodyAsText( + // | client | set | request body | string | codable | required | setRequiredRequestBodyAsString | + public func setRequiredRequestBodyAsString( _ value: T, headerFields: inout [HeaderField], contentType: String @@ -150,35 +101,7 @@ extension Converter { value, headerFields: &headerFields, contentType: contentType, - convert: convertStringConvertibleToTextData - ) - } - - // | client | set | request body | text | date | optional | setOptionalRequestBodyAsText | - public func setOptionalRequestBodyAsText( - _ value: Date?, - headerFields: inout [HeaderField], - contentType: String - ) throws -> Data? { - try setOptionalRequestBody( - value, - headerFields: &headerFields, - contentType: contentType, - convert: convertDateToTextData - ) - } - - // | client | set | request body | text | date | required | setRequiredRequestBodyAsText | - public func setRequiredRequestBodyAsText( - _ value: Date, - headerFields: inout [HeaderField], - contentType: String - ) throws -> Data { - try setRequiredRequestBody( - value, - headerFields: &headerFields, - contentType: contentType, - convert: convertDateToTextData + convert: convertToStringData ) } @@ -238,9 +161,8 @@ extension Converter { ) } - // | client | get | response body | text | string-convertible | required | getResponseBodyAsText | - @available(*, deprecated) - public func getResponseBodyAsText( + // | client | get | response body | string | codable | required | getResponseBodyAsString | + public func getResponseBodyAsString( _ type: T.Type, from data: Data, transforming transform: (T) -> C @@ -249,21 +171,7 @@ extension Converter { type, from: data, transforming: transform, - convert: convertTextDataToStringConvertible - ) - } - - // | client | get | response body | text | date | required | getResponseBodyAsText | - public func getResponseBodyAsText( - _ type: Date.Type, - from data: Data, - transforming transform: (Date) -> C - ) throws -> C { - try getResponseBody( - type, - from: data, - transforming: transform, - convert: convertTextDataToDate + convert: convertFromStringData ) } @@ -277,7 +185,7 @@ extension Converter { type, from: data, transforming: transform, - convert: convertJSONToCodable + convert: convertJSONToBodyCodable ) } diff --git a/Sources/OpenAPIRuntime/Conversion/Converter+Common.swift b/Sources/OpenAPIRuntime/Conversion/Converter+Common.swift index 94c8525b..0312fe4b 100644 --- a/Sources/OpenAPIRuntime/Conversion/Converter+Common.swift +++ b/Sources/OpenAPIRuntime/Conversion/Converter+Common.swift @@ -163,7 +163,7 @@ extension Converter { in: headerFields, name: name, as: type, - convert: convertHeaderFieldJSONToCodable + convert: convertJSONToHeaderFieldCodable ) } @@ -177,7 +177,7 @@ extension Converter { in: headerFields, name: name, as: type, - convert: convertHeaderFieldJSONToCodable + convert: convertJSONToHeaderFieldCodable ) } } diff --git a/Sources/OpenAPIRuntime/Conversion/Converter+Server.swift b/Sources/OpenAPIRuntime/Conversion/Converter+Server.swift index 23029a84..bea778df 100644 --- a/Sources/OpenAPIRuntime/Conversion/Converter+Server.swift +++ b/Sources/OpenAPIRuntime/Conversion/Converter+Server.swift @@ -313,7 +313,7 @@ public extension Converter { type, from: data, transforming: transform, - convert: convertJSONToCodable + convert: convertJSONToBodyCodable ) } @@ -327,7 +327,7 @@ public extension Converter { type, from: data, transforming: transform, - convert: convertJSONToCodable + convert: convertJSONToBodyCodable ) } diff --git a/Sources/OpenAPIRuntime/Conversion/CurrencyExtensions.swift b/Sources/OpenAPIRuntime/Conversion/CurrencyExtensions.swift index 106b866f..397d5f89 100644 --- a/Sources/OpenAPIRuntime/Conversion/CurrencyExtensions.swift +++ b/Sources/OpenAPIRuntime/Conversion/CurrencyExtensions.swift @@ -122,21 +122,7 @@ extension ParameterStyle { extension Converter { - // MARK: Common functions for Converter's SPI helper methods - - @available(*, deprecated) - func convertStringConvertibleToText( - _ value: T - ) throws -> String { - value.description - } - - @available(*, deprecated) - func convertStringConvertibleToTextData( - _ value: T - ) throws -> Data { - try Data(convertStringConvertibleToText(value).utf8) - } + // MARK: Converter helpers func uriCoderConfiguration( style: ParameterStyle, @@ -153,7 +139,8 @@ extension Converter { return .init( style: configStyle, explode: explode, - spaceEscapingCharacter: inBody ? .plus : .percentEncoded + spaceEscapingCharacter: inBody ? .plus : .percentEncoded, + dateTranscoder: configuration.dateTranscoder ) } @@ -174,25 +161,30 @@ extension Converter { let encodedString = try encoder.encode(value, forKey: key) return encodedString } - - func convertDateToText(_ value: Date) throws -> String { - try configuration.dateTranscoder.encode(value) - } - - func convertDateToTextData(_ value: Date) throws -> Data { - try Data(convertDateToText(value).utf8) - } - - func convertTextToDate(_ stringValue: String) throws -> Date { - try configuration.dateTranscoder.decode(stringValue) - } - - func convertTextDataToDate(_ data: Data) throws -> Date { - let stringValue = String(decoding: data, as: UTF8.self) - return try convertTextToDate(stringValue) + + func convertFromURI( + style: ParameterStyle, + explode: Bool, + inBody: Bool, + key: String, + encodedValue: String + ) throws -> T { + let decoder = URIDecoder( + configuration: uriCoderConfiguration( + style: style, + explode: explode, + inBody: inBody + ) + ) + let value = try decoder.decode( + T.self, + forKey: key, + from: encodedValue + ) + return value } - func convertJSONToCodable( + func convertJSONToBodyCodable( _ data: Data ) throws -> T { try decoder.decode(T.self, from: data) @@ -204,10 +196,6 @@ extension Converter { try encoder.encode(value) } - func convertHeaderFieldTextToDate(_ stringValue: String) throws -> Date { - try convertTextToDate(stringValue) - } - func convertHeaderFieldCodableToJSON( _ value: T ) throws -> String { @@ -216,55 +204,47 @@ extension Converter { return stringValue } - func convertHeaderFieldJSONToCodable( + func convertJSONToHeaderFieldCodable( _ stringValue: String ) throws -> T { let data = Data(stringValue.utf8) return try decoder.decode(T.self, from: data) } - - @available(*, deprecated) - func convertTextToStringConvertible( - _ stringValue: String - ) throws -> T { - guard let value = T.init(stringValue) else { - throw RuntimeError.failedToDecodeStringConvertibleValue( - type: String(describing: T.self) - ) - } - return value - } - - func convertFromURI( - style: ParameterStyle, - explode: Bool, - inBody: Bool, - key: String, - encodedValue: String + + func convertFromStringData( + _ data: Data ) throws -> T { - let decoder = URIDecoder( - configuration: uriCoderConfiguration( - style: style, - explode: explode, - inBody: inBody - ) - ) + let encodedString = String(decoding: data, as: UTF8.self) + let decoder = StringDecoder() let value = try decoder.decode( T.self, - forKey: key, - from: encodedValue + from: encodedString ) return value } - @available(*, deprecated) - func convertTextDataToStringConvertible( + func convertToStringData( + _ value: T + ) throws -> Data { + let encoder = StringEncoder() + let encodedString = try encoder.encode(value) + return Data(encodedString.utf8) + } + + func convertBinaryToData( + _ binary: Data + ) throws -> Data { + binary + } + + func convertDataToBinary( _ data: Data - ) throws -> T { - let stringValue = String(decoding: data, as: UTF8.self) - return try convertTextToStringConvertible(stringValue) + ) throws -> Data { + data } + // MARK: - Helpers for specific types of parameters + func setHeaderField( in headerFields: inout [HeaderField], name: String, @@ -280,23 +260,6 @@ extension Converter { ) } - func setHeaderFields( - in headerFields: inout [HeaderField], - name: String, - values: [T]?, - convert: (T) throws -> String - ) throws { - guard let values else { - return - } - for value in values { - headerFields.add( - name: name, - value: try convert(value) - ) - } - } - func getOptionalHeaderField( in headerFields: [HeaderField], name: String, @@ -321,32 +284,6 @@ extension Converter { return try convert(stringValue) } - func getOptionalHeaderFields( - in headerFields: [HeaderField], - name: String, - as type: [T].Type, - convert: (String) throws -> T - ) throws -> [T]? { - let values = headerFields.values(name: name) - if values.isEmpty { - return nil - } - return try values.map { value in try convert(value) } - } - - func getRequiredHeaderFields( - in headerFields: [HeaderField], - name: String, - as type: [T].Type, - convert: (String) throws -> T - ) throws -> [T] { - let values = headerFields.values(name: name) - if values.isEmpty { - throw RuntimeError.missingRequiredHeaderField(name) - } - return try values.map { value in try convert(value) } - } - func setQueryItem( in request: inout Request, style: ParameterStyle?, @@ -370,31 +307,6 @@ extension Converter { ) } - func setQueryItems( - in request: inout Request, - style: ParameterStyle?, - explode: Bool?, - name: String, - values: [T]?, - convert: (T) throws -> String - ) throws { - guard let values else { - return - } - let (_, resolvedExplode) = try ParameterStyle.resolvedQueryStyleAndExplode( - name: name, - style: style, - explode: explode - ) - for value in values { - request.addQueryItem( - name: name, - value: try convert(value), - explode: resolvedExplode - ) - } - } - func getOptionalQueryItem( in queryParameters: [URLQueryItem], style: ParameterStyle?, @@ -444,61 +356,6 @@ extension Converter { return value } - func getOptionalQueryItems( - in queryParameters: [URLQueryItem], - style: ParameterStyle?, - explode: Bool?, - name: String, - as type: [T].Type, - convert: (String) throws -> T - ) throws -> [T]? { - let (_, resolvedExplode) = try ParameterStyle.resolvedQueryStyleAndExplode( - name: name, - style: style, - explode: explode - ) - let untypedValues = - queryParameters - .filter { $0.name == name } - .map { $0.value ?? "" } - // If explode is false, some of the items might have multiple - // comma-separate values, so we need to split them here. - let processedValues: [String] - if resolvedExplode { - processedValues = untypedValues - } else { - processedValues = untypedValues.flatMap { multiValue in - multiValue - .split(separator: ",", omittingEmptySubsequences: false) - .map(String.init) - } - } - return try processedValues.map(convert) - } - - func getRequiredQueryItems( - in queryParameters: [URLQueryItem], - style: ParameterStyle?, - explode: Bool?, - name: String, - as type: [T].Type, - convert: (String) throws -> T - ) throws -> [T] { - guard - let values = try getOptionalQueryItems( - in: queryParameters, - style: style, - explode: explode, - name: name, - as: type, - convert: convert - ), !values.isEmpty - else { - throw RuntimeError.missingRequiredQueryParameter(name) - } - return values - } - func setRequiredRequestBody( _ value: T, headerFields: inout [HeaderField], @@ -579,18 +436,6 @@ extension Converter { return try convert(value) } - func convertBinaryToData( - _ binary: Data - ) throws -> Data { - binary - } - - func convertDataToBinary( - _ data: Data - ) throws -> Data { - data - } - func getRequiredRequestPath( in pathParameters: [String: String], name: String, diff --git a/Sources/OpenAPIRuntime/Deprecated/Deprecated.swift b/Sources/OpenAPIRuntime/Deprecated/Deprecated.swift index f1fc0517..af09a85b 100644 --- a/Sources/OpenAPIRuntime/Deprecated/Deprecated.swift +++ b/Sources/OpenAPIRuntime/Deprecated/Deprecated.swift @@ -1362,4 +1362,378 @@ extension Converter { ) } + // | client | set | request path | text | string-convertible | required | renderedRequestPath | + @available(*, deprecated) + public func renderedRequestPath( + template: String, + parameters: [any _StringConvertible] + ) throws -> String { + var renderedString = template + for parameter in parameters { + if let range = renderedString.range(of: "{}") { + renderedString = renderedString.replacingOccurrences( + of: "{}", + with: parameter.description, + range: range + ) + } + } + return renderedString + } + + // | client | set | request query | text | string-convertible | both | setQueryItemAsText | + @available(*, deprecated) + public func setQueryItemAsText( + in request: inout Request, + style: ParameterStyle?, + explode: Bool?, + name: String, + value: T? + ) throws { + try setQueryItem( + in: &request, + style: style, + explode: explode, + name: name, + value: value, + convert: convertStringConvertibleToText + ) + } + + // | client | set | request query | text | array of string-convertibles | both | setQueryItemAsText | + @available(*, deprecated) + public func setQueryItemAsText( + in request: inout Request, + style: ParameterStyle?, + explode: Bool?, + name: String, + value: [T]? + ) throws { + try setQueryItems( + in: &request, + style: style, + explode: explode, + name: name, + values: value, + convert: convertStringConvertibleToText + ) + } + + // | client | set | request query | text | date | both | setQueryItemAsText | + @available(*, deprecated) + public func setQueryItemAsText( + in request: inout Request, + style: ParameterStyle?, + explode: Bool?, + name: String, + value: Date? + ) throws { + try setQueryItem( + in: &request, + style: style, + explode: explode, + name: name, + value: value, + convert: convertDateToText + ) + } + + // | client | set | request query | text | array of dates | both | setQueryItemAsText | + @available(*, deprecated) + public func setQueryItemAsText( + in request: inout Request, + style: ParameterStyle?, + explode: Bool?, + name: String, + value: [Date]? + ) throws { + try setQueryItems( + in: &request, + style: style, + explode: explode, + name: name, + values: value, + convert: convertDateToText + ) + } + + // | client | get | response body | text | string-convertible | required | getResponseBodyAsText | + @available(*, deprecated) + public func getResponseBodyAsText( + _ type: T.Type, + from data: Data, + transforming transform: (T) -> C + ) throws -> C { + try getResponseBody( + type, + from: data, + transforming: transform, + convert: convertTextDataToStringConvertible + ) + } + + // | client | get | response body | text | date | required | getResponseBodyAsText | + @available(*, deprecated) + public func getResponseBodyAsText( + _ type: Date.Type, + from data: Data, + transforming transform: (Date) -> C + ) throws -> C { + try getResponseBody( + type, + from: data, + transforming: transform, + convert: convertTextDataToDate + ) + } + + // | client | set | request body | text | string-convertible | optional | setOptionalRequestBodyAsText | + @available(*, deprecated) + public func setOptionalRequestBodyAsText( + _ value: T?, + headerFields: inout [HeaderField], + contentType: String + ) throws -> Data? { + try setOptionalRequestBody( + value, + headerFields: &headerFields, + contentType: contentType, + convert: convertStringConvertibleToTextData + ) + } + + // | client | set | request body | text | string-convertible | required | setRequiredRequestBodyAsText | + @available(*, deprecated) + public func setRequiredRequestBodyAsText( + _ value: T, + headerFields: inout [HeaderField], + contentType: String + ) throws -> Data { + try setRequiredRequestBody( + value, + headerFields: &headerFields, + contentType: contentType, + convert: convertStringConvertibleToTextData + ) + } + + // | client | set | request body | text | date | optional | setOptionalRequestBodyAsText | + @available(*, deprecated) + public func setOptionalRequestBodyAsText( + _ value: Date?, + headerFields: inout [HeaderField], + contentType: String + ) throws -> Data? { + try setOptionalRequestBody( + value, + headerFields: &headerFields, + contentType: contentType, + convert: convertDateToTextData + ) + } + + // | client | set | request body | text | date | required | setRequiredRequestBodyAsText | + @available(*, deprecated) + public func setRequiredRequestBodyAsText( + _ value: Date, + headerFields: inout [HeaderField], + contentType: String + ) throws -> Data { + try setRequiredRequestBody( + value, + headerFields: &headerFields, + contentType: contentType, + convert: convertDateToTextData + ) + } + + @available(*, deprecated) + func convertStringConvertibleToText( + _ value: T + ) throws -> String { + value.description + } + + @available(*, deprecated) + func convertStringConvertibleToTextData( + _ value: T + ) throws -> Data { + try Data(convertStringConvertibleToText(value).utf8) + } + + @available(*, deprecated) + func convertDateToText(_ value: Date) throws -> String { + try configuration.dateTranscoder.encode(value) + } + + @available(*, deprecated) + func convertDateToTextData(_ value: Date) throws -> Data { + try Data(convertDateToText(value).utf8) + } + + @available(*, deprecated) + func convertTextToDate(_ stringValue: String) throws -> Date { + try configuration.dateTranscoder.decode(stringValue) + } + + @available(*, deprecated) + func convertTextDataToDate(_ data: Data) throws -> Date { + let stringValue = String(decoding: data, as: UTF8.self) + return try convertTextToDate(stringValue) + } + + @available(*, deprecated) + func convertHeaderFieldTextToDate(_ stringValue: String) throws -> Date { + try convertTextToDate(stringValue) + } + + @available(*, deprecated) + func convertTextToStringConvertible( + _ stringValue: String + ) throws -> T { + guard let value = T.init(stringValue) else { + throw RuntimeError.failedToDecodeStringConvertibleValue( + type: String(describing: T.self) + ) + } + return value + } + + @available(*, deprecated) + func convertTextDataToStringConvertible( + _ data: Data + ) throws -> T { + let stringValue = String(decoding: data, as: UTF8.self) + return try convertTextToStringConvertible(stringValue) + } + + @available(*, deprecated) + func setHeaderFields( + in headerFields: inout [HeaderField], + name: String, + values: [T]?, + convert: (T) throws -> String + ) throws { + guard let values else { + return + } + for value in values { + headerFields.add( + name: name, + value: try convert(value) + ) + } + } + + @available(*, deprecated) + func getOptionalHeaderFields( + in headerFields: [HeaderField], + name: String, + as type: [T].Type, + convert: (String) throws -> T + ) throws -> [T]? { + let values = headerFields.values(name: name) + if values.isEmpty { + return nil + } + return try values.map { value in try convert(value) } + } + + @available(*, deprecated) + func getRequiredHeaderFields( + in headerFields: [HeaderField], + name: String, + as type: [T].Type, + convert: (String) throws -> T + ) throws -> [T] { + let values = headerFields.values(name: name) + if values.isEmpty { + throw RuntimeError.missingRequiredHeaderField(name) + } + return try values.map { value in try convert(value) } + } + + @available(*, deprecated) + func setQueryItems( + in request: inout Request, + style: ParameterStyle?, + explode: Bool?, + name: String, + values: [T]?, + convert: (T) throws -> String + ) throws { + guard let values else { + return + } + let (_, resolvedExplode) = try ParameterStyle.resolvedQueryStyleAndExplode( + name: name, + style: style, + explode: explode + ) + for value in values { + request.addQueryItem( + name: name, + value: try convert(value), + explode: resolvedExplode + ) + } + } + + @available(*, deprecated) + func getOptionalQueryItems( + in queryParameters: [URLQueryItem], + style: ParameterStyle?, + explode: Bool?, + name: String, + as type: [T].Type, + convert: (String) throws -> T + ) throws -> [T]? { + let (_, resolvedExplode) = try ParameterStyle.resolvedQueryStyleAndExplode( + name: name, + style: style, + explode: explode + ) + let untypedValues = + queryParameters + .filter { $0.name == name } + .map { $0.value ?? "" } + // If explode is false, some of the items might have multiple + // comma-separate values, so we need to split them here. + let processedValues: [String] + if resolvedExplode { + processedValues = untypedValues + } else { + processedValues = untypedValues.flatMap { multiValue in + multiValue + .split(separator: ",", omittingEmptySubsequences: false) + .map(String.init) + } + } + return try processedValues.map(convert) + } + + @available(*, deprecated) + func getRequiredQueryItems( + in queryParameters: [URLQueryItem], + style: ParameterStyle?, + explode: Bool?, + name: String, + as type: [T].Type, + convert: (String) throws -> T + ) throws -> [T] { + guard + let values = try getOptionalQueryItems( + in: queryParameters, + style: style, + explode: explode, + name: name, + as: type, + convert: convert + ), !values.isEmpty + else { + throw RuntimeError.missingRequiredQueryParameter(name) + } + return values + } + } From 9315d9bd285b1daf39c1a19c2390d63ee63f527c Mon Sep 17 00:00:00 2001 From: Honza Dvorsky Date: Fri, 25 Aug 2023 10:47:53 +0200 Subject: [PATCH 05/11] Add Date support to String coder, fix up query items --- .../Conversion/Converter+Client.swift | 19 ++- .../Conversion/CurrencyExtensions.swift | 40 +++--- .../Conversion/FoundationExtensions.swift | 72 ++-------- .../Conversion/ParameterStyles.swift | 11 ++ .../Deprecated/Deprecated.swift | 124 ++++++++++++++++-- .../StringCoder/StringDecoder.swift | 25 +++- .../StringCoder/StringEncoder.swift | 32 ++++- .../Test_FoundationExtensions.swift | 17 ++- .../Test_StringCodingRoundtrip.swift | 10 +- 9 files changed, 233 insertions(+), 117 deletions(-) diff --git a/Sources/OpenAPIRuntime/Conversion/Converter+Client.swift b/Sources/OpenAPIRuntime/Conversion/Converter+Client.swift index a361cfab..3ae2a908 100644 --- a/Sources/OpenAPIRuntime/Conversion/Converter+Client.swift +++ b/Sources/OpenAPIRuntime/Conversion/Converter+Client.swift @@ -37,7 +37,14 @@ extension Converter { parameters: [any Encodable] ) throws -> String { var renderedString = template - let encoder = URIEncoder(configuration: .simpleUnexplode) + let encoder = URIEncoder( + configuration: .init( + style: .simple, + explode: false, + spaceEscapingCharacter: .percentEncoded, + dateTranscoder: configuration.dateTranscoder + ) + ) for parameter in parameters { let value = try encoder.encode(parameter, forKey: "") if let range = renderedString.range(of: "{}") { @@ -59,18 +66,18 @@ extension Converter { name: String, value: T? ) throws { - try setQueryItem( + try setEscapedQueryItem( in: &request, style: style, explode: explode, name: name, value: value, - convert: { value in + convert: { value, style, explode in try convertToURI( - style: .simple, - explode: false, + style: style, + explode: explode, inBody: false, - key: "", + key: name, value: value ) } diff --git a/Sources/OpenAPIRuntime/Conversion/CurrencyExtensions.swift b/Sources/OpenAPIRuntime/Conversion/CurrencyExtensions.swift index 397d5f89..3c9df338 100644 --- a/Sources/OpenAPIRuntime/Conversion/CurrencyExtensions.swift +++ b/Sources/OpenAPIRuntime/Conversion/CurrencyExtensions.swift @@ -129,15 +129,8 @@ extension Converter { explode: Bool, inBody: Bool ) -> URICoderConfiguration { - let configStyle: URICoderConfiguration.Style - switch style { - case .form: - configStyle = .form - case .simple: - configStyle = .simple - } - return .init( - style: configStyle, + .init( + style: .init(style), explode: explode, spaceEscapingCharacter: inBody ? .plus : .percentEncoded, dateTranscoder: configuration.dateTranscoder @@ -161,7 +154,7 @@ extension Converter { let encodedString = try encoder.encode(value, forKey: key) return encodedString } - + func convertFromURI( style: ParameterStyle, explode: Bool, @@ -210,12 +203,14 @@ extension Converter { let data = Data(stringValue.utf8) return try decoder.decode(T.self, from: data) } - + func convertFromStringData( _ data: Data ) throws -> T { let encodedString = String(decoding: data, as: UTF8.self) - let decoder = StringDecoder() + let decoder = StringDecoder( + dateTranscoder: configuration.dateTranscoder + ) let value = try decoder.decode( T.self, from: encodedString @@ -226,11 +221,13 @@ extension Converter { func convertToStringData( _ value: T ) throws -> Data { - let encoder = StringEncoder() + let encoder = StringEncoder( + dateTranscoder: configuration.dateTranscoder + ) let encodedString = try encoder.encode(value) return Data(encodedString.utf8) } - + func convertBinaryToData( _ binary: Data ) throws -> Data { @@ -244,7 +241,7 @@ extension Converter { } // MARK: - Helpers for specific types of parameters - + func setHeaderField( in headerFields: inout [HeaderField], name: String, @@ -284,27 +281,24 @@ extension Converter { return try convert(stringValue) } - func setQueryItem( + func setEscapedQueryItem( in request: inout Request, style: ParameterStyle?, explode: Bool?, name: String, value: T?, - convert: (T) throws -> String + convert: (T, ParameterStyle, Bool) throws -> String ) throws { guard let value else { return } - let (_, resolvedExplode) = try ParameterStyle.resolvedQueryStyleAndExplode( + let (resolvedStyle, resolvedExplode) = try ParameterStyle.resolvedQueryStyleAndExplode( name: name, style: style, explode: explode ) - request.addQueryItem( - name: name, - value: try convert(value), - explode: resolvedExplode - ) + let uriSnippet = try convert(value, resolvedStyle, resolvedExplode) + request.addEscapedQuerySnippet(uriSnippet) } func getOptionalQueryItem( diff --git a/Sources/OpenAPIRuntime/Conversion/FoundationExtensions.swift b/Sources/OpenAPIRuntime/Conversion/FoundationExtensions.swift index 4c2b5cb8..ce6d6833 100644 --- a/Sources/OpenAPIRuntime/Conversion/FoundationExtensions.swift +++ b/Sources/OpenAPIRuntime/Conversion/FoundationExtensions.swift @@ -36,69 +36,19 @@ extension Request { query = urlComponents.percentEncodedQuery } - /// Adds the provided name and value to the URL's query. + /// Adds the provided URI snippet to the URL's query. + /// + /// Percent encoding is already applied. /// - Parameters: - /// - name: The name of the query item. - /// - value: The value of the query item. - /// - explode: A Boolean value indicating whether query items with the - /// same name should be provided as separate key-value pairs (`true`) or - /// if all the values for one key should be concatenated with a comma - /// and provided as a single key-value pair (`false`). - mutating func addQueryItem(name: String, value: String, explode: Bool) { - mutateQuery { urlComponents in - urlComponents.addStringQueryItem( - name: name, - value: value, - explode: explode - ) - } - } -} - -extension URLComponents { - - /// Adds the provided name and value to the URL's query. - /// - Parameters: - /// - name: The name of the query item. - /// - value: The value of the query item. - /// - explode: A Boolean value indicating whether query items with the - /// same name should be provided as separate key-value pairs (`true`) or - /// if all the values for one key should be concatenated with a comma - /// and provided as a single key-value pair (`false`). - mutating func addStringQueryItem( - name: String, - value: String, - explode: Bool - ) { - if explode { - queryItems = - (queryItems ?? []) + [ - .init(name: name, value: value) - ] - return - } - // When explode is false, we need to collect all the potential existing - // values from the array with the same name, add the new one, and - // concatenate them with a comma. - let originalQueryItems = queryItems ?? [] - struct GroupedQueryItems { - var matchingValues: [String] = [] - var otherItems: [URLQueryItem] = [] + /// - snippet: A full URI snippet. + mutating func addEscapedQuerySnippet(_ snippet: String) { + let prefix: String + if let query { + prefix = query + "&" + } else { + prefix = "" } - let groups = - originalQueryItems - .reduce(into: GroupedQueryItems()) { partialResult, item in - if item.name == name { - partialResult.matchingValues.append(item.value ?? "") - } else { - partialResult.otherItems.append(item) - } - } - let newItem = URLQueryItem( - name: name, - value: (groups.matchingValues + [value]).joined(separator: ",") - ) - queryItems = groups.otherItems + [newItem] + query = prefix + snippet } } diff --git a/Sources/OpenAPIRuntime/Conversion/ParameterStyles.swift b/Sources/OpenAPIRuntime/Conversion/ParameterStyles.swift index 13f3e8a7..5a833cba 100644 --- a/Sources/OpenAPIRuntime/Conversion/ParameterStyles.swift +++ b/Sources/OpenAPIRuntime/Conversion/ParameterStyles.swift @@ -49,3 +49,14 @@ extension ParameterStyle { style == .form } } + +extension URICoderConfiguration.Style { + init(_ style: ParameterStyle) { + switch style { + case .form: + self = .form + case .simple: + self = .simple + } + } +} diff --git a/Sources/OpenAPIRuntime/Deprecated/Deprecated.swift b/Sources/OpenAPIRuntime/Deprecated/Deprecated.swift index af09a85b..d4a73752 100644 --- a/Sources/OpenAPIRuntime/Deprecated/Deprecated.swift +++ b/Sources/OpenAPIRuntime/Deprecated/Deprecated.swift @@ -983,13 +983,15 @@ extension Converter { name: String, value: T? ) throws { - try setQueryItem( + try setUnescapedQueryItem( in: &request, style: nil, explode: nil, name: name, value: value, - convert: convertStringConvertibleToText + convert: { value, _, _ in + try convertStringConvertibleToText(value) + } ) } @@ -1017,13 +1019,15 @@ extension Converter { name: String, value: Date? ) throws { - try setQueryItem( + try setUnescapedQueryItem( in: &request, style: nil, explode: nil, name: name, value: value, - convert: convertDateToText + convert: { value, _, _ in + try convertDateToText(value) + } ) } @@ -1390,13 +1394,15 @@ extension Converter { name: String, value: T? ) throws { - try setQueryItem( + try setUnescapedQueryItem( in: &request, style: style, explode: explode, name: name, value: value, - convert: convertStringConvertibleToText + convert: { value, _, _ in + try convertStringConvertibleToText(value) + } ) } @@ -1428,13 +1434,15 @@ extension Converter { name: String, value: Date? ) throws { - try setQueryItem( + try setUnescapedQueryItem( in: &request, style: style, explode: explode, name: name, value: value, - convert: convertDateToText + convert: { value, _, _ in + try convertDateToText(value) + } ) } @@ -1456,7 +1464,7 @@ extension Converter { convert: convertDateToText ) } - + // | client | get | response body | text | string-convertible | required | getResponseBodyAsText | @available(*, deprecated) public func getResponseBodyAsText( @@ -1486,7 +1494,7 @@ extension Converter { convert: convertTextDataToDate ) } - + // | client | set | request body | text | string-convertible | optional | setOptionalRequestBodyAsText | @available(*, deprecated) public func setOptionalRequestBodyAsText( @@ -1586,7 +1594,7 @@ extension Converter { func convertHeaderFieldTextToDate(_ stringValue: String) throws -> Date { try convertTextToDate(stringValue) } - + @available(*, deprecated) func convertTextToStringConvertible( _ stringValue: String @@ -1671,7 +1679,7 @@ extension Converter { explode: explode ) for value in values { - request.addQueryItem( + request.addUnescapedQueryItem( name: name, value: try convert(value), explode: resolvedExplode @@ -1736,4 +1744,96 @@ extension Converter { return values } + @available(*, deprecated) + func setUnescapedQueryItem( + in request: inout Request, + style: ParameterStyle?, + explode: Bool?, + name: String, + value: T?, + convert: (T, ParameterStyle, Bool) throws -> String + ) throws { + guard let value else { + return + } + let (resolvedStyle, resolvedExplode) = try ParameterStyle.resolvedQueryStyleAndExplode( + name: name, + style: style, + explode: explode + ) + request.addUnescapedQueryItem( + name: name, + value: try convert(value, resolvedStyle, resolvedExplode), + explode: resolvedExplode + ) + } +} + +extension Request { + /// Adds the provided name and value to the URL's query. + /// - Parameters: + /// - name: The name of the query item. + /// - value: The value of the query item. + /// - explode: A Boolean value indicating whether query items with the + /// same name should be provided as separate key-value pairs (`true`) or + /// if all the values for one key should be concatenated with a comma + /// and provided as a single key-value pair (`false`). + @available(*, deprecated) + mutating func addUnescapedQueryItem(name: String, value: String, explode: Bool) { + mutateQuery { urlComponents in + urlComponents.addUnescapedStringQueryItem( + name: name, + value: value, + explode: explode + ) + } + } +} + +extension URLComponents { + + /// Adds the provided name and value to the URL's query. + /// - Parameters: + /// - name: The name of the query item. + /// - value: The value of the query item. + /// - explode: A Boolean value indicating whether query items with the + /// same name should be provided as separate key-value pairs (`true`) or + /// if all the values for one key should be concatenated with a comma + /// and provided as a single key-value pair (`false`). + @available(*, deprecated) + mutating func addUnescapedStringQueryItem( + name: String, + value: String, + explode: Bool + ) { + if explode { + queryItems = + (queryItems ?? []) + [ + .init(name: name, value: value) + ] + return + } + // When explode is false, we need to collect all the potential existing + // values from the array with the same name, add the new one, and + // concatenate them with a comma. + let originalQueryItems = queryItems ?? [] + struct GroupedQueryItems { + var matchingValues: [String] = [] + var otherItems: [URLQueryItem] = [] + } + let groups = + originalQueryItems + .reduce(into: GroupedQueryItems()) { partialResult, item in + if item.name == name { + partialResult.matchingValues.append(item.value ?? "") + } else { + partialResult.otherItems.append(item) + } + } + let newItem = URLQueryItem( + name: name, + value: (groups.matchingValues + [value]).joined(separator: ",") + ) + queryItems = groups.otherItems + [newItem] + } } diff --git a/Sources/OpenAPIRuntime/StringCoder/StringDecoder.swift b/Sources/OpenAPIRuntime/StringCoder/StringDecoder.swift index 56a1efdb..a8078306 100644 --- a/Sources/OpenAPIRuntime/StringCoder/StringDecoder.swift +++ b/Sources/OpenAPIRuntime/StringCoder/StringDecoder.swift @@ -16,7 +16,9 @@ import Foundation /// A type that decodes a `Decodable` objects from a string /// using `LosslessStringConvertible`. -struct StringDecoder: Sendable {} +struct StringDecoder: Sendable { + let dateTranscoder: any DateTranscoder +} extension StringDecoder { @@ -30,13 +32,26 @@ extension StringDecoder { _ type: T.Type = T.self, from data: String ) throws -> T { - let decoder = LosslessStringConvertibleDecoder(encodedString: data) - let value = try T.init(from: decoder) + let decoder = LosslessStringConvertibleDecoder( + dateTranscoder: dateTranscoder, + encodedString: data + ) + // We have to catch the special values early, otherwise we fall + // back to their Codable implementations, which don't give us + // a chance to customize the coding in the containers. + let value: T + switch type { + case is Date.Type: + value = try decoder.singleValueContainer().decode(Date.self) as! T + default: + value = try T.init(from: decoder) + } return value } } private struct LosslessStringConvertibleDecoder { + let dateTranscoder: any DateTranscoder let encodedString: String } @@ -385,6 +400,10 @@ extension LosslessStringConvertibleDecoder { return try decode(UInt32.self) as! T case is UInt64.Type: return try decode(UInt64.self) as! T + case is Date.Type: + return try decoder + .dateTranscoder + .decode(String(decoder.encodedString)) as! T default: guard let convertileType = T.self as? any LosslessStringConvertible.Type else { throw LosslessStringConvertibleDecoder.DecoderError.containersNotSupported diff --git a/Sources/OpenAPIRuntime/StringCoder/StringEncoder.swift b/Sources/OpenAPIRuntime/StringCoder/StringEncoder.swift index 5b3d60c8..14cae838 100644 --- a/Sources/OpenAPIRuntime/StringCoder/StringEncoder.swift +++ b/Sources/OpenAPIRuntime/StringCoder/StringEncoder.swift @@ -16,7 +16,9 @@ import Foundation /// A type that encodes an `Encodable` objects to a string, if it conforms /// to `CustomStringConvertible`. -struct StringEncoder: Sendable {} +struct StringEncoder: Sendable { + let dateTranscoder: any DateTranscoder +} extension StringEncoder { @@ -26,14 +28,36 @@ extension StringEncoder { /// - value: The value to encode. /// - Returns: The string. func encode(_ value: some Encodable) throws -> String { - let encoder = CustomStringConvertibleEncoder() - try value.encode(to: encoder) + let encoder = CustomStringConvertibleEncoder( + dateTranscoder: dateTranscoder + ) + + // We have to catch the special values early, otherwise we fall + // back to their Codable implementations, which don't give us + // a chance to customize the coding in the containers. + // We have to catch the special values early, otherwise we fall + // back to their Codable implementations, which don't give us + // a chance to customize the coding in the containers. + if let date = value as? Date { + var container = encoder.singleValueContainer() + try container.encode(date) + } else { + try value.encode(to: encoder) + } + return try encoder.nonNilEncodedString() } } private final class CustomStringConvertibleEncoder { + let dateTranscoder: any DateTranscoder + private(set) var encodedString: String? + + init(dateTranscoder: any DateTranscoder) { + self.dateTranscoder = dateTranscoder + self.encodedString = nil + } } extension CustomStringConvertibleEncoder { @@ -185,6 +209,8 @@ extension CustomStringConvertibleEncoder { try encode(value) case let value as Bool: try encode(value) + case let value as Date: + try _encodeCustomStringConvertible(encoder.dateTranscoder.encode(value)) default: guard let customStringConvertible = value as? any CustomStringConvertible else { throw CustomStringConvertibleEncoder.EncoderError.containersNotSupported diff --git a/Tests/OpenAPIRuntimeTests/Conversion/Test_FoundationExtensions.swift b/Tests/OpenAPIRuntimeTests/Conversion/Test_FoundationExtensions.swift index c5c4e68b..ef4e6bba 100644 --- a/Tests/OpenAPIRuntimeTests/Conversion/Test_FoundationExtensions.swift +++ b/Tests/OpenAPIRuntimeTests/Conversion/Test_FoundationExtensions.swift @@ -16,25 +16,28 @@ import XCTest final class Test_FoundationExtensions: Test_Runtime { + @available(*, deprecated) func testURLComponents_addStringQueryItem() throws { var components = testComponents - components.addStringQueryItem(name: "key", value: "value", explode: true) + components.addUnescapedStringQueryItem(name: "key", value: "value", explode: true) XCTAssertEqualURLString(components.url, "/api?key=value") } + @available(*, deprecated) func testURLComponents_addStringQueryItems() throws { var components = testComponents - components.addStringQueryItem(name: "key2", value: "value3", explode: true) - components.addStringQueryItem(name: "key", value: "value1", explode: true) - components.addStringQueryItem(name: "key", value: "value2", explode: true) + components.addUnescapedStringQueryItem(name: "key2", value: "value3", explode: true) + components.addUnescapedStringQueryItem(name: "key", value: "value1", explode: true) + components.addUnescapedStringQueryItem(name: "key", value: "value2", explode: true) XCTAssertEqualURLString(components.url, "/api?key2=value3&key=value1&key=value2") } + @available(*, deprecated) func testURLComponents_addStringQueryItems_unexploded() throws { var components = testComponents - components.addStringQueryItem(name: "key2", value: "value3", explode: false) - components.addStringQueryItem(name: "key", value: "value1", explode: false) - components.addStringQueryItem(name: "key", value: "value2", explode: false) + components.addUnescapedStringQueryItem(name: "key2", value: "value3", explode: false) + components.addUnescapedStringQueryItem(name: "key", value: "value1", explode: false) + components.addUnescapedStringQueryItem(name: "key", value: "value2", explode: false) XCTAssertEqualURLString(components.url, "/api?key2=value3&key=value1,value2") } } diff --git a/Tests/OpenAPIRuntimeTests/StringCoder/Test_StringCodingRoundtrip.swift b/Tests/OpenAPIRuntimeTests/StringCoder/Test_StringCodingRoundtrip.swift index daf3f089..f4ba5188 100644 --- a/Tests/OpenAPIRuntimeTests/StringCoder/Test_StringCodingRoundtrip.swift +++ b/Tests/OpenAPIRuntimeTests/StringCoder/Test_StringCodingRoundtrip.swift @@ -95,6 +95,12 @@ final class Test_StringCodingRoundtrip: Test_Runtime { true, "true" ) + + // A Date. + try _test( + Date(timeIntervalSince1970: 1_692_948_899), + "2023-08-25T07:34:59Z" + ) } func _test( @@ -103,7 +109,7 @@ final class Test_StringCodingRoundtrip: Test_Runtime { file: StaticString = #file, line: UInt = #line ) throws { - let encoder = StringEncoder() + let encoder = StringEncoder(dateTranscoder: .iso8601) let encodedString = try encoder.encode(value) XCTAssertEqual( encodedString, @@ -111,7 +117,7 @@ final class Test_StringCodingRoundtrip: Test_Runtime { file: file, line: line ) - let decoder = StringDecoder() + let decoder = StringDecoder(dateTranscoder: .iso8601) let decodedValue = try decoder.decode( T.self, from: encodedString From e24cb86f42b1f2d5838ecff825f02afb3357509f Mon Sep 17 00:00:00 2001 From: Honza Dvorsky Date: Fri, 25 Aug 2023 11:58:30 +0200 Subject: [PATCH 06/11] Path param support --- .../Conversion/Converter+Server.swift | 24 +++++++++++++++---- .../Deprecated/Deprecated.swift | 17 +++++++++++++ .../Interface/CurrencyTypes.swift | 13 ++++++++++ 3 files changed, 49 insertions(+), 5 deletions(-) diff --git a/Sources/OpenAPIRuntime/Conversion/Converter+Server.swift b/Sources/OpenAPIRuntime/Conversion/Converter+Server.swift index bea778df..58ddecf6 100644 --- a/Sources/OpenAPIRuntime/Conversion/Converter+Server.swift +++ b/Sources/OpenAPIRuntime/Conversion/Converter+Server.swift @@ -82,9 +82,8 @@ public extension Converter { throw RuntimeError.unexpectedAcceptHeader(acceptHeader) } - // | server | get | request path | text | string-convertible | required | getPathParameterAsText | - @available(*, deprecated) - func getPathParameterAsText( + // | server | get | request path | uri | codable | required | getPathParameterAsURI | + func getPathParameterAsURI( in pathParameters: [String: String], name: String, as type: T.Type @@ -92,8 +91,23 @@ public extension Converter { try getRequiredRequestPath( in: pathParameters, name: name, - as: type, - convert: convertTextToStringConvertible + as: T.self, + convert: { encodedString in + let decoder = URIDecoder( + configuration: .init( + style: .simple, + explode: false, + spaceEscapingCharacter: .percentEncoded, + dateTranscoder: configuration.dateTranscoder + ) + ) + let value = try decoder.decode( + T.self, + forKey: name, + from: encodedString + ) + return value + } ) } diff --git a/Sources/OpenAPIRuntime/Deprecated/Deprecated.swift b/Sources/OpenAPIRuntime/Deprecated/Deprecated.swift index d4a73752..bc11bd87 100644 --- a/Sources/OpenAPIRuntime/Deprecated/Deprecated.swift +++ b/Sources/OpenAPIRuntime/Deprecated/Deprecated.swift @@ -1837,3 +1837,20 @@ extension URLComponents { queryItems = groups.otherItems + [newItem] } } + +extension Converter { + // | server | get | request path | text | string-convertible | required | getPathParameterAsText | + @available(*, deprecated) + public func getPathParameterAsText( + in pathParameters: [String: String], + name: String, + as type: T.Type + ) throws -> T { + try getRequiredRequestPath( + in: pathParameters, + name: name, + as: type, + convert: convertTextToStringConvertible + ) + } +} diff --git a/Sources/OpenAPIRuntime/Interface/CurrencyTypes.swift b/Sources/OpenAPIRuntime/Interface/CurrencyTypes.swift index 88b82794..99adbc72 100644 --- a/Sources/OpenAPIRuntime/Interface/CurrencyTypes.swift +++ b/Sources/OpenAPIRuntime/Interface/CurrencyTypes.swift @@ -234,6 +234,7 @@ public struct ServerRequestMetadata: Hashable, Sendable { public var pathParameters: [String: String] /// The query parameters parsed from the URL of the HTTP request. + @available(*, deprecated, message: "Use the Request.query string directly.") public var queryParameters: [URLQueryItem] /// Creates a new metadata wrapper with the specified path and query parameters. @@ -242,6 +243,7 @@ public struct ServerRequestMetadata: Hashable, Sendable { /// request. /// - queryParameters: The query parameters parsed from the URL of /// the HTTP request. + @available(*, deprecated, message: "Use the Request.query string directly.") public init( pathParameters: [String: String] = [:], queryParameters: [URLQueryItem] = [] @@ -249,6 +251,17 @@ public struct ServerRequestMetadata: Hashable, Sendable { self.pathParameters = pathParameters self.queryParameters = queryParameters } + + /// Creates a new metadata wrapper with the specified path and query parameters. + /// - Parameters: + /// - pathParameters: Path parameters parsed from the URL of the HTTP + /// request. + public init( + pathParameters: [String: String] = [:] + ) { + self.pathParameters = pathParameters + self.queryParameters = [] + } } /// Describes the kind and associated data of a URL path component. From fec080e2108c6e5b0cc2d537d163b1c08af96b64 Mon Sep 17 00:00:00 2001 From: Honza Dvorsky Date: Fri, 25 Aug 2023 12:21:23 +0200 Subject: [PATCH 07/11] Query params --- .../Conversion/Converter+Server.swift | 197 +++++------------ .../Conversion/CurrencyExtensions.swift | 27 +-- .../Deprecated/Deprecated.swift | 204 ++++++++++++++++++ 3 files changed, 273 insertions(+), 155 deletions(-) diff --git a/Sources/OpenAPIRuntime/Conversion/Converter+Server.swift b/Sources/OpenAPIRuntime/Conversion/Converter+Server.swift index 58ddecf6..d25da47d 100644 --- a/Sources/OpenAPIRuntime/Conversion/Converter+Server.swift +++ b/Sources/OpenAPIRuntime/Conversion/Converter+Server.swift @@ -13,7 +13,7 @@ //===----------------------------------------------------------------------===// import Foundation -public extension Converter { +extension Converter { // MARK: Miscs @@ -22,7 +22,7 @@ public extension Converter { /// header. /// - Returns: The parsed content types, or the default content types if /// the header was not provided. - func extractAcceptHeaderIfPresent( + public func extractAcceptHeaderIfPresent( in headerFields: [HeaderField] ) throws -> [AcceptHeaderContentType] { guard let rawValue = headerFields.firstValue(name: "accept") else { @@ -48,7 +48,7 @@ public extension Converter { /// - substring: Expected content type, for example "application/json". /// - headerFields: Header fields in which to look for "Accept". /// Also supports wildcars, such as "application/\*" and "\*/\*". - func validateAcceptIfPresent( + public func validateAcceptIfPresent( _ substring: String, in headerFields: [HeaderField] ) throws { @@ -83,7 +83,7 @@ public extension Converter { } // | server | get | request path | uri | codable | required | getPathParameterAsURI | - func getPathParameterAsURI( + public func getPathParameterAsURI( in pathParameters: [String: String], name: String, as type: T.Type @@ -111,157 +111,75 @@ public extension Converter { ) } - // | server | get | request query | text | string-convertible | optional | getOptionalQueryItemAsText | - @available(*, deprecated) - func getOptionalQueryItemAsText( - in queryParameters: [URLQueryItem], + // | server | get | request query | uri | codable | optional | getOptionalQueryItemAsURI | + public func getOptionalQueryItemAsURI( + in query: String?, style: ParameterStyle?, explode: Bool?, name: String, as type: T.Type ) throws -> T? { try getOptionalQueryItem( - in: queryParameters, + in: query, style: style, explode: explode, name: name, as: type, - convert: convertTextToStringConvertible + convert: { query, style, explode in + let decoder = URIDecoder( + configuration: .init( + style: .init(style), + explode: explode, + spaceEscapingCharacter: .percentEncoded, + dateTranscoder: configuration.dateTranscoder + ) + ) + let value = try decoder.decode( + T.self, + forKey: name, + from: query + ) + return value + } ) } - // | server | get | request query | text | string-convertible | required | getRequiredQueryItemAsText | - @available(*, deprecated) - func getRequiredQueryItemAsText( - in queryParameters: [URLQueryItem], + // | server | get | request query | uri | codable | required | getRequiredQueryItemAsURI | + public func getRequiredQueryItemAsText( + in query: String?, style: ParameterStyle?, explode: Bool?, name: String, as type: T.Type ) throws -> T { try getRequiredQueryItem( - in: queryParameters, - style: style, - explode: explode, - name: name, - as: type, - convert: convertTextToStringConvertible - ) - } - - // | server | get | request query | text | array of string-convertibles | optional | getOptionalQueryItemAsText | - @available(*, deprecated) - func getOptionalQueryItemAsText( - in queryParameters: [URLQueryItem], - style: ParameterStyle?, - explode: Bool?, - name: String, - as type: [T].Type - ) throws -> [T]? { - try getOptionalQueryItems( - in: queryParameters, - style: style, - explode: explode, - name: name, - as: type, - convert: convertTextToStringConvertible - ) - } - - // | server | get | request query | text | array of string-convertibles | required | getRequiredQueryItemAsText | - @available(*, deprecated) - func getRequiredQueryItemAsText( - in queryParameters: [URLQueryItem], - style: ParameterStyle?, - explode: Bool?, - name: String, - as type: [T].Type - ) throws -> [T] { - try getRequiredQueryItems( - in: queryParameters, - style: style, - explode: explode, - name: name, - as: type, - convert: convertTextToStringConvertible - ) - } - - // | server | get | request query | text | date | optional | getOptionalQueryItemAsText | - func getOptionalQueryItemAsText( - in queryParameters: [URLQueryItem], - style: ParameterStyle?, - explode: Bool?, - name: String, - as type: Date.Type - ) throws -> Date? { - try getOptionalQueryItem( - in: queryParameters, + in: query, style: style, explode: explode, name: name, as: type, - convert: convertTextToDate - ) - } - - // | server | get | request query | text | date | required | getRequiredQueryItemAsText | - func getRequiredQueryItemAsText( - in queryParameters: [URLQueryItem], - style: ParameterStyle?, - explode: Bool?, - name: String, - as type: Date.Type - ) throws -> Date { - try getRequiredQueryItem( - in: queryParameters, - style: style, - explode: explode, - name: name, - as: type, - convert: convertTextToDate - ) - } - - // | server | get | request query | text | array of dates | optional | getOptionalQueryItemAsText | - func getOptionalQueryItemAsText( - in queryParameters: [URLQueryItem], - style: ParameterStyle?, - explode: Bool?, - name: String, - as type: [Date].Type - ) throws -> [Date]? { - try getOptionalQueryItems( - in: queryParameters, - style: style, - explode: explode, - name: name, - as: type, - convert: convertTextToDate - ) - } - - // | server | get | request query | text | array of dates | required | getRequiredQueryItemAsText | - func getRequiredQueryItemAsText( - in queryParameters: [URLQueryItem], - style: ParameterStyle?, - explode: Bool?, - name: String, - as type: [Date].Type - ) throws -> [Date] { - try getRequiredQueryItems( - in: queryParameters, - style: style, - explode: explode, - name: name, - as: type, - convert: convertTextToDate + convert: { query, style, explode in + let decoder = URIDecoder( + configuration: .init( + style: .init(style), + explode: explode, + spaceEscapingCharacter: .percentEncoded, + dateTranscoder: configuration.dateTranscoder + ) + ) + let value = try decoder.decode( + T.self, + forKey: name, + from: query + ) + return value + } ) } // | server | get | request body | text | string-convertible | optional | getOptionalRequestBodyAsText | @available(*, deprecated) - func getOptionalRequestBodyAsText( + public func getOptionalRequestBodyAsText( _ type: T.Type, from data: Data?, transforming transform: (T) -> C @@ -276,7 +194,7 @@ public extension Converter { // | server | get | request body | text | string-convertible | required | getRequiredRequestBodyAsText | @available(*, deprecated) - func getRequiredRequestBodyAsText( + public func getRequiredRequestBodyAsText( _ type: T.Type, from data: Data?, transforming transform: (T) -> C @@ -290,7 +208,8 @@ public extension Converter { } // | server | get | request body | text | date | optional | getOptionalRequestBodyAsText | - func getOptionalRequestBodyAsText( + @available(*, deprecated) + public func getOptionalRequestBodyAsText( _ type: Date.Type, from data: Data?, transforming transform: (Date) -> C @@ -304,7 +223,8 @@ public extension Converter { } // | server | get | request body | text | date | required | getRequiredRequestBodyAsText | - func getRequiredRequestBodyAsText( + @available(*, deprecated) + public func getRequiredRequestBodyAsText( _ type: Date.Type, from data: Data?, transforming transform: (Date) -> C @@ -318,7 +238,7 @@ public extension Converter { } // | server | get | request body | JSON | codable | optional | getOptionalRequestBodyAsJSON | - func getOptionalRequestBodyAsJSON( + public func getOptionalRequestBodyAsJSON( _ type: T.Type, from data: Data?, transforming transform: (T) -> C @@ -332,7 +252,7 @@ public extension Converter { } // | server | get | request body | JSON | codable | required | getRequiredRequestBodyAsJSON | - func getRequiredRequestBodyAsJSON( + public func getRequiredRequestBodyAsJSON( _ type: T.Type, from data: Data?, transforming transform: (T) -> C @@ -346,7 +266,7 @@ public extension Converter { } // | server | get | request body | binary | data | optional | getOptionalRequestBodyAsBinary | - func getOptionalRequestBodyAsBinary( + public func getOptionalRequestBodyAsBinary( _ type: Data.Type, from data: Data?, transforming transform: (Data) -> C @@ -360,7 +280,7 @@ public extension Converter { } // | server | get | request body | binary | data | required | getRequiredRequestBodyAsBinary | - func getRequiredRequestBodyAsBinary( + public func getRequiredRequestBodyAsBinary( _ type: Data.Type, from data: Data?, transforming transform: (Data) -> C @@ -375,7 +295,7 @@ public extension Converter { // | server | set | response body | text | string-convertible | required | setResponseBodyAsText | @available(*, deprecated) - func setResponseBodyAsText( + public func setResponseBodyAsText( _ value: T, headerFields: inout [HeaderField], contentType: String @@ -389,7 +309,8 @@ public extension Converter { } // | server | set | response body | text | date | required | setResponseBodyAsText | - func setResponseBodyAsText( + @available(*, deprecated) + public func setResponseBodyAsText( _ value: Date, headerFields: inout [HeaderField], contentType: String @@ -403,7 +324,7 @@ public extension Converter { } // | server | set | response body | JSON | codable | required | setResponseBodyAsJSON | - func setResponseBodyAsJSON( + public func setResponseBodyAsJSON( _ value: T, headerFields: inout [HeaderField], contentType: String @@ -417,7 +338,7 @@ public extension Converter { } // | server | set | response body | binary | data | required | setResponseBodyAsBinary | - func setResponseBodyAsBinary( + public func setResponseBodyAsBinary( _ value: Data, headerFields: inout [HeaderField], contentType: String diff --git a/Sources/OpenAPIRuntime/Conversion/CurrencyExtensions.swift b/Sources/OpenAPIRuntime/Conversion/CurrencyExtensions.swift index 3c9df338..c9234ca8 100644 --- a/Sources/OpenAPIRuntime/Conversion/CurrencyExtensions.swift +++ b/Sources/OpenAPIRuntime/Conversion/CurrencyExtensions.swift @@ -302,42 +302,35 @@ extension Converter { } func getOptionalQueryItem( - in queryParameters: [URLQueryItem], + in query: String?, style: ParameterStyle?, explode: Bool?, name: String, as type: T.Type, - convert: (String) throws -> T + convert: (String, ParameterStyle, Bool) throws -> T ) throws -> T? { - // Even though the return value isn't used, the validation - // in the method is important for consistently handling - // style+explode combinations in all the helper functions. - let (_, _) = try ParameterStyle.resolvedQueryStyleAndExplode( + guard let query else { + return nil + } + let (resolvedStyle, resolvedExplode) = try ParameterStyle.resolvedQueryStyleAndExplode( name: name, style: style, explode: explode ) - guard - let untypedValue = - queryParameters - .first(where: { $0.name == name }) - else { - return nil - } - return try convert(untypedValue.value ?? "") + return try convert(query, resolvedStyle, resolvedExplode) } func getRequiredQueryItem( - in queryParameters: [URLQueryItem], + in query: String?, style: ParameterStyle?, explode: Bool?, name: String, as type: T.Type, - convert: (String) throws -> T + convert: (String, ParameterStyle, Bool) throws -> T ) throws -> T { guard let value = try getOptionalQueryItem( - in: queryParameters, + in: query, style: style, explode: explode, name: name, diff --git a/Sources/OpenAPIRuntime/Deprecated/Deprecated.swift b/Sources/OpenAPIRuntime/Deprecated/Deprecated.swift index bc11bd87..52963496 100644 --- a/Sources/OpenAPIRuntime/Deprecated/Deprecated.swift +++ b/Sources/OpenAPIRuntime/Deprecated/Deprecated.swift @@ -1853,4 +1853,208 @@ extension Converter { convert: convertTextToStringConvertible ) } + + // | server | get | request query | text | string-convertible | optional | getOptionalQueryItemAsText | + @available(*, deprecated) + public func getOptionalQueryItemAsText( + in queryParameters: [URLQueryItem], + style: ParameterStyle?, + explode: Bool?, + name: String, + as type: T.Type + ) throws -> T? { + try getOptionalQueryItem( + in: queryParameters, + style: style, + explode: explode, + name: name, + as: type, + convert: convertTextToStringConvertible + ) + } + + // | server | get | request query | text | string-convertible | required | getRequiredQueryItemAsText | + @available(*, deprecated) + public func getRequiredQueryItemAsText( + in queryParameters: [URLQueryItem], + style: ParameterStyle?, + explode: Bool?, + name: String, + as type: T.Type + ) throws -> T { + try getRequiredQueryItem( + in: queryParameters, + style: style, + explode: explode, + name: name, + as: type, + convert: convertTextToStringConvertible + ) + } + + // | server | get | request query | text | array of string-convertibles | optional | getOptionalQueryItemAsText | + @available(*, deprecated) + public func getOptionalQueryItemAsText( + in queryParameters: [URLQueryItem], + style: ParameterStyle?, + explode: Bool?, + name: String, + as type: [T].Type + ) throws -> [T]? { + try getOptionalQueryItems( + in: queryParameters, + style: style, + explode: explode, + name: name, + as: type, + convert: convertTextToStringConvertible + ) + } + + // | server | get | request query | text | array of string-convertibles | required | getRequiredQueryItemAsText | + @available(*, deprecated) + public func getRequiredQueryItemAsText( + in queryParameters: [URLQueryItem], + style: ParameterStyle?, + explode: Bool?, + name: String, + as type: [T].Type + ) throws -> [T] { + try getRequiredQueryItems( + in: queryParameters, + style: style, + explode: explode, + name: name, + as: type, + convert: convertTextToStringConvertible + ) + } + + // | server | get | request query | text | date | optional | getOptionalQueryItemAsText | + @available(*, deprecated) + public func getOptionalQueryItemAsText( + in queryParameters: [URLQueryItem], + style: ParameterStyle?, + explode: Bool?, + name: String, + as type: Date.Type + ) throws -> Date? { + try getOptionalQueryItem( + in: queryParameters, + style: style, + explode: explode, + name: name, + as: type, + convert: convertTextToDate + ) + } + + // | server | get | request query | text | date | required | getRequiredQueryItemAsText | + @available(*, deprecated) + public func getRequiredQueryItemAsText( + in queryParameters: [URLQueryItem], + style: ParameterStyle?, + explode: Bool?, + name: String, + as type: Date.Type + ) throws -> Date { + try getRequiredQueryItem( + in: queryParameters, + style: style, + explode: explode, + name: name, + as: type, + convert: convertTextToDate + ) + } + + // | server | get | request query | text | array of dates | optional | getOptionalQueryItemAsText | + @available(*, deprecated) + public func getOptionalQueryItemAsText( + in queryParameters: [URLQueryItem], + style: ParameterStyle?, + explode: Bool?, + name: String, + as type: [Date].Type + ) throws -> [Date]? { + try getOptionalQueryItems( + in: queryParameters, + style: style, + explode: explode, + name: name, + as: type, + convert: convertTextToDate + ) + } + + // | server | get | request query | text | array of dates | required | getRequiredQueryItemAsText | + @available(*, deprecated) + public func getRequiredQueryItemAsText( + in queryParameters: [URLQueryItem], + style: ParameterStyle?, + explode: Bool?, + name: String, + as type: [Date].Type + ) throws -> [Date] { + try getRequiredQueryItems( + in: queryParameters, + style: style, + explode: explode, + name: name, + as: type, + convert: convertTextToDate + ) + } + + @available(*, deprecated) + func getOptionalQueryItem( + in queryParameters: [URLQueryItem], + style: ParameterStyle?, + explode: Bool?, + name: String, + as type: T.Type, + convert: (String) throws -> T + ) throws -> T? { + // Even though the return value isn't used, the validation + // in the method is important for consistently handling + // style+explode combinations in all the helper functions. + let (_, _) = try ParameterStyle.resolvedQueryStyleAndExplode( + name: name, + style: style, + explode: explode + ) + guard + let untypedValue = + queryParameters + .first(where: { $0.name == name }) + else { + return nil + } + return try convert(untypedValue.value ?? "") + } + + @available(*, deprecated) + func getRequiredQueryItem( + in queryParameters: [URLQueryItem], + style: ParameterStyle?, + explode: Bool?, + name: String, + as type: T.Type, + convert: (String) throws -> T + ) throws -> T { + guard + let value = try getOptionalQueryItem( + in: queryParameters, + style: style, + explode: explode, + name: name, + as: type, + convert: convert + ) + else { + throw RuntimeError.missingRequiredQueryParameter(name) + } + return value + } + } From 5fc468c0693c0d6c09109f51f0c63a5714e66a3e Mon Sep 17 00:00:00 2001 From: Honza Dvorsky Date: Fri, 25 Aug 2023 13:00:09 +0200 Subject: [PATCH 08/11] Request bodies working --- .../Conversion/Converter+Server.swift | 85 ++++++------------ .../Deprecated/Deprecated.swift | 90 +++++++++++++++++++ 2 files changed, 118 insertions(+), 57 deletions(-) diff --git a/Sources/OpenAPIRuntime/Conversion/Converter+Server.swift b/Sources/OpenAPIRuntime/Conversion/Converter+Server.swift index d25da47d..9838011f 100644 --- a/Sources/OpenAPIRuntime/Conversion/Converter+Server.swift +++ b/Sources/OpenAPIRuntime/Conversion/Converter+Server.swift @@ -177,9 +177,8 @@ extension Converter { ) } - // | server | get | request body | text | string-convertible | optional | getOptionalRequestBodyAsText | - @available(*, deprecated) - public func getOptionalRequestBodyAsText( + // | server | get | request body | string | codable | optional | getOptionalRequestBodyAsString | + public func getOptionalRequestBodyAsString( _ type: T.Type, from data: Data?, transforming transform: (T) -> C @@ -188,13 +187,18 @@ extension Converter { type, from: data, transforming: transform, - convert: convertTextDataToStringConvertible + convert: { encodedData in + let decoder = StringDecoder( + dateTranscoder: configuration.dateTranscoder + ) + let encodedString = String(decoding: encodedData, as: UTF8.self) + return try decoder.decode(T.self, from: encodedString) + } ) } - // | server | get | request body | text | string-convertible | required | getRequiredRequestBodyAsText | - @available(*, deprecated) - public func getRequiredRequestBodyAsText( + // | server | get | request body | string | codable | required | getRequiredRequestBodyAsString | + public func getRequiredRequestBodyAsString( _ type: T.Type, from data: Data?, transforming transform: (T) -> C @@ -203,37 +207,13 @@ extension Converter { type, from: data, transforming: transform, - convert: convertTextDataToStringConvertible - ) - } - - // | server | get | request body | text | date | optional | getOptionalRequestBodyAsText | - @available(*, deprecated) - public func getOptionalRequestBodyAsText( - _ type: Date.Type, - from data: Data?, - transforming transform: (Date) -> C - ) throws -> C? { - try getOptionalRequestBody( - type, - from: data, - transforming: transform, - convert: convertTextDataToDate - ) - } - - // | server | get | request body | text | date | required | getRequiredRequestBodyAsText | - @available(*, deprecated) - public func getRequiredRequestBodyAsText( - _ type: Date.Type, - from data: Data?, - transforming transform: (Date) -> C - ) throws -> C { - try getRequiredRequestBody( - type, - from: data, - transforming: transform, - convert: convertTextDataToDate + convert: { encodedData in + let decoder = StringDecoder( + dateTranscoder: configuration.dateTranscoder + ) + let encodedString = String(decoding: encodedData, as: UTF8.self) + return try decoder.decode(T.self, from: encodedString) + } ) } @@ -293,9 +273,8 @@ extension Converter { ) } - // | server | set | response body | text | string-convertible | required | setResponseBodyAsText | - @available(*, deprecated) - public func setResponseBodyAsText( + // | server | set | response body | string | codable | required | setResponseBodyAsString | + public func setResponseBodyAsString( _ value: T, headerFields: inout [HeaderField], contentType: String @@ -304,22 +283,14 @@ extension Converter { value, headerFields: &headerFields, contentType: contentType, - convert: convertStringConvertibleToTextData - ) - } - - // | server | set | response body | text | date | required | setResponseBodyAsText | - @available(*, deprecated) - public func setResponseBodyAsText( - _ value: Date, - headerFields: inout [HeaderField], - contentType: String - ) throws -> Data { - try setResponseBody( - value, - headerFields: &headerFields, - contentType: contentType, - convert: convertDateToTextData + convert: { value in + let encoder = StringEncoder( + dateTranscoder: configuration.dateTranscoder + ) + let encodedString = try encoder.encode(value) + let encodedData = Data(encodedString.utf8) + return encodedData + } ) } diff --git a/Sources/OpenAPIRuntime/Deprecated/Deprecated.swift b/Sources/OpenAPIRuntime/Deprecated/Deprecated.swift index 52963496..8d04bc44 100644 --- a/Sources/OpenAPIRuntime/Deprecated/Deprecated.swift +++ b/Sources/OpenAPIRuntime/Deprecated/Deprecated.swift @@ -2057,4 +2057,94 @@ extension Converter { return value } + // | server | get | request body | text | string-convertible | optional | getOptionalRequestBodyAsText | + @available(*, deprecated) + public func getOptionalRequestBodyAsText( + _ type: T.Type, + from data: Data?, + transforming transform: (T) -> C + ) throws -> C? { + try getOptionalRequestBody( + type, + from: data, + transforming: transform, + convert: convertTextDataToStringConvertible + ) + } + + // | server | get | request body | text | string-convertible | required | getRequiredRequestBodyAsText | + @available(*, deprecated) + public func getRequiredRequestBodyAsText( + _ type: T.Type, + from data: Data?, + transforming transform: (T) -> C + ) throws -> C { + try getRequiredRequestBody( + type, + from: data, + transforming: transform, + convert: convertTextDataToStringConvertible + ) + } + + // | server | get | request body | text | date | optional | getOptionalRequestBodyAsText | + @available(*, deprecated) + public func getOptionalRequestBodyAsText( + _ type: Date.Type, + from data: Data?, + transforming transform: (Date) -> C + ) throws -> C? { + try getOptionalRequestBody( + type, + from: data, + transforming: transform, + convert: convertTextDataToDate + ) + } + + // | server | get | request body | text | date | required | getRequiredRequestBodyAsText | + @available(*, deprecated) + public func getRequiredRequestBodyAsText( + _ type: Date.Type, + from data: Data?, + transforming transform: (Date) -> C + ) throws -> C { + try getRequiredRequestBody( + type, + from: data, + transforming: transform, + convert: convertTextDataToDate + ) + } + + // | server | set | response body | text | string-convertible | required | setResponseBodyAsText | + @available(*, deprecated) + public func setResponseBodyAsText( + _ value: T, + headerFields: inout [HeaderField], + contentType: String + ) throws -> Data { + try setResponseBody( + value, + headerFields: &headerFields, + contentType: contentType, + convert: convertStringConvertibleToTextData + ) + } + + // | server | set | response body | text | date | required | setResponseBodyAsText | + @available(*, deprecated) + public func setResponseBodyAsText( + _ value: Date, + headerFields: inout [HeaderField], + contentType: String + ) throws -> Data { + try setResponseBody( + value, + headerFields: &headerFields, + contentType: contentType, + convert: convertDateToTextData + ) + } + } From bbeb04e1859a17dc8b125df1b1bc02f54c117d9d Mon Sep 17 00:00:00 2001 From: Honza Dvorsky Date: Fri, 25 Aug 2023 13:54:59 +0200 Subject: [PATCH 09/11] More cleanup --- .../Conversion/Converter+Client.swift | 22 ++++++++-------- .../Conversion/Converter+Common.swift | 12 ++++----- .../Conversion/Converter+Server.swift | 26 +++++++++---------- ...cated_AutoLosslessStringConvertible.swift} | 0 .../Deprecated_StringConvertible.swift} | 0 .../Interface/CurrencyTypes.swift | 13 ---------- .../Test_Deprecated_StringConvertible.swift} | 3 ++- 7 files changed, 32 insertions(+), 44 deletions(-) rename Sources/OpenAPIRuntime/{Base/_AutoLosslessStringConvertible.swift => Deprecated/Deprecated_AutoLosslessStringConvertible.swift} (100%) rename Sources/OpenAPIRuntime/{Base/_StringConvertible.swift => Deprecated/Deprecated_StringConvertible.swift} (100%) rename Tests/OpenAPIRuntimeTests/{Base/Test_StringConvertible.swift => Deprecated/Test_Deprecated_StringConvertible.swift} (92%) diff --git a/Sources/OpenAPIRuntime/Conversion/Converter+Client.swift b/Sources/OpenAPIRuntime/Conversion/Converter+Client.swift index 3ae2a908..4c484818 100644 --- a/Sources/OpenAPIRuntime/Conversion/Converter+Client.swift +++ b/Sources/OpenAPIRuntime/Conversion/Converter+Client.swift @@ -31,7 +31,7 @@ extension Converter { ) } - // | client | set | request path | uri | codable | required | renderedPath | + // | client | set | request path | URI | required | renderedPath | public func renderedPath( template: String, parameters: [any Encodable] @@ -58,7 +58,7 @@ extension Converter { return renderedString } - // | client | set | request query | uri | codable | both | setQueryItemAsURI | + // | client | set | request query | URI | both | setQueryItemAsURI | public func setQueryItemAsURI( in request: inout Request, style: ParameterStyle?, @@ -84,7 +84,7 @@ extension Converter { ) } - // | client | set | request body | string | codable | optional | setOptionalRequestBodyAsString | + // | client | set | request body | string | optional | setOptionalRequestBodyAsString | public func setOptionalRequestBodyAsString( _ value: T?, headerFields: inout [HeaderField], @@ -98,7 +98,7 @@ extension Converter { ) } - // | client | set | request body | string | codable | required | setRequiredRequestBodyAsString | + // | client | set | request body | string | required | setRequiredRequestBodyAsString | public func setRequiredRequestBodyAsString( _ value: T, headerFields: inout [HeaderField], @@ -112,7 +112,7 @@ extension Converter { ) } - // | client | set | request body | JSON | codable | optional | setOptionalRequestBodyAsJSON | + // | client | set | request body | JSON | optional | setOptionalRequestBodyAsJSON | public func setOptionalRequestBodyAsJSON( _ value: T?, headerFields: inout [HeaderField], @@ -126,7 +126,7 @@ extension Converter { ) } - // | client | set | request body | JSON | codable | required | setRequiredRequestBodyAsJSON | + // | client | set | request body | JSON | required | setRequiredRequestBodyAsJSON | public func setRequiredRequestBodyAsJSON( _ value: T, headerFields: inout [HeaderField], @@ -140,7 +140,7 @@ extension Converter { ) } - // | client | set | request body | binary | data | optional | setOptionalRequestBodyAsBinary | + // | client | set | request body | binary | optional | setOptionalRequestBodyAsBinary | public func setOptionalRequestBodyAsBinary( _ value: Data?, headerFields: inout [HeaderField], @@ -154,7 +154,7 @@ extension Converter { ) } - // | client | set | request body | binary | data | required | setRequiredRequestBodyAsBinary | + // | client | set | request body | binary | required | setRequiredRequestBodyAsBinary | public func setRequiredRequestBodyAsBinary( _ value: Data, headerFields: inout [HeaderField], @@ -168,7 +168,7 @@ extension Converter { ) } - // | client | get | response body | string | codable | required | getResponseBodyAsString | + // | client | get | response body | string | required | getResponseBodyAsString | public func getResponseBodyAsString( _ type: T.Type, from data: Data, @@ -182,7 +182,7 @@ extension Converter { ) } - // | client | get | response body | JSON | codable | required | getResponseBodyAsJSON | + // | client | get | response body | JSON | required | getResponseBodyAsJSON | public func getResponseBodyAsJSON( _ type: T.Type, from data: Data, @@ -196,7 +196,7 @@ extension Converter { ) } - // | client | get | response body | binary | data | required | getResponseBodyAsBinary | + // | client | get | response body | binary | required | getResponseBodyAsBinary | public func getResponseBodyAsBinary( _ type: Data.Type, from data: Data, diff --git a/Sources/OpenAPIRuntime/Conversion/Converter+Common.swift b/Sources/OpenAPIRuntime/Conversion/Converter+Common.swift index 0312fe4b..1630a397 100644 --- a/Sources/OpenAPIRuntime/Conversion/Converter+Common.swift +++ b/Sources/OpenAPIRuntime/Conversion/Converter+Common.swift @@ -70,7 +70,7 @@ extension Converter { // MARK: - Converter helper methods - // | common | set | header field | uri | codable | both | setHeaderFieldAsURI | + // | common | set | header field | URI | both | setHeaderFieldAsURI | public func setHeaderFieldAsURI( in headerFields: inout [HeaderField], name: String, @@ -95,7 +95,7 @@ extension Converter { ) } - // | common | set | header field | JSON | codable | both | setHeaderFieldAsJSON | + // | common | set | header field | JSON | both | setHeaderFieldAsJSON | public func setHeaderFieldAsJSON( in headerFields: inout [HeaderField], name: String, @@ -109,7 +109,7 @@ extension Converter { ) } - // | common | get | header field | text | codable | optional | getOptionalHeaderFieldAsURI | + // | common | get | header field | URI | optional | getOptionalHeaderFieldAsURI | public func getOptionalHeaderFieldAsURI( in headerFields: [HeaderField], name: String, @@ -131,7 +131,7 @@ extension Converter { ) } - // | common | get | header field | text | codable | required | getRequiredHeaderFieldAsURI | + // | common | get | header field | URI | required | getRequiredHeaderFieldAsURI | public func getRequiredHeaderFieldAsURI( in headerFields: [HeaderField], name: String, @@ -153,7 +153,7 @@ extension Converter { ) } - // | common | get | header field | JSON | codable | optional | getOptionalHeaderFieldAsJSON | + // | common | get | header field | JSON | optional | getOptionalHeaderFieldAsJSON | public func getOptionalHeaderFieldAsJSON( in headerFields: [HeaderField], name: String, @@ -167,7 +167,7 @@ extension Converter { ) } - // | common | get | header field | JSON | codable | required | getRequiredHeaderFieldAsJSON | + // | common | get | header field | JSON | required | getRequiredHeaderFieldAsJSON | public func getRequiredHeaderFieldAsJSON( in headerFields: [HeaderField], name: String, diff --git a/Sources/OpenAPIRuntime/Conversion/Converter+Server.swift b/Sources/OpenAPIRuntime/Conversion/Converter+Server.swift index 9838011f..05c34097 100644 --- a/Sources/OpenAPIRuntime/Conversion/Converter+Server.swift +++ b/Sources/OpenAPIRuntime/Conversion/Converter+Server.swift @@ -82,7 +82,7 @@ extension Converter { throw RuntimeError.unexpectedAcceptHeader(acceptHeader) } - // | server | get | request path | uri | codable | required | getPathParameterAsURI | + // | server | get | request path | URI | required | getPathParameterAsURI | public func getPathParameterAsURI( in pathParameters: [String: String], name: String, @@ -111,7 +111,7 @@ extension Converter { ) } - // | server | get | request query | uri | codable | optional | getOptionalQueryItemAsURI | + // | server | get | request query | URI | optional | getOptionalQueryItemAsURI | public func getOptionalQueryItemAsURI( in query: String?, style: ParameterStyle?, @@ -144,8 +144,8 @@ extension Converter { ) } - // | server | get | request query | uri | codable | required | getRequiredQueryItemAsURI | - public func getRequiredQueryItemAsText( + // | server | get | request query | URI | required | getRequiredQueryItemAsURI | + public func getRequiredQueryItemAsURI( in query: String?, style: ParameterStyle?, explode: Bool?, @@ -177,7 +177,7 @@ extension Converter { ) } - // | server | get | request body | string | codable | optional | getOptionalRequestBodyAsString | + // | server | get | request body | string | optional | getOptionalRequestBodyAsString | public func getOptionalRequestBodyAsString( _ type: T.Type, from data: Data?, @@ -197,7 +197,7 @@ extension Converter { ) } - // | server | get | request body | string | codable | required | getRequiredRequestBodyAsString | + // | server | get | request body | string | required | getRequiredRequestBodyAsString | public func getRequiredRequestBodyAsString( _ type: T.Type, from data: Data?, @@ -217,7 +217,7 @@ extension Converter { ) } - // | server | get | request body | JSON | codable | optional | getOptionalRequestBodyAsJSON | + // | server | get | request body | JSON | optional | getOptionalRequestBodyAsJSON | public func getOptionalRequestBodyAsJSON( _ type: T.Type, from data: Data?, @@ -231,7 +231,7 @@ extension Converter { ) } - // | server | get | request body | JSON | codable | required | getRequiredRequestBodyAsJSON | + // | server | get | request body | JSON | required | getRequiredRequestBodyAsJSON | public func getRequiredRequestBodyAsJSON( _ type: T.Type, from data: Data?, @@ -245,7 +245,7 @@ extension Converter { ) } - // | server | get | request body | binary | data | optional | getOptionalRequestBodyAsBinary | + // | server | get | request body | binary | optional | getOptionalRequestBodyAsBinary | public func getOptionalRequestBodyAsBinary( _ type: Data.Type, from data: Data?, @@ -259,7 +259,7 @@ extension Converter { ) } - // | server | get | request body | binary | data | required | getRequiredRequestBodyAsBinary | + // | server | get | request body | binary | required | getRequiredRequestBodyAsBinary | public func getRequiredRequestBodyAsBinary( _ type: Data.Type, from data: Data?, @@ -273,7 +273,7 @@ extension Converter { ) } - // | server | set | response body | string | codable | required | setResponseBodyAsString | + // | server | set | response body | string | required | setResponseBodyAsString | public func setResponseBodyAsString( _ value: T, headerFields: inout [HeaderField], @@ -294,7 +294,7 @@ extension Converter { ) } - // | server | set | response body | JSON | codable | required | setResponseBodyAsJSON | + // | server | set | response body | JSON | required | setResponseBodyAsJSON | public func setResponseBodyAsJSON( _ value: T, headerFields: inout [HeaderField], @@ -308,7 +308,7 @@ extension Converter { ) } - // | server | set | response body | binary | data | required | setResponseBodyAsBinary | + // | server | set | response body | binary | required | setResponseBodyAsBinary | public func setResponseBodyAsBinary( _ value: Data, headerFields: inout [HeaderField], diff --git a/Sources/OpenAPIRuntime/Base/_AutoLosslessStringConvertible.swift b/Sources/OpenAPIRuntime/Deprecated/Deprecated_AutoLosslessStringConvertible.swift similarity index 100% rename from Sources/OpenAPIRuntime/Base/_AutoLosslessStringConvertible.swift rename to Sources/OpenAPIRuntime/Deprecated/Deprecated_AutoLosslessStringConvertible.swift diff --git a/Sources/OpenAPIRuntime/Base/_StringConvertible.swift b/Sources/OpenAPIRuntime/Deprecated/Deprecated_StringConvertible.swift similarity index 100% rename from Sources/OpenAPIRuntime/Base/_StringConvertible.swift rename to Sources/OpenAPIRuntime/Deprecated/Deprecated_StringConvertible.swift diff --git a/Sources/OpenAPIRuntime/Interface/CurrencyTypes.swift b/Sources/OpenAPIRuntime/Interface/CurrencyTypes.swift index 99adbc72..88b82794 100644 --- a/Sources/OpenAPIRuntime/Interface/CurrencyTypes.swift +++ b/Sources/OpenAPIRuntime/Interface/CurrencyTypes.swift @@ -234,7 +234,6 @@ public struct ServerRequestMetadata: Hashable, Sendable { public var pathParameters: [String: String] /// The query parameters parsed from the URL of the HTTP request. - @available(*, deprecated, message: "Use the Request.query string directly.") public var queryParameters: [URLQueryItem] /// Creates a new metadata wrapper with the specified path and query parameters. @@ -243,7 +242,6 @@ public struct ServerRequestMetadata: Hashable, Sendable { /// request. /// - queryParameters: The query parameters parsed from the URL of /// the HTTP request. - @available(*, deprecated, message: "Use the Request.query string directly.") public init( pathParameters: [String: String] = [:], queryParameters: [URLQueryItem] = [] @@ -251,17 +249,6 @@ public struct ServerRequestMetadata: Hashable, Sendable { self.pathParameters = pathParameters self.queryParameters = queryParameters } - - /// Creates a new metadata wrapper with the specified path and query parameters. - /// - Parameters: - /// - pathParameters: Path parameters parsed from the URL of the HTTP - /// request. - public init( - pathParameters: [String: String] = [:] - ) { - self.pathParameters = pathParameters - self.queryParameters = [] - } } /// Describes the kind and associated data of a URL path component. diff --git a/Tests/OpenAPIRuntimeTests/Base/Test_StringConvertible.swift b/Tests/OpenAPIRuntimeTests/Deprecated/Test_Deprecated_StringConvertible.swift similarity index 92% rename from Tests/OpenAPIRuntimeTests/Base/Test_StringConvertible.swift rename to Tests/OpenAPIRuntimeTests/Deprecated/Test_Deprecated_StringConvertible.swift index 7eee5776..5e30740c 100644 --- a/Tests/OpenAPIRuntimeTests/Base/Test_StringConvertible.swift +++ b/Tests/OpenAPIRuntimeTests/Deprecated/Test_Deprecated_StringConvertible.swift @@ -14,8 +14,9 @@ import XCTest @_spi(Generated)@testable import OpenAPIRuntime -final class Test_StringConvertible: XCTestCase { +final class Test_Deprecated_StringConvertible: XCTestCase { + @available(*, deprecated) func testConformances() throws { let values: [(any _StringConvertible, String)] = [ ("hello" as String, "hello"), From 9fafceb124b6d94bd56a0260f0e8bbc51588151b Mon Sep 17 00:00:00 2001 From: Honza Dvorsky Date: Sat, 26 Aug 2023 10:07:39 +0200 Subject: [PATCH 10/11] Brought in changes from the standalone PR --- .../StringCoder/StringDecoder.swift | 537 ++++++++++-------- .../StringCoder/StringEncoder.swift | 496 ++++++++-------- 2 files changed, 556 insertions(+), 477 deletions(-) diff --git a/Sources/OpenAPIRuntime/StringCoder/StringDecoder.swift b/Sources/OpenAPIRuntime/StringCoder/StringDecoder.swift index a8078306..22add7bf 100644 --- a/Sources/OpenAPIRuntime/StringCoder/StringDecoder.swift +++ b/Sources/OpenAPIRuntime/StringCoder/StringDecoder.swift @@ -17,16 +17,17 @@ import Foundation /// A type that decodes a `Decodable` objects from a string /// using `LosslessStringConvertible`. struct StringDecoder: Sendable { + + /// The coder used to serialize Date values. let dateTranscoder: any DateTranscoder } extension StringDecoder { /// Attempt to decode an object from a string. - /// /// - Parameters: - /// - type: The type to decode. - /// - data: The encoded string. + /// - type: The type to decode. + /// - data: The encoded string. /// - Returns: The decoded value. func decode( _ type: T.Type = T.self, @@ -50,14 +51,27 @@ extension StringDecoder { } } +/// The decoder used by `StringDecoder`. private struct LosslessStringConvertibleDecoder { + + /// The coder used to serialize Date values. let dateTranscoder: any DateTranscoder + + /// The underlying encoded string. let encodedString: String } extension LosslessStringConvertibleDecoder { + + /// A decoder error. enum DecoderError: Swift.Error { + + /// The `LosslessStringConvertible` initializer returned nil for the + /// provided raw string. case failedToDecodeValue + + /// The decoder tried to decode a nested container, which are not + /// supported. case containersNotSupported } } @@ -89,327 +103,346 @@ extension LosslessStringConvertibleDecoder: Decoder { extension LosslessStringConvertibleDecoder { - struct KeyedContainer: KeyedDecodingContainerProtocol { + /// A single value container used by `LosslessStringConvertibleDecoder`. + struct SingleValueContainer { + + /// The underlying decoder. let decoder: LosslessStringConvertibleDecoder - var codingPath: [any CodingKey] { - [] + /// Decodes a value of type conforming to `LosslessStringConvertible`. + /// - Returns: The decoded value. + private func _decodeLosslessStringConvertible( + _: T.Type = T.self + ) throws -> T { + guard let parsedValue = T(String(decoder.encodedString)) else { + throw DecodingError.typeMismatch( + T.self, + .init( + codingPath: codingPath, + debugDescription: "Failed to convert to the requested type." + ) + ) + } + return parsedValue } + } - var allKeys: [Key] { - [] - } + /// An unkeyed container used by `LosslessStringConvertibleDecoder`. + struct UnkeyedContainer { - func contains(_ key: Key) -> Bool { - false - } + /// The underlying decoder. + let decoder: LosslessStringConvertibleDecoder + } - func decodeNil(forKey key: Key) throws -> Bool { - throw LosslessStringConvertibleDecoder.DecoderError.containersNotSupported - } + /// A keyed container used by `LosslessStringConvertibleDecoder`. + struct KeyedContainer { - func decode(_ type: Bool.Type, forKey key: Key) throws -> Bool { - throw LosslessStringConvertibleDecoder.DecoderError.containersNotSupported - } + /// The underlying decoder. + let decoder: LosslessStringConvertibleDecoder + } +} - func decode(_ type: String.Type, forKey key: Key) throws -> String { - throw LosslessStringConvertibleDecoder.DecoderError.containersNotSupported - } +extension LosslessStringConvertibleDecoder.SingleValueContainer: SingleValueDecodingContainer { - func decode(_ type: Double.Type, forKey key: Key) throws -> Double { - throw LosslessStringConvertibleDecoder.DecoderError.containersNotSupported - } + var codingPath: [any CodingKey] { + [] + } - func decode(_ type: Float.Type, forKey key: Key) throws -> Float { - throw LosslessStringConvertibleDecoder.DecoderError.containersNotSupported - } + func decodeNil() -> Bool { + false + } - func decode(_ type: Int.Type, forKey key: Key) throws -> Int { - throw LosslessStringConvertibleDecoder.DecoderError.containersNotSupported - } + func decode(_ type: Bool.Type) throws -> Bool { + try _decodeLosslessStringConvertible() + } - func decode(_ type: Int8.Type, forKey key: Key) throws -> Int8 { - throw LosslessStringConvertibleDecoder.DecoderError.containersNotSupported - } + func decode(_ type: String.Type) throws -> String { + try _decodeLosslessStringConvertible() + } - func decode(_ type: Int16.Type, forKey key: Key) throws -> Int16 { - throw LosslessStringConvertibleDecoder.DecoderError.containersNotSupported - } + func decode(_ type: Double.Type) throws -> Double { + try _decodeLosslessStringConvertible() + } - func decode(_ type: Int32.Type, forKey key: Key) throws -> Int32 { - throw LosslessStringConvertibleDecoder.DecoderError.containersNotSupported - } + func decode(_ type: Float.Type) throws -> Float { + try _decodeLosslessStringConvertible() + } - func decode(_ type: Int64.Type, forKey key: Key) throws -> Int64 { - throw LosslessStringConvertibleDecoder.DecoderError.containersNotSupported - } + func decode(_ type: Int.Type) throws -> Int { + try _decodeLosslessStringConvertible() + } - func decode(_ type: UInt.Type, forKey key: Key) throws -> UInt { - throw LosslessStringConvertibleDecoder.DecoderError.containersNotSupported - } + func decode(_ type: Int8.Type) throws -> Int8 { + try _decodeLosslessStringConvertible() + } - func decode(_ type: UInt8.Type, forKey key: Key) throws -> UInt8 { - throw LosslessStringConvertibleDecoder.DecoderError.containersNotSupported - } + func decode(_ type: Int16.Type) throws -> Int16 { + try _decodeLosslessStringConvertible() + } - func decode(_ type: UInt16.Type, forKey key: Key) throws -> UInt16 { - throw LosslessStringConvertibleDecoder.DecoderError.containersNotSupported - } + func decode(_ type: Int32.Type) throws -> Int32 { + try _decodeLosslessStringConvertible() + } - func decode(_ type: UInt32.Type, forKey key: Key) throws -> UInt32 { - throw LosslessStringConvertibleDecoder.DecoderError.containersNotSupported - } + func decode(_ type: Int64.Type) throws -> Int64 { + try _decodeLosslessStringConvertible() + } - func decode(_ type: UInt64.Type, forKey key: Key) throws -> UInt64 { - throw LosslessStringConvertibleDecoder.DecoderError.containersNotSupported - } + func decode(_ type: UInt.Type) throws -> UInt { + try _decodeLosslessStringConvertible() + } - func decode(_ type: T.Type, forKey key: Key) throws -> T where T: Decodable { - throw LosslessStringConvertibleDecoder.DecoderError.containersNotSupported - } + func decode(_ type: UInt8.Type) throws -> UInt8 { + try _decodeLosslessStringConvertible() + } - func nestedContainer( - keyedBy type: NestedKey.Type, - forKey key: Key - ) throws -> KeyedDecodingContainer where NestedKey: CodingKey { - throw LosslessStringConvertibleDecoder.DecoderError.containersNotSupported - } + func decode(_ type: UInt16.Type) throws -> UInt16 { + try _decodeLosslessStringConvertible() + } - func nestedUnkeyedContainer(forKey key: Key) throws -> any UnkeyedDecodingContainer { - throw LosslessStringConvertibleDecoder.DecoderError.containersNotSupported - } + func decode(_ type: UInt32.Type) throws -> UInt32 { + try _decodeLosslessStringConvertible() + } - func superDecoder() throws -> any Decoder { - decoder - } + func decode(_ type: UInt64.Type) throws -> UInt64 { + try _decodeLosslessStringConvertible() + } - func superDecoder(forKey key: Key) throws -> any Decoder { - decoder + func decode(_ type: T.Type) throws -> T where T: Decodable { + switch type { + case is Bool.Type: + return try decode(Bool.self) as! T + case is String.Type: + return try decode(String.self) as! T + case is Double.Type: + return try decode(Double.self) as! T + case is Float.Type: + return try decode(Float.self) as! T + case is Int.Type: + return try decode(Int.self) as! T + case is Int8.Type: + return try decode(Int8.self) as! T + case is Int16.Type: + return try decode(Int16.self) as! T + case is Int32.Type: + return try decode(Int32.self) as! T + case is Int64.Type: + return try decode(Int64.self) as! T + case is UInt.Type: + return try decode(UInt.self) as! T + case is UInt8.Type: + return try decode(UInt8.self) as! T + case is UInt16.Type: + return try decode(UInt16.self) as! T + case is UInt32.Type: + return try decode(UInt32.self) as! T + case is UInt64.Type: + return try decode(UInt64.self) as! T + case is Date.Type: + return try decoder + .dateTranscoder + .decode(String(decoder.encodedString)) as! T + default: + guard let convertileType = T.self as? any LosslessStringConvertible.Type else { + throw LosslessStringConvertibleDecoder.DecoderError.containersNotSupported + } + return try _decodeLosslessStringConvertible(convertileType) as! T } } +} - struct UnkeyedContainer: UnkeyedDecodingContainer { +extension LosslessStringConvertibleDecoder.UnkeyedContainer: UnkeyedDecodingContainer { - let decoder: LosslessStringConvertibleDecoder + var codingPath: [any CodingKey] { + [] + } - var codingPath: [any CodingKey] { - [] - } + var count: Int? { + nil + } - var count: Int? { - nil - } + var isAtEnd: Bool { + true + } - var isAtEnd: Bool { - true - } + var currentIndex: Int { + 0 + } - var currentIndex: Int { - 0 - } + mutating func decodeNil() throws -> Bool { + false + } - mutating func decodeNil() throws -> Bool { - false - } + mutating func decode(_ type: Bool.Type) throws -> Bool { + throw LosslessStringConvertibleDecoder.DecoderError.containersNotSupported + } - mutating func decode(_ type: Bool.Type) throws -> Bool { - throw LosslessStringConvertibleDecoder.DecoderError.containersNotSupported - } + mutating func decode(_ type: String.Type) throws -> String { + throw LosslessStringConvertibleDecoder.DecoderError.containersNotSupported + } - mutating func decode(_ type: String.Type) throws -> String { - throw LosslessStringConvertibleDecoder.DecoderError.containersNotSupported - } + mutating func decode(_ type: Double.Type) throws -> Double { + throw LosslessStringConvertibleDecoder.DecoderError.containersNotSupported + } - mutating func decode(_ type: Double.Type) throws -> Double { - throw LosslessStringConvertibleDecoder.DecoderError.containersNotSupported - } + mutating func decode(_ type: Float.Type) throws -> Float { + throw LosslessStringConvertibleDecoder.DecoderError.containersNotSupported + } - mutating func decode(_ type: Float.Type) throws -> Float { - throw LosslessStringConvertibleDecoder.DecoderError.containersNotSupported - } + mutating func decode(_ type: Int.Type) throws -> Int { + throw LosslessStringConvertibleDecoder.DecoderError.containersNotSupported + } - mutating func decode(_ type: Int.Type) throws -> Int { - throw LosslessStringConvertibleDecoder.DecoderError.containersNotSupported - } + mutating func decode(_ type: Int8.Type) throws -> Int8 { + throw LosslessStringConvertibleDecoder.DecoderError.containersNotSupported + } - mutating func decode(_ type: Int8.Type) throws -> Int8 { - throw LosslessStringConvertibleDecoder.DecoderError.containersNotSupported - } + mutating func decode(_ type: Int16.Type) throws -> Int16 { + throw LosslessStringConvertibleDecoder.DecoderError.containersNotSupported + } - mutating func decode(_ type: Int16.Type) throws -> Int16 { - throw LosslessStringConvertibleDecoder.DecoderError.containersNotSupported - } + mutating func decode(_ type: Int32.Type) throws -> Int32 { + throw LosslessStringConvertibleDecoder.DecoderError.containersNotSupported + } - mutating func decode(_ type: Int32.Type) throws -> Int32 { - throw LosslessStringConvertibleDecoder.DecoderError.containersNotSupported - } + mutating func decode(_ type: Int64.Type) throws -> Int64 { + throw LosslessStringConvertibleDecoder.DecoderError.containersNotSupported + } - mutating func decode(_ type: Int64.Type) throws -> Int64 { - throw LosslessStringConvertibleDecoder.DecoderError.containersNotSupported - } + mutating func decode(_ type: UInt.Type) throws -> UInt { + throw LosslessStringConvertibleDecoder.DecoderError.containersNotSupported + } - mutating func decode(_ type: UInt.Type) throws -> UInt { - throw LosslessStringConvertibleDecoder.DecoderError.containersNotSupported - } + mutating func decode(_ type: UInt8.Type) throws -> UInt8 { + throw LosslessStringConvertibleDecoder.DecoderError.containersNotSupported + } - mutating func decode(_ type: UInt8.Type) throws -> UInt8 { - throw LosslessStringConvertibleDecoder.DecoderError.containersNotSupported - } + mutating func decode(_ type: UInt16.Type) throws -> UInt16 { + throw LosslessStringConvertibleDecoder.DecoderError.containersNotSupported + } - mutating func decode(_ type: UInt16.Type) throws -> UInt16 { - throw LosslessStringConvertibleDecoder.DecoderError.containersNotSupported - } + mutating func decode(_ type: UInt32.Type) throws -> UInt32 { + throw LosslessStringConvertibleDecoder.DecoderError.containersNotSupported + } - mutating func decode(_ type: UInt32.Type) throws -> UInt32 { - throw LosslessStringConvertibleDecoder.DecoderError.containersNotSupported - } + mutating func decode(_ type: UInt64.Type) throws -> UInt64 { + throw LosslessStringConvertibleDecoder.DecoderError.containersNotSupported + } - mutating func decode(_ type: UInt64.Type) throws -> UInt64 { - throw LosslessStringConvertibleDecoder.DecoderError.containersNotSupported - } + mutating func decode(_ type: T.Type) throws -> T where T: Decodable { + throw LosslessStringConvertibleDecoder.DecoderError.containersNotSupported + } - mutating func decode(_ type: T.Type) throws -> T where T: Decodable { - throw LosslessStringConvertibleDecoder.DecoderError.containersNotSupported - } + mutating func nestedContainer( + keyedBy type: NestedKey.Type + ) throws -> KeyedDecodingContainer where NestedKey: CodingKey { + throw LosslessStringConvertibleDecoder.DecoderError.containersNotSupported + } - mutating func nestedContainer( - keyedBy type: NestedKey.Type - ) throws -> KeyedDecodingContainer where NestedKey: CodingKey { - throw LosslessStringConvertibleDecoder.DecoderError.containersNotSupported - } + mutating func nestedUnkeyedContainer() throws -> any UnkeyedDecodingContainer { + throw LosslessStringConvertibleDecoder.DecoderError.containersNotSupported + } - mutating func nestedUnkeyedContainer() throws -> any UnkeyedDecodingContainer { - throw LosslessStringConvertibleDecoder.DecoderError.containersNotSupported - } + mutating func superDecoder() throws -> any Decoder { + decoder + } - mutating func superDecoder() throws -> any Decoder { - decoder - } +} +extension LosslessStringConvertibleDecoder.KeyedContainer: KeyedDecodingContainerProtocol { + + var codingPath: [any CodingKey] { + [] } - struct SingleValueContainer: SingleValueDecodingContainer { - let decoder: LosslessStringConvertibleDecoder + var allKeys: [Key] { + [] + } - var codingPath: [any CodingKey] { - [] - } + func contains(_ key: Key) -> Bool { + false + } - func decodeNil() -> Bool { - false - } + func decodeNil(forKey key: Key) throws -> Bool { + throw LosslessStringConvertibleDecoder.DecoderError.containersNotSupported + } - private func _decodeLosslessStringConvertible( - _: T.Type = T.self - ) throws -> T { - guard let parsedValue = T(String(decoder.encodedString)) else { - throw DecodingError.typeMismatch( - T.self, - .init( - codingPath: codingPath, - debugDescription: "Failed to convert to the requested type." - ) - ) - } - return parsedValue - } + func decode(_ type: Bool.Type, forKey key: Key) throws -> Bool { + throw LosslessStringConvertibleDecoder.DecoderError.containersNotSupported + } - func decode(_ type: Bool.Type) throws -> Bool { - try _decodeLosslessStringConvertible() - } + func decode(_ type: String.Type, forKey key: Key) throws -> String { + throw LosslessStringConvertibleDecoder.DecoderError.containersNotSupported + } - func decode(_ type: String.Type) throws -> String { - try _decodeLosslessStringConvertible() - } + func decode(_ type: Double.Type, forKey key: Key) throws -> Double { + throw LosslessStringConvertibleDecoder.DecoderError.containersNotSupported + } - func decode(_ type: Double.Type) throws -> Double { - try _decodeLosslessStringConvertible() - } + func decode(_ type: Float.Type, forKey key: Key) throws -> Float { + throw LosslessStringConvertibleDecoder.DecoderError.containersNotSupported + } - func decode(_ type: Float.Type) throws -> Float { - try _decodeLosslessStringConvertible() - } + func decode(_ type: Int.Type, forKey key: Key) throws -> Int { + throw LosslessStringConvertibleDecoder.DecoderError.containersNotSupported + } - func decode(_ type: Int.Type) throws -> Int { - try _decodeLosslessStringConvertible() - } + func decode(_ type: Int8.Type, forKey key: Key) throws -> Int8 { + throw LosslessStringConvertibleDecoder.DecoderError.containersNotSupported + } - func decode(_ type: Int8.Type) throws -> Int8 { - try _decodeLosslessStringConvertible() - } + func decode(_ type: Int16.Type, forKey key: Key) throws -> Int16 { + throw LosslessStringConvertibleDecoder.DecoderError.containersNotSupported + } - func decode(_ type: Int16.Type) throws -> Int16 { - try _decodeLosslessStringConvertible() - } + func decode(_ type: Int32.Type, forKey key: Key) throws -> Int32 { + throw LosslessStringConvertibleDecoder.DecoderError.containersNotSupported + } - func decode(_ type: Int32.Type) throws -> Int32 { - try _decodeLosslessStringConvertible() - } + func decode(_ type: Int64.Type, forKey key: Key) throws -> Int64 { + throw LosslessStringConvertibleDecoder.DecoderError.containersNotSupported + } - func decode(_ type: Int64.Type) throws -> Int64 { - try _decodeLosslessStringConvertible() - } + func decode(_ type: UInt.Type, forKey key: Key) throws -> UInt { + throw LosslessStringConvertibleDecoder.DecoderError.containersNotSupported + } - func decode(_ type: UInt.Type) throws -> UInt { - try _decodeLosslessStringConvertible() - } + func decode(_ type: UInt8.Type, forKey key: Key) throws -> UInt8 { + throw LosslessStringConvertibleDecoder.DecoderError.containersNotSupported + } - func decode(_ type: UInt8.Type) throws -> UInt8 { - try _decodeLosslessStringConvertible() - } + func decode(_ type: UInt16.Type, forKey key: Key) throws -> UInt16 { + throw LosslessStringConvertibleDecoder.DecoderError.containersNotSupported + } - func decode(_ type: UInt16.Type) throws -> UInt16 { - try _decodeLosslessStringConvertible() - } + func decode(_ type: UInt32.Type, forKey key: Key) throws -> UInt32 { + throw LosslessStringConvertibleDecoder.DecoderError.containersNotSupported + } - func decode(_ type: UInt32.Type) throws -> UInt32 { - try _decodeLosslessStringConvertible() - } + func decode(_ type: UInt64.Type, forKey key: Key) throws -> UInt64 { + throw LosslessStringConvertibleDecoder.DecoderError.containersNotSupported + } - func decode(_ type: UInt64.Type) throws -> UInt64 { - try _decodeLosslessStringConvertible() - } + func decode(_ type: T.Type, forKey key: Key) throws -> T where T: Decodable { + throw LosslessStringConvertibleDecoder.DecoderError.containersNotSupported + } - func decode(_ type: T.Type) throws -> T where T: Decodable { - switch type { - case is Bool.Type: - return try decode(Bool.self) as! T - case is String.Type: - return try decode(String.self) as! T - case is Double.Type: - return try decode(Double.self) as! T - case is Float.Type: - return try decode(Float.self) as! T - case is Int.Type: - return try decode(Int.self) as! T - case is Int8.Type: - return try decode(Int8.self) as! T - case is Int16.Type: - return try decode(Int16.self) as! T - case is Int32.Type: - return try decode(Int32.self) as! T - case is Int64.Type: - return try decode(Int64.self) as! T - case is UInt.Type: - return try decode(UInt.self) as! T - case is UInt8.Type: - return try decode(UInt8.self) as! T - case is UInt16.Type: - return try decode(UInt16.self) as! T - case is UInt32.Type: - return try decode(UInt32.self) as! T - case is UInt64.Type: - return try decode(UInt64.self) as! T - case is Date.Type: - return try decoder - .dateTranscoder - .decode(String(decoder.encodedString)) as! T - default: - guard let convertileType = T.self as? any LosslessStringConvertible.Type else { - throw LosslessStringConvertibleDecoder.DecoderError.containersNotSupported - } - return try _decodeLosslessStringConvertible(convertileType) as! T - } - } + func nestedContainer( + keyedBy type: NestedKey.Type, + forKey key: Key + ) throws -> KeyedDecodingContainer where NestedKey: CodingKey { + throw LosslessStringConvertibleDecoder.DecoderError.containersNotSupported + } + + func nestedUnkeyedContainer(forKey key: Key) throws -> any UnkeyedDecodingContainer { + throw LosslessStringConvertibleDecoder.DecoderError.containersNotSupported + } + + func superDecoder() throws -> any Decoder { + decoder + } + + func superDecoder(forKey key: Key) throws -> any Decoder { + decoder } } diff --git a/Sources/OpenAPIRuntime/StringCoder/StringEncoder.swift b/Sources/OpenAPIRuntime/StringCoder/StringEncoder.swift index 14cae838..22444b0a 100644 --- a/Sources/OpenAPIRuntime/StringCoder/StringEncoder.swift +++ b/Sources/OpenAPIRuntime/StringCoder/StringEncoder.swift @@ -17,6 +17,8 @@ import Foundation /// A type that encodes an `Encodable` objects to a string, if it conforms /// to `CustomStringConvertible`. struct StringEncoder: Sendable { + + /// The coder used to serialize Date values. let dateTranscoder: any DateTranscoder } @@ -25,8 +27,8 @@ extension StringEncoder { /// Attempt to encode a value into a string using `CustomStringConvertible`. /// /// - Parameters: - /// - value: The value to encode. - /// - Returns: The string. + /// - value: The value to encode. + /// - Returns: The encoded string. func encode(_ value: some Encodable) throws -> String { let encoder = CustomStringConvertibleEncoder( dateTranscoder: dateTranscoder @@ -49,11 +51,19 @@ extension StringEncoder { } } +/// The encoded used by `StringEncoder`. private final class CustomStringConvertibleEncoder { + + /// The coder used to serialize Date values. let dateTranscoder: any DateTranscoder + /// The underlying encoded string. + /// + /// Nil before the encoder set the value. private(set) var encodedString: String? + /// Creates a new encoder. + /// - Parameter dateTranscoder: The coder used to serialize Date values. init(dateTranscoder: any DateTranscoder) { self.dateTranscoder = dateTranscoder self.encodedString = nil @@ -61,13 +71,26 @@ private final class CustomStringConvertibleEncoder { } extension CustomStringConvertibleEncoder { + + /// An encoder error. enum EncoderError: Swift.Error { + + /// No value was set during the `encode(to:)` of the provided value. case valueNotSet + + /// The encoder set a nil values, which is not supported. case nilNotSupported + + /// The encoder encoded a container, which is not supported. case containersNotSupported + + /// The encoder set a value multiple times, which is not supported. case cannotEncodeMultipleValues } + /// Sets the provided value as the underlying string. + /// - Parameter value: The encoded string. + /// - Throws: An error if a value was already set previously. func setEncodedString(_ value: String) throws { guard encodedString == nil else { throw EncoderError.cannotEncodeMultipleValues @@ -75,6 +98,9 @@ extension CustomStringConvertibleEncoder { encodedString = value } + /// Checks that the underlying string was set, and returns it. + /// - Returns: The underlying string. + /// - Throws: If the underlying string is nil. func nonNilEncodedString() throws -> String { guard let encodedString else { throw EncoderError.valueNotSet @@ -108,296 +134,316 @@ extension CustomStringConvertibleEncoder: Encoder { extension CustomStringConvertibleEncoder { - struct SingleValueContainer: SingleValueEncodingContainer { - let encoder: CustomStringConvertibleEncoder + /// A single value container used by `CustomStringConvertibleEncoder`. + struct SingleValueContainer { - var codingPath: [any CodingKey] { - [] - } - - mutating func encodeNil() throws { - throw CustomStringConvertibleEncoder.EncoderError.nilNotSupported - } + /// The underlying encoder. + let encoder: CustomStringConvertibleEncoder + /// Converts the provided value to string and sets the result as the + /// underlying encoder's encoded value. + /// - Parameter value: The value to be encoded. mutating func _encodeCustomStringConvertible(_ value: some CustomStringConvertible) throws { try encoder.setEncodedString(value.description) } + } - mutating func encode(_ value: Bool) throws { - try _encodeCustomStringConvertible(value) - } + /// An unkeyed container used by `CustomStringConvertibleEncoder`. + struct UnkeyedContainer { - mutating func encode(_ value: String) throws { - try _encodeCustomStringConvertible(value) - } + /// The underlying encoder. + let encoder: CustomStringConvertibleEncoder + } - mutating func encode(_ value: Double) throws { - try _encodeCustomStringConvertible(value) - } + /// A keyed container used by `CustomStringConvertibleEncoder`. + struct KeyedContainer { - mutating func encode(_ value: Float) throws { - try _encodeCustomStringConvertible(value) - } + /// The underlying encoder. + let encoder: CustomStringConvertibleEncoder + } +} - mutating func encode(_ value: Int) throws { - try _encodeCustomStringConvertible(value) - } +extension CustomStringConvertibleEncoder.SingleValueContainer: SingleValueEncodingContainer { - mutating func encode(_ value: Int8) throws { - try _encodeCustomStringConvertible(value) - } + var codingPath: [any CodingKey] { + [] + } - mutating func encode(_ value: Int16) throws { - try _encodeCustomStringConvertible(value) - } + mutating func encodeNil() throws { + throw CustomStringConvertibleEncoder.EncoderError.nilNotSupported + } - mutating func encode(_ value: Int32) throws { - try _encodeCustomStringConvertible(value) - } + mutating func encode(_ value: Bool) throws { + try _encodeCustomStringConvertible(value) + } - mutating func encode(_ value: Int64) throws { - try _encodeCustomStringConvertible(value) - } + mutating func encode(_ value: String) throws { + try _encodeCustomStringConvertible(value) + } - mutating func encode(_ value: UInt) throws { - try _encodeCustomStringConvertible(value) - } + mutating func encode(_ value: Double) throws { + try _encodeCustomStringConvertible(value) + } - mutating func encode(_ value: UInt8) throws { - try _encodeCustomStringConvertible(value) - } + mutating func encode(_ value: Float) throws { + try _encodeCustomStringConvertible(value) + } - mutating func encode(_ value: UInt16) throws { - try _encodeCustomStringConvertible(value) - } + mutating func encode(_ value: Int) throws { + try _encodeCustomStringConvertible(value) + } - mutating func encode(_ value: UInt32) throws { - try _encodeCustomStringConvertible(value) - } + mutating func encode(_ value: Int8) throws { + try _encodeCustomStringConvertible(value) + } - mutating func encode(_ value: UInt64) throws { - try _encodeCustomStringConvertible(value) - } + mutating func encode(_ value: Int16) throws { + try _encodeCustomStringConvertible(value) + } - mutating func encode(_ value: T) throws where T: Encodable { - switch value { - case let value as UInt8: - try encode(value) - case let value as Int8: - try encode(value) - case let value as UInt16: - try encode(value) - case let value as Int16: - try encode(value) - case let value as UInt32: - try encode(value) - case let value as Int32: - try encode(value) - case let value as UInt64: - try encode(value) - case let value as Int64: - try encode(value) - case let value as Int: - try encode(value) - case let value as UInt: - try encode(value) - case let value as Float: - try encode(value) - case let value as Double: - try encode(value) - case let value as String: - try encode(value) - case let value as Bool: - try encode(value) - case let value as Date: - try _encodeCustomStringConvertible(encoder.dateTranscoder.encode(value)) - default: - guard let customStringConvertible = value as? any CustomStringConvertible else { - throw CustomStringConvertibleEncoder.EncoderError.containersNotSupported - } - try _encodeCustomStringConvertible(customStringConvertible) - } - } + mutating func encode(_ value: Int32) throws { + try _encodeCustomStringConvertible(value) } - struct KeyedContainer: KeyedEncodingContainerProtocol { - let encoder: CustomStringConvertibleEncoder + mutating func encode(_ value: Int64) throws { + try _encodeCustomStringConvertible(value) + } - var codingPath: [any CodingKey] { - [] - } + mutating func encode(_ value: UInt) throws { + try _encodeCustomStringConvertible(value) + } - mutating func superEncoder() -> any Encoder { - encoder - } + mutating func encode(_ value: UInt8) throws { + try _encodeCustomStringConvertible(value) + } - mutating func encodeNil(forKey key: Key) throws { - throw CustomStringConvertibleEncoder.EncoderError.containersNotSupported - } + mutating func encode(_ value: UInt16) throws { + try _encodeCustomStringConvertible(value) + } - mutating func encode(_ value: Bool, forKey key: Key) throws { - throw CustomStringConvertibleEncoder.EncoderError.containersNotSupported - } + mutating func encode(_ value: UInt32) throws { + try _encodeCustomStringConvertible(value) + } - mutating func encode(_ value: String, forKey key: Key) throws { - throw CustomStringConvertibleEncoder.EncoderError.containersNotSupported - } + mutating func encode(_ value: UInt64) throws { + try _encodeCustomStringConvertible(value) + } - mutating func encode(_ value: Double, forKey key: Key) throws { - throw CustomStringConvertibleEncoder.EncoderError.containersNotSupported + mutating func encode(_ value: T) throws where T: Encodable { + switch value { + case let value as UInt8: + try encode(value) + case let value as Int8: + try encode(value) + case let value as UInt16: + try encode(value) + case let value as Int16: + try encode(value) + case let value as UInt32: + try encode(value) + case let value as Int32: + try encode(value) + case let value as UInt64: + try encode(value) + case let value as Int64: + try encode(value) + case let value as Int: + try encode(value) + case let value as UInt: + try encode(value) + case let value as Float: + try encode(value) + case let value as Double: + try encode(value) + case let value as String: + try encode(value) + case let value as Bool: + try encode(value) + case let value as Date: + try _encodeCustomStringConvertible(encoder.dateTranscoder.encode(value)) + default: + guard let customStringConvertible = value as? any CustomStringConvertible else { + throw CustomStringConvertibleEncoder.EncoderError.containersNotSupported + } + try _encodeCustomStringConvertible(customStringConvertible) } + } +} - mutating func encode(_ value: Float, forKey key: Key) throws { - throw CustomStringConvertibleEncoder.EncoderError.containersNotSupported - } +extension CustomStringConvertibleEncoder.UnkeyedContainer: UnkeyedEncodingContainer { - mutating func encode(_ value: Int, forKey key: Key) throws { - throw CustomStringConvertibleEncoder.EncoderError.containersNotSupported - } + var codingPath: [any CodingKey] { + [] + } - mutating func encode(_ value: Int8, forKey key: Key) throws { - throw CustomStringConvertibleEncoder.EncoderError.containersNotSupported - } + var count: Int { + 0 + } - mutating func encode(_ value: Int16, forKey key: Key) throws { - throw CustomStringConvertibleEncoder.EncoderError.containersNotSupported - } + mutating func encodeNil() throws { + throw CustomStringConvertibleEncoder.EncoderError.containersNotSupported + } - mutating func encode(_ value: Int32, forKey key: Key) throws { - throw CustomStringConvertibleEncoder.EncoderError.containersNotSupported - } + mutating func encode(_ value: Bool) throws { + throw CustomStringConvertibleEncoder.EncoderError.containersNotSupported + } - mutating func encode(_ value: Int64, forKey key: Key) throws { - throw CustomStringConvertibleEncoder.EncoderError.containersNotSupported - } + mutating func encode(_ value: String) throws { + throw CustomStringConvertibleEncoder.EncoderError.containersNotSupported + } - mutating func encode(_ value: UInt, forKey key: Key) throws { - throw CustomStringConvertibleEncoder.EncoderError.containersNotSupported - } + mutating func encode(_ value: Double) throws { + throw CustomStringConvertibleEncoder.EncoderError.containersNotSupported + } - mutating func encode(_ value: UInt8, forKey key: Key) throws { - throw CustomStringConvertibleEncoder.EncoderError.containersNotSupported - } + mutating func encode(_ value: Float) throws { + throw CustomStringConvertibleEncoder.EncoderError.containersNotSupported + } - mutating func encode(_ value: UInt16, forKey key: Key) throws { - throw CustomStringConvertibleEncoder.EncoderError.containersNotSupported - } + mutating func encode(_ value: Int) throws { + throw CustomStringConvertibleEncoder.EncoderError.containersNotSupported + } - mutating func encode(_ value: UInt32, forKey key: Key) throws { - throw CustomStringConvertibleEncoder.EncoderError.containersNotSupported - } + mutating func encode(_ value: Int8) throws { + throw CustomStringConvertibleEncoder.EncoderError.containersNotSupported + } - mutating func encode(_ value: UInt64, forKey key: Key) throws { - throw CustomStringConvertibleEncoder.EncoderError.containersNotSupported - } + mutating func encode(_ value: Int16) throws { + throw CustomStringConvertibleEncoder.EncoderError.containersNotSupported + } - mutating func encode(_ value: T, forKey key: Key) throws where T: Encodable { - throw CustomStringConvertibleEncoder.EncoderError.containersNotSupported - } + mutating func encode(_ value: Int32) throws { + throw CustomStringConvertibleEncoder.EncoderError.containersNotSupported + } - mutating func nestedContainer( - keyedBy keyType: NestedKey.Type, - forKey key: Key - ) -> KeyedEncodingContainer where NestedKey: CodingKey { - encoder.container(keyedBy: NestedKey.self) - } + mutating func encode(_ value: Int64) throws { + throw CustomStringConvertibleEncoder.EncoderError.containersNotSupported + } - mutating func nestedUnkeyedContainer(forKey key: Key) -> any UnkeyedEncodingContainer { - encoder.unkeyedContainer() - } + mutating func encode(_ value: UInt) throws { + throw CustomStringConvertibleEncoder.EncoderError.containersNotSupported + } - mutating func superEncoder(forKey key: Key) -> any Encoder { - encoder - } + mutating func encode(_ value: UInt8) throws { + throw CustomStringConvertibleEncoder.EncoderError.containersNotSupported } - struct UnkeyedContainer: UnkeyedEncodingContainer { + mutating func encode(_ value: UInt16) throws { + throw CustomStringConvertibleEncoder.EncoderError.containersNotSupported + } - let encoder: CustomStringConvertibleEncoder + mutating func encode(_ value: UInt32) throws { + throw CustomStringConvertibleEncoder.EncoderError.containersNotSupported + } - var codingPath: [any CodingKey] { - [] - } + mutating func encode(_ value: UInt64) throws { + throw CustomStringConvertibleEncoder.EncoderError.containersNotSupported + } - var count: Int { - 0 - } + mutating func encode(_ value: T) throws where T: Encodable { + throw CustomStringConvertibleEncoder.EncoderError.containersNotSupported + } - mutating func encodeNil() throws { - throw CustomStringConvertibleEncoder.EncoderError.containersNotSupported - } + mutating func nestedContainer(keyedBy keyType: NestedKey.Type) -> KeyedEncodingContainer + where NestedKey: CodingKey { + encoder.container(keyedBy: NestedKey.self) + } - mutating func encode(_ value: Bool) throws { - throw CustomStringConvertibleEncoder.EncoderError.containersNotSupported - } + mutating func nestedUnkeyedContainer() -> any UnkeyedEncodingContainer { + encoder.unkeyedContainer() + } - mutating func encode(_ value: String) throws { - throw CustomStringConvertibleEncoder.EncoderError.containersNotSupported - } + mutating func superEncoder() -> any Encoder { + encoder + } +} - mutating func encode(_ value: Double) throws { - throw CustomStringConvertibleEncoder.EncoderError.containersNotSupported - } +extension CustomStringConvertibleEncoder.KeyedContainer: KeyedEncodingContainerProtocol { - mutating func encode(_ value: Float) throws { - throw CustomStringConvertibleEncoder.EncoderError.containersNotSupported - } + var codingPath: [any CodingKey] { + [] + } - mutating func encode(_ value: Int) throws { - throw CustomStringConvertibleEncoder.EncoderError.containersNotSupported - } + mutating func superEncoder() -> any Encoder { + encoder + } - mutating func encode(_ value: Int8) throws { - throw CustomStringConvertibleEncoder.EncoderError.containersNotSupported - } + mutating func encodeNil(forKey key: Key) throws { + throw CustomStringConvertibleEncoder.EncoderError.containersNotSupported + } - mutating func encode(_ value: Int16) throws { - throw CustomStringConvertibleEncoder.EncoderError.containersNotSupported - } + mutating func encode(_ value: Bool, forKey key: Key) throws { + throw CustomStringConvertibleEncoder.EncoderError.containersNotSupported + } - mutating func encode(_ value: Int32) throws { - throw CustomStringConvertibleEncoder.EncoderError.containersNotSupported - } + mutating func encode(_ value: String, forKey key: Key) throws { + throw CustomStringConvertibleEncoder.EncoderError.containersNotSupported + } - mutating func encode(_ value: Int64) throws { - throw CustomStringConvertibleEncoder.EncoderError.containersNotSupported - } + mutating func encode(_ value: Double, forKey key: Key) throws { + throw CustomStringConvertibleEncoder.EncoderError.containersNotSupported + } - mutating func encode(_ value: UInt) throws { - throw CustomStringConvertibleEncoder.EncoderError.containersNotSupported - } + mutating func encode(_ value: Float, forKey key: Key) throws { + throw CustomStringConvertibleEncoder.EncoderError.containersNotSupported + } - mutating func encode(_ value: UInt8) throws { - throw CustomStringConvertibleEncoder.EncoderError.containersNotSupported - } + mutating func encode(_ value: Int, forKey key: Key) throws { + throw CustomStringConvertibleEncoder.EncoderError.containersNotSupported + } - mutating func encode(_ value: UInt16) throws { - throw CustomStringConvertibleEncoder.EncoderError.containersNotSupported - } + mutating func encode(_ value: Int8, forKey key: Key) throws { + throw CustomStringConvertibleEncoder.EncoderError.containersNotSupported + } - mutating func encode(_ value: UInt32) throws { - throw CustomStringConvertibleEncoder.EncoderError.containersNotSupported - } + mutating func encode(_ value: Int16, forKey key: Key) throws { + throw CustomStringConvertibleEncoder.EncoderError.containersNotSupported + } - mutating func encode(_ value: UInt64) throws { - throw CustomStringConvertibleEncoder.EncoderError.containersNotSupported - } + mutating func encode(_ value: Int32, forKey key: Key) throws { + throw CustomStringConvertibleEncoder.EncoderError.containersNotSupported + } - mutating func encode(_ value: T) throws where T: Encodable { - throw CustomStringConvertibleEncoder.EncoderError.containersNotSupported - } + mutating func encode(_ value: Int64, forKey key: Key) throws { + throw CustomStringConvertibleEncoder.EncoderError.containersNotSupported + } - mutating func nestedContainer(keyedBy keyType: NestedKey.Type) -> KeyedEncodingContainer - where NestedKey: CodingKey { - encoder.container(keyedBy: NestedKey.self) - } + mutating func encode(_ value: UInt, forKey key: Key) throws { + throw CustomStringConvertibleEncoder.EncoderError.containersNotSupported + } - mutating func nestedUnkeyedContainer() -> any UnkeyedEncodingContainer { - encoder.unkeyedContainer() - } + mutating func encode(_ value: UInt8, forKey key: Key) throws { + throw CustomStringConvertibleEncoder.EncoderError.containersNotSupported + } - mutating func superEncoder() -> any Encoder { - encoder - } + mutating func encode(_ value: UInt16, forKey key: Key) throws { + throw CustomStringConvertibleEncoder.EncoderError.containersNotSupported + } + + mutating func encode(_ value: UInt32, forKey key: Key) throws { + throw CustomStringConvertibleEncoder.EncoderError.containersNotSupported + } + + mutating func encode(_ value: UInt64, forKey key: Key) throws { + throw CustomStringConvertibleEncoder.EncoderError.containersNotSupported + } + + mutating func encode(_ value: T, forKey key: Key) throws where T: Encodable { + throw CustomStringConvertibleEncoder.EncoderError.containersNotSupported + } + + mutating func nestedContainer( + keyedBy keyType: NestedKey.Type, + forKey key: Key + ) -> KeyedEncodingContainer where NestedKey: CodingKey { + encoder.container(keyedBy: NestedKey.self) + } + + mutating func nestedUnkeyedContainer(forKey key: Key) -> any UnkeyedEncodingContainer { + encoder.unkeyedContainer() + } + + mutating func superEncoder(forKey key: Key) -> any Encoder { + encoder } } From e94d59c91d7895f4d6c7b67c7bc7c21079a13043 Mon Sep 17 00:00:00 2001 From: Honza Dvorsky Date: Mon, 28 Aug 2023 13:58:41 +0200 Subject: [PATCH 11/11] Updated unit tests --- .../Conversion/CurrencyExtensions.swift | 25 +- .../Conversion/Test_Converter+Client.swift | 91 ++- .../Conversion/Test_Converter+Common.swift | 116 ++-- .../Conversion/Test_Converter+Server.swift | 182 ++---- .../Deprecated/Test_Deprecated.swift | 617 ++++++++++++++++++ Tests/OpenAPIRuntimeTests/Test_Runtime.swift | 22 + 6 files changed, 826 insertions(+), 227 deletions(-) diff --git a/Sources/OpenAPIRuntime/Conversion/CurrencyExtensions.swift b/Sources/OpenAPIRuntime/Conversion/CurrencyExtensions.swift index c9234ca8..5b444431 100644 --- a/Sources/OpenAPIRuntime/Conversion/CurrencyExtensions.swift +++ b/Sources/OpenAPIRuntime/Conversion/CurrencyExtensions.swift @@ -257,13 +257,29 @@ extension Converter { ) } + func getHeaderFieldValuesString( + in headerFields: [HeaderField], + name: String + ) -> String? { + let values = headerFields.values(name: name) + guard !values.isEmpty else { + return nil + } + return values.joined(separator: ",") + } + func getOptionalHeaderField( in headerFields: [HeaderField], name: String, as type: T.Type, convert: (String) throws -> T ) throws -> T? { - guard let stringValue = headerFields.firstValue(name: name) else { + guard + let stringValue = getHeaderFieldValuesString( + in: headerFields, + name: name + ) + else { return nil } return try convert(stringValue) @@ -275,7 +291,12 @@ extension Converter { as type: T.Type, convert: (String) throws -> T ) throws -> T { - guard let stringValue = headerFields.firstValue(name: name) else { + guard + let stringValue = getHeaderFieldValuesString( + in: headerFields, + name: name + ) + else { throw RuntimeError.missingRequiredHeaderField(name) } return try convert(stringValue) diff --git a/Tests/OpenAPIRuntimeTests/Conversion/Test_Converter+Client.swift b/Tests/OpenAPIRuntimeTests/Conversion/Test_Converter+Client.swift index 370d44e9..11c44cbf 100644 --- a/Tests/OpenAPIRuntimeTests/Conversion/Test_Converter+Client.swift +++ b/Tests/OpenAPIRuntimeTests/Conversion/Test_Converter+Client.swift @@ -32,22 +32,23 @@ final class Test_ClientConverterExtensions: Test_Runtime { // MARK: Converter helper methods - // | client | set | request path | text | string-convertible | required | renderedRequestPath | - func test_renderedRequestPath_stringConvertible() throws { - let renderedPath = try converter.renderedRequestPath( - template: "/items/{}/detail/{}", + // | client | set | request path | URI | required | renderedPath | + func test_renderedPath_string() throws { + let renderedPath = try converter.renderedPath( + template: "/items/{}/detail/{}/habitats/{}", parameters: [ 1 as Int, "foo" as String, + [.land, .air] as [TestHabitat], ] ) - XCTAssertEqual(renderedPath, "/items/1/detail/foo") + XCTAssertEqual(renderedPath, "/items/1/detail/foo/habitats/land,air") } - // | client | set | request query | text | string-convertible | both | setQueryItemAsText | - func test_setQueryItemAsText_stringConvertible() throws { + // | client | set | request query | URI | both | setQueryItemAsURI | + func test_setQueryItemAsURI_string() throws { var request = testRequest - try converter.setQueryItemAsText( + try converter.setQueryItemAsURI( in: &request, style: nil, explode: nil, @@ -57,9 +58,9 @@ final class Test_ClientConverterExtensions: Test_Runtime { XCTAssertEqual(request.query, "search=foo") } - func test_setQueryItemAsText_stringConvertible_needsEncoding() throws { + func test_setQueryItemAsURI_stringConvertible_needsEncoding() throws { var request = testRequest - try converter.setQueryItemAsText( + try converter.setQueryItemAsURI( in: &request, style: nil, explode: nil, @@ -69,10 +70,9 @@ final class Test_ClientConverterExtensions: Test_Runtime { XCTAssertEqual(request.query, "search=h%25llo") } - // | client | set | request query | text | array of string-convertibles | both | setQueryItemAsText | - func test_setQueryItemAsText_arrayOfStringConvertibles() throws { + func test_setQueryItemAsURI_arrayOfStrings() throws { var request = testRequest - try converter.setQueryItemAsText( + try converter.setQueryItemAsURI( in: &request, style: nil, explode: nil, @@ -82,10 +82,9 @@ final class Test_ClientConverterExtensions: Test_Runtime { XCTAssertEqual(request.query, "search=foo&search=bar") } - // | client | set | request query | text | array of string-convertibles | both | setQueryItemAsText | - func test_setQueryItemAsText_arrayOfStringConvertibles_unexploded() throws { + func test_setQueryItemAsURI_arrayOfStrings_unexploded() throws { var request = testRequest - try converter.setQueryItemAsText( + try converter.setQueryItemAsURI( in: &request, style: nil, explode: false, @@ -95,36 +94,34 @@ final class Test_ClientConverterExtensions: Test_Runtime { XCTAssertEqual(request.query, "search=foo,bar") } - // | client | set | request query | text | date | both | setQueryItemAsText | - func test_setQueryItemAsText_date() throws { + func test_setQueryItemAsURI_date() throws { var request = testRequest - try converter.setQueryItemAsText( + try converter.setQueryItemAsURI( in: &request, style: nil, explode: nil, name: "search", value: testDate ) - XCTAssertEqual(request.query, "search=2023-01-18T10:04:11Z") + XCTAssertEqual(request.query, "search=2023-01-18T10%3A04%3A11Z") } - // | client | set | request query | text | array of dates | both | setQueryItemAsText | - func test_setQueryItemAsText_arrayOfDates() throws { + func test_setQueryItemAsURI_arrayOfDates() throws { var request = testRequest - try converter.setQueryItemAsText( + try converter.setQueryItemAsURI( in: &request, style: nil, explode: nil, name: "search", value: [testDate, testDate] ) - XCTAssertEqual(request.query, "search=2023-01-18T10:04:11Z&search=2023-01-18T10:04:11Z") + XCTAssertEqual(request.query, "search=2023-01-18T10%3A04%3A11Z&search=2023-01-18T10%3A04%3A11Z") } - // | client | set | request body | text | string-convertible | optional | setOptionalRequestBodyAsText | - func test_setOptionalRequestBodyAsText_stringConvertible() throws { + // | client | set | request body | string | optional | setOptionalRequestBodyAsString | + func test_setOptionalRequestBodyAsString_string() throws { var headerFields: [HeaderField] = [] - let body = try converter.setOptionalRequestBodyAsText( + let body = try converter.setOptionalRequestBodyAsString( testString, headerFields: &headerFields, contentType: "text/plain" @@ -138,10 +135,10 @@ final class Test_ClientConverterExtensions: Test_Runtime { ) } - // | client | set | request body | text | string-convertible | required | setRequiredRequestBodyAsText | - func test_setRequiredRequestBodyAsText_stringConvertible() throws { + // | client | set | request body | string | required | setRequiredRequestBodyAsString | + func test_setRequiredRequestBodyAsString_string() throws { var headerFields: [HeaderField] = [] - let body = try converter.setRequiredRequestBodyAsText( + let body = try converter.setRequiredRequestBodyAsString( testString, headerFields: &headerFields, contentType: "text/plain" @@ -155,10 +152,9 @@ final class Test_ClientConverterExtensions: Test_Runtime { ) } - // | client | set | request body | text | date | optional | setOptionalRequestBodyAsText | - func test_setOptionalRequestBodyAsText_date() throws { + func test_setOptionalRequestBodyAsString_date() throws { var headerFields: [HeaderField] = [] - let body = try converter.setOptionalRequestBodyAsText( + let body = try converter.setOptionalRequestBodyAsString( testDate, headerFields: &headerFields, contentType: "text/plain" @@ -172,10 +168,9 @@ final class Test_ClientConverterExtensions: Test_Runtime { ) } - // | client | set | request body | text | date | required | setRequiredRequestBodyAsText | - func test_setRequiredRequestBodyAsText_date() throws { + func test_setRequiredRequestBodyAsString_date() throws { var headerFields: [HeaderField] = [] - let body = try converter.setRequiredRequestBodyAsText( + let body = try converter.setRequiredRequestBodyAsString( testDate, headerFields: &headerFields, contentType: "text/plain" @@ -189,7 +184,7 @@ final class Test_ClientConverterExtensions: Test_Runtime { ) } - // | client | set | request body | JSON | codable | optional | setOptionalRequestBodyAsJSON | + // | client | set | request body | JSON | optional | setOptionalRequestBodyAsJSON | func test_setOptionalRequestBodyAsJSON_codable() throws { var headerFields: [HeaderField] = [] let body = try converter.setOptionalRequestBodyAsJSON( @@ -222,7 +217,7 @@ final class Test_ClientConverterExtensions: Test_Runtime { ) } - // | client | set | request body | JSON | codable | required | setRequiredRequestBodyAsJSON | + // | client | set | request body | JSON | required | setRequiredRequestBodyAsJSON | func test_setRequiredRequestBodyAsJSON_codable() throws { var headerFields: [HeaderField] = [] let body = try converter.setRequiredRequestBodyAsJSON( @@ -239,7 +234,7 @@ final class Test_ClientConverterExtensions: Test_Runtime { ) } - // | client | set | request body | binary | data | optional | setOptionalRequestBodyAsBinary | + // | client | set | request body | binary | optional | setOptionalRequestBodyAsBinary | func test_setOptionalRequestBodyAsBinary_data() throws { var headerFields: [HeaderField] = [] let body = try converter.setOptionalRequestBodyAsBinary( @@ -256,7 +251,7 @@ final class Test_ClientConverterExtensions: Test_Runtime { ) } - // | client | set | request body | binary | data | required | setRequiredRequestBodyAsBinary | + // | client | set | request body | binary | required | setRequiredRequestBodyAsBinary | func test_setRequiredRequestBodyAsBinary_data() throws { var headerFields: [HeaderField] = [] let body = try converter.setRequiredRequestBodyAsBinary( @@ -273,9 +268,9 @@ final class Test_ClientConverterExtensions: Test_Runtime { ) } - // | client | get | response body | text | string-convertible | required | getResponseBodyAsText | - func test_getResponseBodyAsText_stringConvertible() throws { - let value: String = try converter.getResponseBodyAsText( + // | client | get | response body | string | required | getResponseBodyAsString | + func test_getResponseBodyAsString_stringConvertible() throws { + let value: String = try converter.getResponseBodyAsString( String.self, from: testStringData, transforming: { $0 } @@ -283,9 +278,9 @@ final class Test_ClientConverterExtensions: Test_Runtime { XCTAssertEqual(value, testString) } - // | client | get | response body | text | date | required | getResponseBodyAsText | - func test_getResponseBodyAsText_date() throws { - let value: Date = try converter.getResponseBodyAsText( + // | client | get | response body | string | required | getResponseBodyAsString | + func test_getResponseBodyAsString_date() throws { + let value: Date = try converter.getResponseBodyAsString( Date.self, from: testDateStringData, transforming: { $0 } @@ -293,7 +288,7 @@ final class Test_ClientConverterExtensions: Test_Runtime { XCTAssertEqual(value, testDate) } - // | client | get | response body | JSON | codable | required | getResponseBodyAsJSON | + // | client | get | response body | JSON | required | getResponseBodyAsJSON | func test_getResponseBodyAsJSON_codable() throws { let value: TestPet = try converter.getResponseBodyAsJSON( TestPet.self, @@ -303,7 +298,7 @@ final class Test_ClientConverterExtensions: Test_Runtime { XCTAssertEqual(value, testStruct) } - // | client | get | response body | binary | data | required | getResponseBodyAsBinary | + // | client | get | response body | binary | required | getResponseBodyAsBinary | func test_getResponseBodyAsBinary_data() throws { let value: Data = try converter.getResponseBodyAsBinary( Data.self, diff --git a/Tests/OpenAPIRuntimeTests/Conversion/Test_Converter+Common.swift b/Tests/OpenAPIRuntimeTests/Conversion/Test_Converter+Common.swift index 27408e60..23c3d1bc 100644 --- a/Tests/OpenAPIRuntimeTests/Conversion/Test_Converter+Common.swift +++ b/Tests/OpenAPIRuntimeTests/Conversion/Test_Converter+Common.swift @@ -49,10 +49,10 @@ final class Test_CommonConverterExtensions: Test_Runtime { // MARK: Converter helper methods - // | common | set | header field | text | string-convertible | both | setHeaderFieldAsText | - func test_setHeaderFieldAsText_stringConvertible() throws { + // | common | set | header field | URI | both | setHeaderFieldAsURI | + func test_setHeaderFieldAsURI_string() throws { var headerFields: [HeaderField] = [] - try converter.setHeaderFieldAsText( + try converter.setHeaderFieldAsURI( in: &headerFields, name: "foo", value: "bar" @@ -65,10 +65,9 @@ final class Test_CommonConverterExtensions: Test_Runtime { ) } - // | common | set | header field | text | array of string-convertibles | both | setHeaderFieldAsText | - func test_setHeaderFieldAsText_arrayOfStringConvertible() throws { + func test_setHeaderFieldAsURI_arrayOfStrings() throws { var headerFields: [HeaderField] = [] - try converter.setHeaderFieldAsText( + try converter.setHeaderFieldAsURI( in: &headerFields, name: "foo", value: ["bar", "baz"] as [String] @@ -76,16 +75,14 @@ final class Test_CommonConverterExtensions: Test_Runtime { XCTAssertEqual( headerFields, [ - .init(name: "foo", value: "bar"), - .init(name: "foo", value: "baz"), + .init(name: "foo", value: "bar,baz") ] ) } - // | common | set | header field | text | date | both | setHeaderFieldAsText | - func test_setHeaderFieldAsText_date() throws { + func test_setHeaderFieldAsURI_date() throws { var headerFields: [HeaderField] = [] - try converter.setHeaderFieldAsText( + try converter.setHeaderFieldAsURI( in: &headerFields, name: "foo", value: testDate @@ -93,15 +90,14 @@ final class Test_CommonConverterExtensions: Test_Runtime { XCTAssertEqual( headerFields, [ - .init(name: "foo", value: testDateString) + .init(name: "foo", value: testDateEscapedString) ] ) } - // | common | set | header field | text | array of dates | both | setHeaderFieldAsText | - func test_setHeaderFieldAsText_arrayOfDates() throws { + func test_setHeaderFieldAsURI_arrayOfDates() throws { var headerFields: [HeaderField] = [] - try converter.setHeaderFieldAsText( + try converter.setHeaderFieldAsURI( in: &headerFields, name: "foo", value: [testDate, testDate] @@ -109,13 +105,27 @@ final class Test_CommonConverterExtensions: Test_Runtime { XCTAssertEqual( headerFields, [ - .init(name: "foo", value: testDateString), - .init(name: "foo", value: testDateString), + .init(name: "foo", value: "\(testDateEscapedString),\(testDateEscapedString)") ] ) } - // | common | set | header field | JSON | codable | both | setHeaderFieldAsJSON | + func test_setHeaderFieldAsURI_struct() throws { + var headerFields: [HeaderField] = [] + try converter.setHeaderFieldAsURI( + in: &headerFields, + name: "foo", + value: testStruct + ) + XCTAssertEqual( + headerFields, + [ + .init(name: "foo", value: "name,Fluffz") + ] + ) + } + + // | common | set | header field | JSON | both | setHeaderFieldAsJSON | func test_setHeaderFieldAsJSON_codable() throws { var headerFields: [HeaderField] = [] try converter.setHeaderFieldAsJSON( @@ -146,12 +156,12 @@ final class Test_CommonConverterExtensions: Test_Runtime { ) } - // | common | get | header field | text | string-convertible | optional | getOptionalHeaderFieldAsText | - func test_getOptionalHeaderFieldAsText_stringConvertible() throws { + // | common | get | header field | URI | optional | getOptionalHeaderFieldAsURI | + func test_getOptionalHeaderFieldAsURI_string() throws { let headerFields: [HeaderField] = [ .init(name: "foo", value: "bar") ] - let value: String? = try converter.getOptionalHeaderFieldAsText( + let value: String? = try converter.getOptionalHeaderFieldAsURI( in: headerFields, name: "foo", as: String.self @@ -159,12 +169,12 @@ final class Test_CommonConverterExtensions: Test_Runtime { XCTAssertEqual(value, "bar") } - // | common | get | header field | text | string-convertible | required | getRequiredHeaderFieldAsText | - func test_getRequiredHeaderFieldAsText_stringConvertible() throws { + // | common | get | header field | URI | required | getRequiredHeaderFieldAsURI | + func test_getRequiredHeaderFieldAsURI_stringConvertible() throws { let headerFields: [HeaderField] = [ .init(name: "foo", value: "bar") ] - let value: String = try converter.getRequiredHeaderFieldAsText( + let value: String = try converter.getRequiredHeaderFieldAsURI( in: headerFields, name: "foo", as: String.self @@ -172,13 +182,12 @@ final class Test_CommonConverterExtensions: Test_Runtime { XCTAssertEqual(value, "bar") } - // | common | get | header field | text | array of string-convertibles | optional | getOptionalHeaderFieldAsText | - func test_getOptionalHeaderFieldAsText_arrayOfStringConvertibles() throws { + func test_getOptionalHeaderFieldAsURI_arrayOfStrings_splitHeaders() throws { let headerFields: [HeaderField] = [ .init(name: "foo", value: "bar"), .init(name: "foo", value: "baz"), ] - let value: [String]? = try converter.getOptionalHeaderFieldAsText( + let value: [String]? = try converter.getOptionalHeaderFieldAsURI( in: headerFields, name: "foo", as: [String].self @@ -186,13 +195,11 @@ final class Test_CommonConverterExtensions: Test_Runtime { XCTAssertEqual(value, ["bar", "baz"]) } - // | common | get | header field | text | array of string-convertibles | required | getRequiredHeaderFieldAsText | - func test_getRequiredHeaderFieldAsText_arrayOfStringConvertibles() throws { + func test_getOptionalHeaderFieldAsURI_arrayOfStrings_singleHeader() throws { let headerFields: [HeaderField] = [ - .init(name: "foo", value: "bar"), - .init(name: "foo", value: "baz"), + .init(name: "foo", value: "bar,baz") ] - let value: [String] = try converter.getRequiredHeaderFieldAsText( + let value: [String]? = try converter.getOptionalHeaderFieldAsURI( in: headerFields, name: "foo", as: [String].self @@ -200,12 +207,11 @@ final class Test_CommonConverterExtensions: Test_Runtime { XCTAssertEqual(value, ["bar", "baz"]) } - // | common | get | header field | text | date | optional | getOptionalHeaderFieldAsText | - func test_getOptionalHeaderFieldAsText_date() throws { + func test_getOptionalHeaderFieldAsURI_date() throws { let headerFields: [HeaderField] = [ - .init(name: "foo", value: testDateString) + .init(name: "foo", value: testDateEscapedString) ] - let value: Date? = try converter.getOptionalHeaderFieldAsText( + let value: Date? = try converter.getOptionalHeaderFieldAsURI( in: headerFields, name: "foo", as: Date.self @@ -213,26 +219,12 @@ final class Test_CommonConverterExtensions: Test_Runtime { XCTAssertEqual(value, testDate) } - // | common | get | header field | text | date | required | getRequiredHeaderFieldAsText | - func test_getRequiredHeaderFieldAsText_date() throws { + func test_getRequiredHeaderFieldAsURI_arrayOfDates() throws { let headerFields: [HeaderField] = [ - .init(name: "foo", value: testDateString) + .init(name: "foo", value: testDateString), // escaped + .init(name: "foo", value: testDateEscapedString), // unescaped ] - let value: Date = try converter.getRequiredHeaderFieldAsText( - in: headerFields, - name: "foo", - as: Date.self - ) - XCTAssertEqual(value, testDate) - } - - // | common | get | header field | text | array of dates | optional | getOptionalHeaderFieldAsText | - func test_getOptionalHeaderFieldAsText_arrayOfDates() throws { - let headerFields: [HeaderField] = [ - .init(name: "foo", value: testDateString), - .init(name: "foo", value: testDateString), - ] - let value: [Date]? = try converter.getOptionalHeaderFieldAsText( + let value: [Date] = try converter.getRequiredHeaderFieldAsURI( in: headerFields, name: "foo", as: [Date].self @@ -240,21 +232,19 @@ final class Test_CommonConverterExtensions: Test_Runtime { XCTAssertEqual(value, [testDate, testDate]) } - // | common | get | header field | text | array of dates | required | getRequiredHeaderFieldAsText | - func test_getRequiredHeaderFieldAsText_arrayOfDates() throws { + func test_getOptionalHeaderFieldAsURI_struct() throws { let headerFields: [HeaderField] = [ - .init(name: "foo", value: testDateString), - .init(name: "foo", value: testDateString), + .init(name: "foo", value: "name,Sprinkles") ] - let value: [Date] = try converter.getRequiredHeaderFieldAsText( + let value: TestPet? = try converter.getOptionalHeaderFieldAsURI( in: headerFields, name: "foo", - as: [Date].self + as: TestPet.self ) - XCTAssertEqual(value, [testDate, testDate]) + XCTAssertEqual(value, .init(name: "Sprinkles")) } - // | common | get | header field | JSON | codable | optional | getOptionalHeaderFieldAsJSON | + // | common | get | header field | JSON | optional | getOptionalHeaderFieldAsJSON | func test_getOptionalHeaderFieldAsJSON_codable() throws { let headerFields: [HeaderField] = [ .init(name: "foo", value: testStructString) @@ -267,7 +257,7 @@ final class Test_CommonConverterExtensions: Test_Runtime { XCTAssertEqual(value, testStruct) } - // | common | get | header field | JSON | codable | required | getRequiredHeaderFieldAsJSON | + // | common | get | header field | JSON | required | getRequiredHeaderFieldAsJSON | func test_getRequiredHeaderFieldAsJSON_codable() throws { let headerFields: [HeaderField] = [ .init(name: "foo", value: testStructString) diff --git a/Tests/OpenAPIRuntimeTests/Conversion/Test_Converter+Server.swift b/Tests/OpenAPIRuntimeTests/Conversion/Test_Converter+Server.swift index 8cd8bc5e..713a2232 100644 --- a/Tests/OpenAPIRuntimeTests/Conversion/Test_Converter+Server.swift +++ b/Tests/OpenAPIRuntimeTests/Conversion/Test_Converter+Server.swift @@ -119,26 +119,43 @@ final class Test_ServerConverterExtensions: Test_Runtime { // MARK: Converter helper methods - // | server | get | request path | text | string-convertible | required | getPathParameterAsText | - func test_getPathParameterAsText_stringConvertible() throws { + // | server | get | request path | URI | required | getPathParameterAsURI | + func test_getPathParameterAsURI_various() throws { let path: [String: String] = [ - "foo": "bar" + "foo": "bar", + "number": "1", + "habitats": "land,air", ] - let value: String = try converter.getPathParameterAsText( - in: path, - name: "foo", - as: String.self - ) - XCTAssertEqual(value, "bar") + do { + let value = try converter.getPathParameterAsURI( + in: path, + name: "foo", + as: String.self + ) + XCTAssertEqual(value, "bar") + } + do { + let value = try converter.getPathParameterAsURI( + in: path, + name: "number", + as: Int.self + ) + XCTAssertEqual(value, 1) + } + do { + let value = try converter.getPathParameterAsURI( + in: path, + name: "habitats", + as: [TestHabitat].self + ) + XCTAssertEqual(value, [.land, .air]) + } } - // | server | get | request query | text | string-convertible | optional | getOptionalQueryItemAsText | - func test_getOptionalQueryItemAsText_stringConvertible() throws { - let query: [URLQueryItem] = [ - .init(name: "search", value: "foo") - ] - let value: String? = try converter.getOptionalQueryItemAsText( - in: query, + // | server | get | request query | URI | optional | getOptionalQueryItemAsURI | + func test_getOptionalQueryItemAsURI_string() throws { + let value: String? = try converter.getOptionalQueryItemAsURI( + in: "search=foo", style: nil, explode: nil, name: "search", @@ -147,13 +164,10 @@ final class Test_ServerConverterExtensions: Test_Runtime { XCTAssertEqual(value, "foo") } - // | server | get | request query | text | string-convertible | required | getRequiredQueryItemAsText | - func test_getRequiredQueryItemAsText_stringConvertible() throws { - let query: [URLQueryItem] = [ - .init(name: "search", value: "foo") - ] - let value: String = try converter.getRequiredQueryItemAsText( - in: query, + // | server | get | request query | URI | required | getRequiredQueryItemAsURI | + func test_getRequiredQueryItemAsURI_string() throws { + let value: String = try converter.getRequiredQueryItemAsURI( + in: "search=foo", style: nil, explode: nil, name: "search", @@ -162,13 +176,9 @@ final class Test_ServerConverterExtensions: Test_Runtime { XCTAssertEqual(value, "foo") } - // | server | get | request query | text | array of string-convertibles | optional | getOptionalQueryItemAsText | - func test_getOptionalQueryItemAsText_arrayOfStringConvertibles() throws { - let query: [URLQueryItem] = [ - .init(name: "search", value: "foo"), - .init(name: "search", value: "bar"), - ] - let value: [String]? = try converter.getOptionalQueryItemAsText( + func test_getOptionalQueryItemAsURI_arrayOfStrings() throws { + let query = "search=foo&search=bar" + let value: [String]? = try converter.getOptionalQueryItemAsURI( in: query, style: nil, explode: nil, @@ -178,13 +188,9 @@ final class Test_ServerConverterExtensions: Test_Runtime { XCTAssertEqual(value, ["foo", "bar"]) } - // | server | get | request query | text | array of string-convertibles | required | getRequiredQueryItemAsText | - func test_getRequiredQueryItemAsText_arrayOfStringConvertibles() throws { - let query: [URLQueryItem] = [ - .init(name: "search", value: "foo"), - .init(name: "search", value: "bar"), - ] - let value: [String] = try converter.getRequiredQueryItemAsText( + func test_getRequiredQueryItemAsURI_arrayOfStrings() throws { + let query = "search=foo&search=bar" + let value: [String] = try converter.getRequiredQueryItemAsURI( in: query, style: nil, explode: nil, @@ -194,12 +200,9 @@ final class Test_ServerConverterExtensions: Test_Runtime { XCTAssertEqual(value, ["foo", "bar"]) } - // | server | get | request query | text | array of string-convertibles | required | getRequiredQueryItemAsText | - func test_getRequiredQueryItemAsText_arrayOfStringConvertibles_unexploded() throws { - let query: [URLQueryItem] = [ - .init(name: "search", value: "foo,bar") - ] - let value: [String] = try converter.getRequiredQueryItemAsText( + func test_getRequiredQueryItemAsURI_arrayOfStrings_unexploded() throws { + let query = "search=foo,bar" + let value: [String] = try converter.getRequiredQueryItemAsURI( in: query, style: nil, explode: false, @@ -209,27 +212,9 @@ final class Test_ServerConverterExtensions: Test_Runtime { XCTAssertEqual(value, ["foo", "bar"]) } - // | server | get | request query | text | date | optional | getOptionalQueryItemAsText | - func test_getOptionalQueryItemAsText_date() throws { - let query: [URLQueryItem] = [ - .init(name: "search", value: testDateString) - ] - let value: Date? = try converter.getOptionalQueryItemAsText( - in: query, - style: nil, - explode: nil, - name: "search", - as: Date.self - ) - XCTAssertEqual(value, testDate) - } - - // | server | get | request query | text | date | required | getRequiredQueryItemAsText | - func test_getRequiredQueryItemAsText_date() throws { - let query: [URLQueryItem] = [ - .init(name: "search", value: testDateString) - ] - let value: Date = try converter.getRequiredQueryItemAsText( + func test_getOptionalQueryItemAsURI_date() throws { + let query = "search=\(testDateEscapedString)" + let value: Date? = try converter.getOptionalQueryItemAsURI( in: query, style: nil, explode: nil, @@ -239,29 +224,9 @@ final class Test_ServerConverterExtensions: Test_Runtime { XCTAssertEqual(value, testDate) } - // | server | get | request query | text | array of dates | optional | getOptionalQueryItemAsText | - func test_getOptionalQueryItemAsText_arrayOfDates() throws { - let query: [URLQueryItem] = [ - .init(name: "search", value: testDateString), - .init(name: "search", value: testDateString), - ] - let value: [Date]? = try converter.getOptionalQueryItemAsText( - in: query, - style: nil, - explode: nil, - name: "search", - as: [Date].self - ) - XCTAssertEqual(value, [testDate, testDate]) - } - - // | server | get | request query | text | array of dates | required | getRequiredQueryItemAsText | - func test_getRequiredQueryItemAsText_arrayOfDates() throws { - let query: [URLQueryItem] = [ - .init(name: "search", value: testDateString), - .init(name: "search", value: testDateString), - ] - let value: [Date] = try converter.getRequiredQueryItemAsText( + func test_getRequiredQueryItemAsURI_arrayOfDates() throws { + let query = "search=\(testDateEscapedString)&search=\(testDateEscapedString)" + let value: [Date] = try converter.getRequiredQueryItemAsURI( in: query, style: nil, explode: nil, @@ -271,9 +236,9 @@ final class Test_ServerConverterExtensions: Test_Runtime { XCTAssertEqual(value, [testDate, testDate]) } - // | server | get | request body | text | string-convertible | optional | getOptionalRequestBodyAsText | - func test_getOptionalRequestBodyAsText_stringConvertible() throws { - let body: String? = try converter.getOptionalRequestBodyAsText( + // | server | get | request body | string | optional | getOptionalRequestBodyAsString | + func test_getOptionalRequestBodyAsText_string() throws { + let body: String? = try converter.getOptionalRequestBodyAsString( String.self, from: testStringData, transforming: { $0 } @@ -281,9 +246,9 @@ final class Test_ServerConverterExtensions: Test_Runtime { XCTAssertEqual(body, testString) } - // | server | get | request body | text | string-convertible | required | getRequiredRequestBodyAsText | + // | server | get | request body | string | required | getRequiredRequestBodyAsString | func test_getRequiredRequestBodyAsText_stringConvertible() throws { - let body: String = try converter.getRequiredRequestBodyAsText( + let body: String = try converter.getRequiredRequestBodyAsString( String.self, from: testStringData, transforming: { $0 } @@ -291,19 +256,8 @@ final class Test_ServerConverterExtensions: Test_Runtime { XCTAssertEqual(body, testString) } - // | server | get | request body | text | date | optional | getOptionalRequestBodyAsText | - func test_getOptionalRequestBodyAsText_date() throws { - let body: Date? = try converter.getOptionalRequestBodyAsText( - Date.self, - from: testDateStringData, - transforming: { $0 } - ) - XCTAssertEqual(body, testDate) - } - - // | server | get | request body | text | date | required | getRequiredRequestBodyAsText | func test_getRequiredRequestBodyAsText_date() throws { - let body: Date = try converter.getRequiredRequestBodyAsText( + let body: Date = try converter.getRequiredRequestBodyAsString( Date.self, from: testDateStringData, transforming: { $0 } @@ -311,7 +265,7 @@ final class Test_ServerConverterExtensions: Test_Runtime { XCTAssertEqual(body, testDate) } - // | server | get | request body | JSON | codable | optional | getOptionalRequestBodyAsJSON | + // | server | get | request body | JSON | optional | getOptionalRequestBodyAsJSON | func test_getOptionalRequestBodyAsJSON_codable() throws { let body: TestPet? = try converter.getOptionalRequestBodyAsJSON( TestPet.self, @@ -330,7 +284,7 @@ final class Test_ServerConverterExtensions: Test_Runtime { XCTAssertEqual(body, testString) } - // | server | get | request body | JSON | codable | required | getRequiredRequestBodyAsJSON | + // | server | get | request body | JSON | required | getRequiredRequestBodyAsJSON | func test_getRequiredRequestBodyAsJSON_codable() throws { let body: TestPet = try converter.getRequiredRequestBodyAsJSON( TestPet.self, @@ -340,7 +294,7 @@ final class Test_ServerConverterExtensions: Test_Runtime { XCTAssertEqual(body, testStruct) } - // | server | get | request body | binary | data | optional | getOptionalRequestBodyAsBinary | + // | server | get | request body | binary | optional | getOptionalRequestBodyAsBinary | func test_getOptionalRequestBodyAsBinary_data() throws { let body: Data? = try converter.getOptionalRequestBodyAsBinary( Data.self, @@ -350,7 +304,7 @@ final class Test_ServerConverterExtensions: Test_Runtime { XCTAssertEqual(body, testStringData) } - // | server | get | request body | binary | data | required | getRequiredRequestBodyAsBinary | + // | server | get | request body | binary | required | getRequiredRequestBodyAsBinary | func test_getRequiredRequestBodyAsBinary_data() throws { let body: Data = try converter.getRequiredRequestBodyAsBinary( Data.self, @@ -360,10 +314,10 @@ final class Test_ServerConverterExtensions: Test_Runtime { XCTAssertEqual(body, testStringData) } - // | server | set | response body | text | string-convertible | required | setResponseBodyAsText | + // | server | set | response body | string | required | setResponseBodyAsString | func test_setResponseBodyAsText_stringConvertible() throws { var headers: [HeaderField] = [] - let data = try converter.setResponseBodyAsText( + let data = try converter.setResponseBodyAsString( testString, headerFields: &headers, contentType: "text/plain" @@ -377,10 +331,10 @@ final class Test_ServerConverterExtensions: Test_Runtime { ) } - // | server | set | response body | text | date | required | setResponseBodyAsText | + // | server | set | response body | string | required | setResponseBodyAsString | func test_setResponseBodyAsText_date() throws { var headers: [HeaderField] = [] - let data = try converter.setResponseBodyAsText( + let data = try converter.setResponseBodyAsString( testDate, headerFields: &headers, contentType: "text/plain" @@ -394,7 +348,7 @@ final class Test_ServerConverterExtensions: Test_Runtime { ) } - // | server | set | response body | JSON | codable | required | setResponseBodyAsJSON | + // | server | set | response body | JSON | required | setResponseBodyAsJSON | func test_setResponseBodyAsJSON_codable() throws { var headers: [HeaderField] = [] let data = try converter.setResponseBodyAsJSON( @@ -411,7 +365,7 @@ final class Test_ServerConverterExtensions: Test_Runtime { ) } - // | server | set | response body | binary | data | required | setResponseBodyAsBinary | + // | server | set | response body | binary | required | setResponseBodyAsBinary | func test_setResponseBodyAsBinary_data() throws { var headers: [HeaderField] = [] let data = try converter.setResponseBodyAsBinary( diff --git a/Tests/OpenAPIRuntimeTests/Deprecated/Test_Deprecated.swift b/Tests/OpenAPIRuntimeTests/Deprecated/Test_Deprecated.swift index 4faafbd8..7eff20eb 100644 --- a/Tests/OpenAPIRuntimeTests/Deprecated/Test_Deprecated.swift +++ b/Tests/OpenAPIRuntimeTests/Deprecated/Test_Deprecated.swift @@ -375,4 +375,621 @@ final class Test_Deprecated: Test_Runtime { ) } + // | common | set | header field | text | string-convertible | both | setHeaderFieldAsText | + @available(*, deprecated) + func test_setHeaderFieldAsText_stringConvertible() throws { + var headerFields: [HeaderField] = [] + try converter.setHeaderFieldAsText( + in: &headerFields, + name: "foo", + value: "bar" + ) + XCTAssertEqual( + headerFields, + [ + .init(name: "foo", value: "bar") + ] + ) + } + + // | common | set | header field | text | array of string-convertibles | both | setHeaderFieldAsText | + @available(*, deprecated) + func test_setHeaderFieldAsText_arrayOfStringConvertible() throws { + var headerFields: [HeaderField] = [] + try converter.setHeaderFieldAsText( + in: &headerFields, + name: "foo", + value: ["bar", "baz"] as [String] + ) + XCTAssertEqual( + headerFields, + [ + .init(name: "foo", value: "bar"), + .init(name: "foo", value: "baz"), + ] + ) + } + + // | common | set | header field | text | date | both | setHeaderFieldAsText | + @available(*, deprecated) + func test_setHeaderFieldAsText_date() throws { + var headerFields: [HeaderField] = [] + try converter.setHeaderFieldAsText( + in: &headerFields, + name: "foo", + value: testDate + ) + XCTAssertEqual( + headerFields, + [ + .init(name: "foo", value: testDateString) + ] + ) + } + + // | common | set | header field | text | array of dates | both | setHeaderFieldAsText | + @available(*, deprecated) + func test_setHeaderFieldAsText_arrayOfDates() throws { + var headerFields: [HeaderField] = [] + try converter.setHeaderFieldAsText( + in: &headerFields, + name: "foo", + value: [testDate, testDate] + ) + XCTAssertEqual( + headerFields, + [ + .init(name: "foo", value: testDateString), + .init(name: "foo", value: testDateString), + ] + ) + } + + // | common | get | header field | text | string-convertible | optional | getOptionalHeaderFieldAsText | + @available(*, deprecated) + func test_getOptionalHeaderFieldAsText_stringConvertible() throws { + let headerFields: [HeaderField] = [ + .init(name: "foo", value: "bar") + ] + let value: String? = try converter.getOptionalHeaderFieldAsText( + in: headerFields, + name: "foo", + as: String.self + ) + XCTAssertEqual(value, "bar") + } + + // | common | get | header field | text | string-convertible | required | getRequiredHeaderFieldAsText | + @available(*, deprecated) + func test_getRequiredHeaderFieldAsText_stringConvertible() throws { + let headerFields: [HeaderField] = [ + .init(name: "foo", value: "bar") + ] + let value: String = try converter.getRequiredHeaderFieldAsText( + in: headerFields, + name: "foo", + as: String.self + ) + XCTAssertEqual(value, "bar") + } + + // | common | get | header field | text | array of string-convertibles | optional | getOptionalHeaderFieldAsText | + @available(*, deprecated) + func test_getOptionalHeaderFieldAsText_arrayOfStringConvertibles() throws { + let headerFields: [HeaderField] = [ + .init(name: "foo", value: "bar"), + .init(name: "foo", value: "baz"), + ] + let value: [String]? = try converter.getOptionalHeaderFieldAsText( + in: headerFields, + name: "foo", + as: [String].self + ) + XCTAssertEqual(value, ["bar", "baz"]) + } + + // | common | get | header field | text | array of string-convertibles | required | getRequiredHeaderFieldAsText | + @available(*, deprecated) + func test_getRequiredHeaderFieldAsText_arrayOfStringConvertibles() throws { + let headerFields: [HeaderField] = [ + .init(name: "foo", value: "bar"), + .init(name: "foo", value: "baz"), + ] + let value: [String] = try converter.getRequiredHeaderFieldAsText( + in: headerFields, + name: "foo", + as: [String].self + ) + XCTAssertEqual(value, ["bar", "baz"]) + } + + // | common | get | header field | text | date | optional | getOptionalHeaderFieldAsText | + @available(*, deprecated) + func test_getOptionalHeaderFieldAsText_date() throws { + let headerFields: [HeaderField] = [ + .init(name: "foo", value: testDateString) + ] + let value: Date? = try converter.getOptionalHeaderFieldAsText( + in: headerFields, + name: "foo", + as: Date.self + ) + XCTAssertEqual(value, testDate) + } + + // | common | get | header field | text | date | required | getRequiredHeaderFieldAsText | + @available(*, deprecated) + func test_getRequiredHeaderFieldAsText_date() throws { + let headerFields: [HeaderField] = [ + .init(name: "foo", value: testDateString) + ] + let value: Date = try converter.getRequiredHeaderFieldAsText( + in: headerFields, + name: "foo", + as: Date.self + ) + XCTAssertEqual(value, testDate) + } + + // | common | get | header field | text | array of dates | optional | getOptionalHeaderFieldAsText | + @available(*, deprecated) + func test_getOptionalHeaderFieldAsText_arrayOfDates() throws { + let headerFields: [HeaderField] = [ + .init(name: "foo", value: testDateString), + .init(name: "foo", value: testDateString), + ] + let value: [Date]? = try converter.getOptionalHeaderFieldAsText( + in: headerFields, + name: "foo", + as: [Date].self + ) + XCTAssertEqual(value, [testDate, testDate]) + } + + // | common | get | header field | text | array of dates | required | getRequiredHeaderFieldAsText | + @available(*, deprecated) + func test_getRequiredHeaderFieldAsText_arrayOfDates() throws { + let headerFields: [HeaderField] = [ + .init(name: "foo", value: testDateString), + .init(name: "foo", value: testDateString), + ] + let value: [Date] = try converter.getRequiredHeaderFieldAsText( + in: headerFields, + name: "foo", + as: [Date].self + ) + XCTAssertEqual(value, [testDate, testDate]) + } + + // | client | set | request path | text | string-convertible | required | renderedRequestPath | + @available(*, deprecated) + func test_renderedRequestPath_stringConvertible() throws { + let renderedPath = try converter.renderedRequestPath( + template: "/items/{}/detail/{}", + parameters: [ + 1 as Int, + "foo" as String, + ] + ) + XCTAssertEqual(renderedPath, "/items/1/detail/foo") + } + + // | client | set | request query | text | string-convertible | both | setQueryItemAsText | + @available(*, deprecated) + func test_setQueryItemAsText_stringConvertible() throws { + var request = testRequest + try converter.setQueryItemAsText( + in: &request, + style: nil, + explode: nil, + name: "search", + value: "foo" + ) + XCTAssertEqual(request.query, "search=foo") + } + + @available(*, deprecated) + func test_setQueryItemAsText_stringConvertible_needsEncoding() throws { + var request = testRequest + try converter.setQueryItemAsText( + in: &request, + style: nil, + explode: nil, + name: "search", + value: "h%llo" + ) + XCTAssertEqual(request.query, "search=h%25llo") + } + + // | client | set | request query | text | array of string-convertibles | both | setQueryItemAsText | + @available(*, deprecated) + func test_setQueryItemAsText_arrayOfStringConvertibles() throws { + var request = testRequest + try converter.setQueryItemAsText( + in: &request, + style: nil, + explode: nil, + name: "search", + value: ["foo", "bar"] + ) + XCTAssertEqual(request.query, "search=foo&search=bar") + } + + // | client | set | request query | text | array of string-convertibles | both | setQueryItemAsText | + @available(*, deprecated) + func test_setQueryItemAsText_arrayOfStringConvertibles_unexploded() throws { + var request = testRequest + try converter.setQueryItemAsText( + in: &request, + style: nil, + explode: false, + name: "search", + value: ["foo", "bar"] + ) + XCTAssertEqual(request.query, "search=foo,bar") + } + + // | client | set | request query | text | date | both | setQueryItemAsText | + @available(*, deprecated) + func test_setQueryItemAsText_date() throws { + var request = testRequest + try converter.setQueryItemAsText( + in: &request, + style: nil, + explode: nil, + name: "search", + value: testDate + ) + XCTAssertEqual(request.query, "search=2023-01-18T10:04:11Z") + } + + // | client | set | request query | text | array of dates | both | setQueryItemAsText | + @available(*, deprecated) + func test_setQueryItemAsText_arrayOfDates() throws { + var request = testRequest + try converter.setQueryItemAsText( + in: &request, + style: nil, + explode: nil, + name: "search", + value: [testDate, testDate] + ) + XCTAssertEqual(request.query, "search=2023-01-18T10:04:11Z&search=2023-01-18T10:04:11Z") + } + + // | client | set | request body | text | string-convertible | optional | setOptionalRequestBodyAsText | + @available(*, deprecated) + func test_setOptionalRequestBodyAsText_stringConvertible() throws { + var headerFields: [HeaderField] = [] + let body = try converter.setOptionalRequestBodyAsText( + testString, + headerFields: &headerFields, + contentType: "text/plain" + ) + XCTAssertEqual(body, testStringData) + XCTAssertEqual( + headerFields, + [ + .init(name: "content-type", value: "text/plain") + ] + ) + } + + // | client | set | request body | text | string-convertible | required | setRequiredRequestBodyAsText | + @available(*, deprecated) + func test_setRequiredRequestBodyAsText_stringConvertible() throws { + var headerFields: [HeaderField] = [] + let body = try converter.setRequiredRequestBodyAsText( + testString, + headerFields: &headerFields, + contentType: "text/plain" + ) + XCTAssertEqual(body, testStringData) + XCTAssertEqual( + headerFields, + [ + .init(name: "content-type", value: "text/plain") + ] + ) + } + + // | client | set | request body | text | date | optional | setOptionalRequestBodyAsText | + @available(*, deprecated) + func test_setOptionalRequestBodyAsText_date() throws { + var headerFields: [HeaderField] = [] + let body = try converter.setOptionalRequestBodyAsText( + testDate, + headerFields: &headerFields, + contentType: "text/plain" + ) + XCTAssertEqual(body, testDateStringData) + XCTAssertEqual( + headerFields, + [ + .init(name: "content-type", value: "text/plain") + ] + ) + } + + // | client | set | request body | text | date | required | setRequiredRequestBodyAsText | + @available(*, deprecated) + func test_setRequiredRequestBodyAsText_date() throws { + var headerFields: [HeaderField] = [] + let body = try converter.setRequiredRequestBodyAsText( + testDate, + headerFields: &headerFields, + contentType: "text/plain" + ) + XCTAssertEqual(body, testDateStringData) + XCTAssertEqual( + headerFields, + [ + .init(name: "content-type", value: "text/plain") + ] + ) + } + + // | client | get | response body | text | string-convertible | required | getResponseBodyAsText | + @available(*, deprecated) + func test_getResponseBodyAsText_stringConvertible() throws { + let value: String = try converter.getResponseBodyAsText( + String.self, + from: testStringData, + transforming: { $0 } + ) + XCTAssertEqual(value, testString) + } + + // | client | get | response body | text | date | required | getResponseBodyAsText | + @available(*, deprecated) + func test_getResponseBodyAsText_date() throws { + let value: Date = try converter.getResponseBodyAsText( + Date.self, + from: testDateStringData, + transforming: { $0 } + ) + XCTAssertEqual(value, testDate) + } + + // | server | get | request path | text | string-convertible | required | getPathParameterAsText | + @available(*, deprecated) + func test_getPathParameterAsText_stringConvertible() throws { + let path: [String: String] = [ + "foo": "bar" + ] + let value: String = try converter.getPathParameterAsText( + in: path, + name: "foo", + as: String.self + ) + XCTAssertEqual(value, "bar") + } + + // | server | get | request query | text | string-convertible | optional | getOptionalQueryItemAsText | + @available(*, deprecated) + func test_getOptionalQueryItemAsText_stringConvertible() throws { + let query: [URLQueryItem] = [ + .init(name: "search", value: "foo") + ] + let value: String? = try converter.getOptionalQueryItemAsText( + in: query, + style: nil, + explode: nil, + name: "search", + as: String.self + ) + XCTAssertEqual(value, "foo") + } + + // | server | get | request query | text | string-convertible | required | getRequiredQueryItemAsText | + @available(*, deprecated) + func test_getRequiredQueryItemAsText_stringConvertible() throws { + let query: [URLQueryItem] = [ + .init(name: "search", value: "foo") + ] + let value: String = try converter.getRequiredQueryItemAsText( + in: query, + style: nil, + explode: nil, + name: "search", + as: String.self + ) + XCTAssertEqual(value, "foo") + } + + // | server | get | request query | text | array of string-convertibles | optional | getOptionalQueryItemAsText | + @available(*, deprecated) + func test_getOptionalQueryItemAsText_arrayOfStringConvertibles() throws { + let query: [URLQueryItem] = [ + .init(name: "search", value: "foo"), + .init(name: "search", value: "bar"), + ] + let value: [String]? = try converter.getOptionalQueryItemAsText( + in: query, + style: nil, + explode: nil, + name: "search", + as: [String].self + ) + XCTAssertEqual(value, ["foo", "bar"]) + } + + // | server | get | request query | text | array of string-convertibles | required | getRequiredQueryItemAsText | + @available(*, deprecated) + func test_getRequiredQueryItemAsText_arrayOfStringConvertibles() throws { + let query: [URLQueryItem] = [ + .init(name: "search", value: "foo"), + .init(name: "search", value: "bar"), + ] + let value: [String] = try converter.getRequiredQueryItemAsText( + in: query, + style: nil, + explode: nil, + name: "search", + as: [String].self + ) + XCTAssertEqual(value, ["foo", "bar"]) + } + + // | server | get | request query | text | array of string-convertibles | required | getRequiredQueryItemAsText | + @available(*, deprecated) + func test_getRequiredQueryItemAsText_arrayOfStringConvertibles_unexploded() throws { + let query: [URLQueryItem] = [ + .init(name: "search", value: "foo,bar") + ] + let value: [String] = try converter.getRequiredQueryItemAsText( + in: query, + style: nil, + explode: false, + name: "search", + as: [String].self + ) + XCTAssertEqual(value, ["foo", "bar"]) + } + + // | server | get | request query | text | date | optional | getOptionalQueryItemAsText | + @available(*, deprecated) + func test_getOptionalQueryItemAsText_date() throws { + let query: [URLQueryItem] = [ + .init(name: "search", value: testDateString) + ] + let value: Date? = try converter.getOptionalQueryItemAsText( + in: query, + style: nil, + explode: nil, + name: "search", + as: Date.self + ) + XCTAssertEqual(value, testDate) + } + + // | server | get | request query | text | date | required | getRequiredQueryItemAsText | + @available(*, deprecated) + func test_getRequiredQueryItemAsText_date() throws { + let query: [URLQueryItem] = [ + .init(name: "search", value: testDateString) + ] + let value: Date = try converter.getRequiredQueryItemAsText( + in: query, + style: nil, + explode: nil, + name: "search", + as: Date.self + ) + XCTAssertEqual(value, testDate) + } + + // | server | get | request query | text | array of dates | optional | getOptionalQueryItemAsText | + @available(*, deprecated) + func test_getOptionalQueryItemAsText_arrayOfDates() throws { + let query: [URLQueryItem] = [ + .init(name: "search", value: testDateString), + .init(name: "search", value: testDateString), + ] + let value: [Date]? = try converter.getOptionalQueryItemAsText( + in: query, + style: nil, + explode: nil, + name: "search", + as: [Date].self + ) + XCTAssertEqual(value, [testDate, testDate]) + } + + // | server | get | request query | text | array of dates | required | getRequiredQueryItemAsText | + @available(*, deprecated) + func test_getRequiredQueryItemAsText_arrayOfDates() throws { + let query: [URLQueryItem] = [ + .init(name: "search", value: testDateString), + .init(name: "search", value: testDateString), + ] + let value: [Date] = try converter.getRequiredQueryItemAsText( + in: query, + style: nil, + explode: nil, + name: "search", + as: [Date].self + ) + XCTAssertEqual(value, [testDate, testDate]) + } + + // | server | get | request body | text | string-convertible | optional | getOptionalRequestBodyAsText | + @available(*, deprecated) + func test_getOptionalRequestBodyAsText_stringConvertible() throws { + let body: String? = try converter.getOptionalRequestBodyAsText( + String.self, + from: testStringData, + transforming: { $0 } + ) + XCTAssertEqual(body, testString) + } + + // | server | get | request body | text | string-convertible | required | getRequiredRequestBodyAsText | + @available(*, deprecated) + func test_getRequiredRequestBodyAsText_stringConvertible() throws { + let body: String = try converter.getRequiredRequestBodyAsText( + String.self, + from: testStringData, + transforming: { $0 } + ) + XCTAssertEqual(body, testString) + } + + // | server | get | request body | text | date | optional | getOptionalRequestBodyAsText | + @available(*, deprecated) + func test_getOptionalRequestBodyAsText_date() throws { + let body: Date? = try converter.getOptionalRequestBodyAsText( + Date.self, + from: testDateStringData, + transforming: { $0 } + ) + XCTAssertEqual(body, testDate) + } + + // | server | get | request body | text | date | required | getRequiredRequestBodyAsText | + @available(*, deprecated) + func test_getRequiredRequestBodyAsText_date() throws { + let body: Date = try converter.getRequiredRequestBodyAsText( + Date.self, + from: testDateStringData, + transforming: { $0 } + ) + XCTAssertEqual(body, testDate) + } + + // | server | set | response body | text | string-convertible | required | setResponseBodyAsText | + @available(*, deprecated) + func test_setResponseBodyAsText_stringConvertible() throws { + var headers: [HeaderField] = [] + let data = try converter.setResponseBodyAsText( + testString, + headerFields: &headers, + contentType: "text/plain" + ) + XCTAssertEqual(data, testStringData) + XCTAssertEqual( + headers, + [ + .init(name: "content-type", value: "text/plain") + ] + ) + } + + // | server | set | response body | text | date | required | setResponseBodyAsText | + @available(*, deprecated) + func test_setResponseBodyAsText_date() throws { + var headers: [HeaderField] = [] + let data = try converter.setResponseBodyAsText( + testDate, + headerFields: &headers, + contentType: "text/plain" + ) + XCTAssertEqual(data, testDateStringData) + XCTAssertEqual( + headers, + [ + .init(name: "content-type", value: "text/plain") + ] + ) + } } diff --git a/Tests/OpenAPIRuntimeTests/Test_Runtime.swift b/Tests/OpenAPIRuntimeTests/Test_Runtime.swift index 496cca2b..155a45d7 100644 --- a/Tests/OpenAPIRuntimeTests/Test_Runtime.swift +++ b/Tests/OpenAPIRuntimeTests/Test_Runtime.swift @@ -54,10 +54,18 @@ class Test_Runtime: XCTestCase { "2023-01-18T10:04:11Z" } + var testDateEscapedString: String { + "2023-01-18T10%3A04%3A11Z" + } + var testDateStringData: Data { Data(testDateString.utf8) } + var testDateEscapedStringData: Data { + Data(testDateEscapedString.utf8) + } + var testString: String { "hello" } @@ -90,6 +98,14 @@ class Test_Runtime: XCTestCase { """# } + var testEnum: TestHabitat { + .water + } + + var testEnumString: String { + "water" + } + var testStructData: Data { Data(testStructString.utf8) } @@ -124,6 +140,12 @@ struct TestPet: Codable, Equatable { var name: String } +enum TestHabitat: String, Codable, Equatable { + case water + case land + case air +} + /// Injects an authentication header to every request. struct AuthenticationMiddleware: ClientMiddleware {