diff --git a/Sources/OpenAPIRuntime/StringCoder/StringDecoder.swift b/Sources/OpenAPIRuntime/StringCoder/StringDecoder.swift new file mode 100644 index 00000000..22add7bf --- /dev/null +++ b/Sources/OpenAPIRuntime/StringCoder/StringDecoder.swift @@ -0,0 +1,448 @@ +//===----------------------------------------------------------------------===// +// +// 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 { + + /// 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. + /// - Returns: The decoded value. + func decode( + _ type: T.Type = T.self, + from data: String + ) throws -> T { + 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 + } +} + +/// 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 + } +} + +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 { + + /// A single value container used by `LosslessStringConvertibleDecoder`. + struct SingleValueContainer { + + /// The underlying decoder. + let decoder: LosslessStringConvertibleDecoder + + /// 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 + } + } + + /// An unkeyed container used by `LosslessStringConvertibleDecoder`. + struct UnkeyedContainer { + + /// The underlying decoder. + let decoder: LosslessStringConvertibleDecoder + } + + /// A keyed container used by `LosslessStringConvertibleDecoder`. + struct KeyedContainer { + + /// The underlying decoder. + let decoder: LosslessStringConvertibleDecoder + } +} + +extension LosslessStringConvertibleDecoder.SingleValueContainer: SingleValueDecodingContainer { + + var codingPath: [any CodingKey] { + [] + } + + func decodeNil() -> Bool { + false + } + + 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 + 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 + } + } +} + +extension LosslessStringConvertibleDecoder.UnkeyedContainer: UnkeyedDecodingContainer { + + 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 + } + +} + +extension LosslessStringConvertibleDecoder.KeyedContainer: KeyedDecodingContainerProtocol { + + 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 + } +} diff --git a/Sources/OpenAPIRuntime/StringCoder/StringEncoder.swift b/Sources/OpenAPIRuntime/StringCoder/StringEncoder.swift new file mode 100644 index 00000000..ba3fc943 --- /dev/null +++ b/Sources/OpenAPIRuntime/StringCoder/StringEncoder.swift @@ -0,0 +1,446 @@ +//===----------------------------------------------------------------------===// +// +// 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 { + + /// The coder used to serialize Date values. + let dateTranscoder: any DateTranscoder +} + +extension StringEncoder { + + /// Attempt to encode a value into a string using `CustomStringConvertible`. + /// + /// - Parameters: + /// - value: The value to encode. + /// - Returns: The encoded string. + func encode(_ value: some Encodable) throws -> String { + 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. + if let date = value as? Date { + var container = encoder.singleValueContainer() + try container.encode(date) + } else { + try value.encode(to: encoder) + } + + return try encoder.nonNilEncodedString() + } +} + +/// 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 + } +} + +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 + } + 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 + } + 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 { + + /// A single value container used by `CustomStringConvertibleEncoder`. + struct SingleValueContainer { + + /// 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) + } + } + + /// An unkeyed container used by `CustomStringConvertibleEncoder`. + struct UnkeyedContainer { + + /// The underlying encoder. + let encoder: CustomStringConvertibleEncoder + } + + /// A keyed container used by `CustomStringConvertibleEncoder`. + struct KeyedContainer { + + /// The underlying encoder. + let encoder: CustomStringConvertibleEncoder + } +} + +extension CustomStringConvertibleEncoder.SingleValueContainer: SingleValueEncodingContainer { + + var codingPath: [any CodingKey] { + [] + } + + mutating func encodeNil() throws { + throw CustomStringConvertibleEncoder.EncoderError.nilNotSupported + } + + 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) + 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) + } + } +} + +extension CustomStringConvertibleEncoder.UnkeyedContainer: UnkeyedEncodingContainer { + + 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 + } +} + +extension CustomStringConvertibleEncoder.KeyedContainer: KeyedEncodingContainerProtocol { + + 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 + } +} diff --git a/Tests/OpenAPIRuntimeTests/StringCoder/Test_StringCodingRoundtrip.swift b/Tests/OpenAPIRuntimeTests/StringCoder/Test_StringCodingRoundtrip.swift new file mode 100644 index 00000000..f4ba5188 --- /dev/null +++ b/Tests/OpenAPIRuntimeTests/StringCoder/Test_StringCodingRoundtrip.swift @@ -0,0 +1,132 @@ +//===----------------------------------------------------------------------===// +// +// 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" + ) + + // A Date. + try _test( + Date(timeIntervalSince1970: 1_692_948_899), + "2023-08-25T07:34:59Z" + ) + } + + func _test( + _ value: T, + _ expectedString: String, + file: StaticString = #file, + line: UInt = #line + ) throws { + let encoder = StringEncoder(dateTranscoder: .iso8601) + let encodedString = try encoder.encode(value) + XCTAssertEqual( + encodedString, + expectedString, + file: file, + line: line + ) + let decoder = StringDecoder(dateTranscoder: .iso8601) + let decodedValue = try decoder.decode( + T.self, + from: encodedString + ) + XCTAssertEqual( + decodedValue, + value, + file: file, + line: line + ) + } +}