diff --git a/Sources/OpenAPIKit/CodableVendorExtendable.swift b/Sources/OpenAPIKit/CodableVendorExtendable.swift index 9cfa2e0e0..fadb3e826 100644 --- a/Sources/OpenAPIKit/CodableVendorExtendable.swift +++ b/Sources/OpenAPIKit/CodableVendorExtendable.swift @@ -79,16 +79,12 @@ extension CodableVendorExtendable { return [:] } - let decoded = try AnyCodable(from: decoder).value + let decoded = try AnyCodable(from: decoder) - guard (decoded as? [Any]) == nil else { + guard case .object(let decodedAny) = decoded else { throw VendorExtensionDecodingError.selfIsArrayNotDict } - guard let decodedAny = decoded as? [String: Any] else { - throw VendorExtensionDecodingError.foundNonStringKeys - } - let extensions = decodedAny.filter { let key = CodingKeys.key(for: $0.key) @@ -105,7 +101,7 @@ extension CodableVendorExtendable { ) } - return extensions.mapValues(AnyCodable.init) + return extensions } internal func encodeExtensions(to container: inout T) throws where T.Key == Self.CodingKeys { diff --git a/Sources/OpenAPIKit/Schema Object/JSONSchema.swift b/Sources/OpenAPIKit/Schema Object/JSONSchema.swift index 9eebc8428..d0d841222 100644 --- a/Sources/OpenAPIKit/Schema Object/JSONSchema.swift +++ b/Sources/OpenAPIKit/Schema Object/JSONSchema.swift @@ -1967,21 +1967,19 @@ extension JSONSchema: Decodable { return } - let decoded = try AnyCodable(from: decoder).value - - guard (decoded as? [Any]) == nil else { + let decoded = try AnyCodable(from: decoder) + + switch decoded { + case let .object(dictionary): + extensions = dictionary + .filter { $0.key.lowercased().starts(with: "x-") } + + self.value = value.with(vendorExtensions: extensions) + case .array: throw VendorExtensionDecodingError.selfIsArrayNotDict - } - - guard let decodedAny = decoded as? [String: Any] else { + default: throw VendorExtensionDecodingError.foundNonStringKeys } - - extensions = decodedAny - .filter { $0.key.lowercased().starts(with: "x-") } - .mapValues(AnyCodable.init) - - self.value = value.with(vendorExtensions: extensions) } private static func decodeTypes(from container: KeyedDecodingContainer) throws -> [JSONType] { diff --git a/Sources/OpenAPIKit/Schema Object/JSONSchemaContext.swift b/Sources/OpenAPIKit/Schema Object/JSONSchemaContext.swift index 6cb0dae0c..54a8a349a 100644 --- a/Sources/OpenAPIKit/Schema Object/JSONSchemaContext.swift +++ b/Sources/OpenAPIKit/Schema Object/JSONSchemaContext.swift @@ -858,9 +858,11 @@ extension JSONSchema.CoreContext: Decodable { externalDocs = try container.decodeIfPresent(OpenAPI.ExternalDocumentation.self, forKey: .externalDocs) if Format.self == JSONTypeFormat.StringFormat.self { if nullable { - allowedValues = try Self.decodeAllowedValuesOrConst(String?.self, inContainer: container)?.map(AnyCodable.init) + allowedValues = try Self.decodeAllowedValuesOrConst(String?.self, inContainer: container)?.map { + $0.map(AnyCodable.string) ?? .null + } } else { - allowedValues = try Self.decodeAllowedValuesOrConst(String.self, inContainer: container)?.map(AnyCodable.init) + allowedValues = try Self.decodeAllowedValuesOrConst(String.self, inContainer: container)?.map(AnyCodable.string) } } else { allowedValues = try Self.decodeAllowedValuesOrConst(AnyCodable.self, inContainer: container) diff --git a/Sources/OpenAPIKit/_CoreReExport.swift b/Sources/OpenAPIKit/_CoreReExport.swift index 8f7ed5d46..00f5a5c12 100644 --- a/Sources/OpenAPIKit/_CoreReExport.swift +++ b/Sources/OpenAPIKit/_CoreReExport.swift @@ -5,7 +5,7 @@ // Created by Mathew Polzin on 2/28/21. // -@_exported import struct OpenAPIKitCore.AnyCodable +@_exported import enum OpenAPIKitCore.AnyCodable @_exported import struct OpenAPIKitCore.CodingPathError @_exported import enum OpenAPIKitCore.Either @_exported import protocol OpenAPIKitCore.OpenAPIError diff --git a/Sources/OpenAPIKit30/CodableVendorExtendable.swift b/Sources/OpenAPIKit30/CodableVendorExtendable.swift index 9cfa2e0e0..97ea46cdf 100644 --- a/Sources/OpenAPIKit30/CodableVendorExtendable.swift +++ b/Sources/OpenAPIKit30/CodableVendorExtendable.swift @@ -79,33 +79,32 @@ extension CodableVendorExtendable { return [:] } - let decoded = try AnyCodable(from: decoder).value - - guard (decoded as? [Any]) == nil else { + let decoded = try AnyCodable(from: decoder) + + switch decoded { + case .object(let dictionary): + let extensions = dictionary.filter { + let key = CodingKeys.key(for: $0.key) + + return !CodingKeys.allBuiltinKeys.contains(key) + } + + let invalidKeys = extensions.keys.filter { !$0.lowercased().starts(with: "x-") } + if !invalidKeys.isEmpty { + let invalidKeysList = "[ " + invalidKeys.joined(separator: ", ") + " ]" + throw InconsistencyError( + subjectName: "Vendor Extension", + details: "Found at least one vendor extension property that does not begin with the required 'x-' prefix. Invalid properties: \(invalidKeysList)", + codingPath: decoder.codingPath + ) + } + + return extensions + case .array: throw VendorExtensionDecodingError.selfIsArrayNotDict - } - - guard let decodedAny = decoded as? [String: Any] else { + default: throw VendorExtensionDecodingError.foundNonStringKeys } - - let extensions = decodedAny.filter { - let key = CodingKeys.key(for: $0.key) - - return !CodingKeys.allBuiltinKeys.contains(key) - } - - let invalidKeys = extensions.keys.filter { !$0.lowercased().starts(with: "x-") } - if !invalidKeys.isEmpty { - let invalidKeysList = "[ " + invalidKeys.joined(separator: ", ") + " ]" - throw InconsistencyError( - subjectName: "Vendor Extension", - details: "Found at least one vendor extension property that does not begin with the required 'x-' prefix. Invalid properties: \(invalidKeysList)", - codingPath: decoder.codingPath - ) - } - - return extensions.mapValues(AnyCodable.init) } internal func encodeExtensions(to container: inout T) throws where T.Key == Self.CodingKeys { diff --git a/Sources/OpenAPIKit30/Schema Object/JSONSchema.swift b/Sources/OpenAPIKit30/Schema Object/JSONSchema.swift index 514bf3659..e2f6bbe0a 100644 --- a/Sources/OpenAPIKit30/Schema Object/JSONSchema.swift +++ b/Sources/OpenAPIKit30/Schema Object/JSONSchema.swift @@ -1894,19 +1894,17 @@ extension JSONSchema: Decodable { return } - let decoded = try AnyCodable(from: decoder).value - - guard (decoded as? [Any]) == nil else { + let decoded = try AnyCodable(from: decoder) + + switch decoded { + case .object(let dictionary): + let extensions = dictionary.filter { $0.key.lowercased().starts(with: "x-") } + self.vendorExtensions = extensions + case .array: throw VendorExtensionDecodingError.selfIsArrayNotDict - } - - guard let decodedAny = decoded as? [String: Any] else { + default: throw VendorExtensionDecodingError.foundNonStringKeys } - - let extensions = decodedAny.filter { $0.key.lowercased().starts(with: "x-") } - - self.vendorExtensions = extensions.mapValues(AnyCodable.init) } } diff --git a/Sources/OpenAPIKit30/Schema Object/JSONSchemaContext.swift b/Sources/OpenAPIKit30/Schema Object/JSONSchemaContext.swift index 63a72ca22..e3c308712 100644 --- a/Sources/OpenAPIKit30/Schema Object/JSONSchemaContext.swift +++ b/Sources/OpenAPIKit30/Schema Object/JSONSchemaContext.swift @@ -748,7 +748,9 @@ extension JSONSchema.CoreContext: Decodable { externalDocs = try container.decodeIfPresent(OpenAPI.ExternalDocumentation.self, forKey: .externalDocs) if Format.self == JSONTypeFormat.StringFormat.self { if (nullable ?? false) { - allowedValues = try container.decodeIfPresent([String?].self, forKey: .allowedValues)?.map(AnyCodable.init) + allowedValues = try container.decodeIfPresent([String?].self, forKey: .allowedValues)?.map { + $0.map(AnyCodable.string) ?? .null + } } else { allowedValues = try container.decodeIfPresent([String].self, forKey: .allowedValues)?.map(AnyCodable.init) } diff --git a/Sources/OpenAPIKit30/_CoreReExport.swift b/Sources/OpenAPIKit30/_CoreReExport.swift index 8f7ed5d46..00f5a5c12 100644 --- a/Sources/OpenAPIKit30/_CoreReExport.swift +++ b/Sources/OpenAPIKit30/_CoreReExport.swift @@ -5,7 +5,7 @@ // Created by Mathew Polzin on 2/28/21. // -@_exported import struct OpenAPIKitCore.AnyCodable +@_exported import enum OpenAPIKitCore.AnyCodable @_exported import struct OpenAPIKitCore.CodingPathError @_exported import enum OpenAPIKitCore.Either @_exported import protocol OpenAPIKitCore.OpenAPIError diff --git a/Sources/OpenAPIKitCore/AnyCodable/AnyCodable.swift b/Sources/OpenAPIKitCore/AnyCodable/AnyCodable.swift index de8b82aa1..34d0bb71f 100644 --- a/Sources/OpenAPIKitCore/AnyCodable/AnyCodable.swift +++ b/Sources/OpenAPIKitCore/AnyCodable/AnyCodable.swift @@ -32,99 +32,31 @@ import Foundation and other collections that require `Encodable` or `Decodable` conformance by declaring their contained type to be `AnyCodable`. */ -public struct AnyCodable { - public let value: Any - - public init(_ value: T?) { - self.value = value ?? () - } +public enum AnyCodable: Equatable { + + case string(String) + case bool(Bool) + case int(Int) + case double(Double) + case object([String: AnyCodable]) + case array([AnyCodable]) + case null } extension AnyCodable: Encodable { public func encode(to encoder: Encoder) throws { - var container = encoder.singleValueContainer() - - switch value { - #if os(macOS) || os(iOS) || os(watchOS) || os(tvOS) - case let number as NSNumber: - try encode(nsnumber: number, into: &container) - #endif - case is NSNull, is Void: + switch self { + case let .string(value): try value.encode(to: encoder) + case let .bool(value): try value.encode(to: encoder) + case let .int(value): try value.encode(to: encoder) + case let .double(value): try value.encode(to: encoder) + case let .object(value): try value.encode(to: encoder) + case let .array(value): try value.encode(to: encoder) + case .null: + var container = encoder.singleValueContainer() try container.encodeNil() - case let bool as Bool: - try container.encode(bool) - case let int as Int: - try container.encode(int) - case let int8 as Int8: - try container.encode(int8) - case let int16 as Int16: - try container.encode(int16) - case let int32 as Int32: - try container.encode(int32) - case let int64 as Int64: - try container.encode(int64) - case let uint as UInt: - try container.encode(uint) - case let uint8 as UInt8: - try container.encode(uint8) - case let uint16 as UInt16: - try container.encode(uint16) - case let uint32 as UInt32: - try container.encode(uint32) - case let uint64 as UInt64: - try container.encode(uint64) - case let float as Float: - try container.encode(float) - case let double as Double: - try container.encode(double) - case let string as String: - try container.encode(string) - case let date as Date: - try container.encode(date) - case let url as URL: - try container.encode(url) - case let array as [Any?]: - try container.encode(array.map { AnyCodable($0) }) - case let dictionary as [String: Any?]: - try container.encode(dictionary.mapValues { AnyCodable($0) }) - default: - let context = EncodingError.Context(codingPath: container.codingPath, debugDescription: "AnyCodable value cannot be encoded") - throw EncodingError.invalidValue(value, context) } } - - #if os(macOS) || os(iOS) || os(watchOS) || os(tvOS) - private func encode(nsnumber: NSNumber, into container: inout SingleValueEncodingContainer) throws { - switch CFNumberGetType(nsnumber) { - case .charType: - try container.encode(nsnumber.boolValue) - case .sInt8Type: - try container.encode(nsnumber.int8Value) - case .sInt16Type: - try container.encode(nsnumber.int16Value) - case .sInt32Type: - try container.encode(nsnumber.int32Value) - case .sInt64Type: - try container.encode(nsnumber.int64Value) - case .shortType: - try container.encode(nsnumber.uint16Value) - case .longType: - try container.encode(nsnumber.uint32Value) - case .longLongType: - try container.encode(nsnumber.uint64Value) - case .intType, .nsIntegerType, .cfIndexType: - try container.encode(nsnumber.intValue) - case .floatType, .float32Type: - try container.encode(nsnumber.floatValue) - case .doubleType, .float64Type, .cgFloatType: - try container.encode(nsnumber.doubleValue) - #if swift(>=5.0) - @unknown default: - fatalError() - #endif - } - } - #endif } extension AnyCodable: Decodable { @@ -132,107 +64,49 @@ extension AnyCodable: Decodable { let container = try decoder.singleValueContainer() if container.decodeNil() { - self.init(NSNull()) + self = .null } else if let bool = try? container.decode(Bool.self) { - self.init(bool) + self = .bool(bool) } else if let int = try? container.decode(Int.self) { - self.init(int) - } else if let uint = try? container.decode(UInt.self) { - self.init(uint) + self = .int(int) } else if let double = try? container.decode(Double.self) { - self.init(double) + self = .double(double) } else if let string = try? container.decode(String.self) { - self.init(string) + self = .string(string) } else if let array = try? container.decode([AnyCodable].self) { - self.init(array.map { $0.value }) + self = .array(array) } else if let dictionary = try? container.decode([String: AnyCodable].self) { - self.init(dictionary.mapValues { $0.value }) + self = .object(dictionary) } else { throw DecodingError.dataCorruptedError(in: container, debugDescription: "AnyCodable value cannot be decoded") } } } -extension AnyCodable: Equatable { - public static func == (lhs: AnyCodable, rhs: AnyCodable) -> Bool { - switch (lhs.value, rhs.value) { - case is (Void, Void): - return true - case let (lhs as Bool, rhs as Bool): - return lhs == rhs - case let (lhs as Int, rhs as Int): - return lhs == rhs - case let (lhs as Int8, rhs as Int8): - return lhs == rhs - case let (lhs as Int16, rhs as Int16): - return lhs == rhs - case let (lhs as Int32, rhs as Int32): - return lhs == rhs - case let (lhs as Int64, rhs as Int64): - return lhs == rhs - case let (lhs as UInt, rhs as UInt): - return lhs == rhs - case let (lhs as UInt8, rhs as UInt8): - return lhs == rhs - case let (lhs as UInt16, rhs as UInt16): - return lhs == rhs - case let (lhs as UInt32, rhs as UInt32): - return lhs == rhs - case let (lhs as UInt64, rhs as UInt64): - return lhs == rhs - case let (lhs as Float, rhs as Float): - return lhs == rhs - case let (lhs as Double, rhs as Double): - return lhs == rhs - case let (lhs as String, rhs as String): - return lhs == rhs - case let (lhs as [String: String], rhs as [String: String]): - return lhs == rhs - case let (lhs as [String: Int], rhs as [String: Int]): - return lhs == rhs - case let (lhs as [String: Double], rhs as [String: Double]): - return lhs == rhs - case let (lhs as [String: Bool], rhs as [String: Bool]): - return lhs == rhs - case let (lhs as [String: AnyCodable], rhs as [String: AnyCodable]): - return lhs == rhs - case let (lhs as [String], rhs as [String]): - return lhs == rhs - case let (lhs as [Int], rhs as [Int]): - return lhs == rhs - case let (lhs as [Double], rhs as [Double]): - return lhs == rhs - case let (lhs as [Bool], rhs as [Bool]): - return lhs == rhs - case let (lhs as [AnyCodable], rhs as [AnyCodable]): - return lhs == rhs - default: - return false - } - } -} - extension AnyCodable: CustomStringConvertible { public var description: String { - switch value { - case is Void: - return String(describing: nil as Any?) - case let value as CustomStringConvertible: - return value.description - default: - return String(describing: value) + switch self { + case .string(let string): + return "\"\(string.description)\"" + case .bool(let bool): + return bool.description + case .int(let int): + return int.description + case .double(let double): + return double.description + case .object(let dictionary): + return "[\(dictionary.sorted(by: { $0.key < $1.key }).map { "\"\($0)\": \($1.description)" }.joined(separator: ", "))]" + case .array(let array): + return "[\(array.map { $0.description }.joined(separator: ", "))]" + case .null: + return "nil" } } } extension AnyCodable: CustomDebugStringConvertible { public var debugDescription: String { - switch value { - case let value as CustomDebugStringConvertible: - return "AnyCodable(\(value.debugDescription))" - default: - return "AnyCodable(\(description))" - } + "AnyCodable(\(description))" } } @@ -246,30 +120,216 @@ extension AnyCodable: ExpressibleByDictionaryLiteral {} extension AnyCodable { public init(nilLiteral _: ()) { - self.init(nil as Any?) + self = .null } public init(booleanLiteral value: Bool) { - self.init(value) + self = .bool(value) } public init(integerLiteral value: Int) { - self.init(value) + self = .int(value) } public init(floatLiteral value: Double) { - self.init(value) + self = .double(value) } public init(stringLiteral value: String) { - self.init(value) + self = .string(value) + } + + public init(arrayLiteral elements: AnyCodable...) { + self = .array(elements) + } + + public init(dictionaryLiteral elements: (String, AnyCodable)...) { + self = .object([String: AnyCodable](elements, uniquingKeysWith: { first, _ in first })) } +} + +extension AnyCodable { - public init(arrayLiteral elements: Any...) { - self.init(elements) + @available(*, deprecated, message: "`value` doesn't present the value was wrapped anymore, use typed vars or switch instead") + public var value: Any { + switch self { + case .string(let string): + return string + case .bool(let bool): + return bool + case .int(let int): + return int + case .double(let double): + return double + case .object(let dictionary): + return dictionary.mapValues { $0.value } + case .array(let array): + return array.map { $0.value } + case .null: + return Optional.none as Any + } + } + + public var string: String? { + if case let .string(string) = self { + return string + } + return nil + } + + public var bool: Bool? { + if case let .bool(bool) = self { + return bool + } + return nil + } + + public var int: Int? { + if case let .int(int) = self { + return int + } + return nil + } + + public var double: Double? { + if case let .double(double) = self { + return double + } + return nil + } + + public var object: [String: AnyCodable]? { + if case let .object(object) = self { + return object + } + return nil + } + + public var array: [AnyCodable]? { + if case let .array(array) = self { + return array + } + return nil + } + + public var isNull: Bool { + if case .null = self { + return true + } + return false } + + public init(_ value: Any?) { + guard var value = value else { + self = .null + return + } + while let optional = value as? OptionalProtocol { + if let unwrapped = optional.anyValue { + value = unwrapped + } else { + self = .null + return + } + } + switch value { + case let anyCodable as AnyCodable: + self = anyCodable + case is NSNull, is Void: + self = .null + case let bool as Bool: + self = .bool(bool) + case let int as Int: + self = .int(int) + case let int8 as Int8: + self = .int(Int(int8)) + case let int16 as Int16: + self = .int(Int(int16)) + case let int32 as Int32: + self = .int(Int(int32)) + case let int64 as Int64: + self = .int(Int(int64)) + case let uint as UInt: + self = .int(Int(uint)) + case let uint8 as UInt8: + self = .int(Int(uint8)) + case let uint16 as UInt16: + self = .int(Int(uint16)) + case let uint32 as UInt32: + self = .int(Int(uint32)) + case let uint64 as UInt64: + self = .int(Int(min(uint64, UInt64(Int.max)))) + case let float as Float: + self = .double(Double(float)) + case let double as Double: + self = .double(double) + case let string as String: + self = .string(string) + case let date as Date: + self = .double(date.timeIntervalSince1970) + case let url as URL: + self = .string(url.absoluteString) + case let array as [Any?]: + self = .array(array.map { AnyCodable($0) }) + case let dictionary as [String: Any?]: + self = .object(dictionary.mapValues { AnyCodable($0) }) +#if os(macOS) || os(iOS) || os(watchOS) || os(tvOS) + case let number as NSNumber: + self = .encode(nsnumber: number) +#endif + default: + if let encodable = value as? Encodable, let anyCodable = try? AnyCodable.encoded(encodable) { + self = anyCodable + return + } + let mirror = Mirror(reflecting: value) + switch mirror.displayStyle { + case .optional: + if mirror.children.isEmpty { + self = .null + } else { + self = AnyCodable(mirror.children.first?.value) + } + case .collection, .set: + self = .array(mirror.children.map { AnyCodable($0.value) }) + default: + if mirror.children.isEmpty { + self = .string("\(value)") + } else { + self = .object( + mirror.children.reduce(into: [String: AnyCodable]()) { result, child in + result[child.label ?? ".\(result.count)"] = AnyCodable(child.value) + } + ) + } + } + } + } +} - public init(dictionaryLiteral elements: (AnyHashable, Any)...) { - self.init([AnyHashable: Any](elements, uniquingKeysWith: { first, _ in first })) +private extension AnyCodable { + + static func encode(nsnumber: NSNumber) -> AnyCodable { + switch CFNumberGetType(nsnumber) { + case .charType, .sInt8Type, .sInt16Type, .sInt32Type, .sInt64Type, + .shortType, .longType, .longLongType, .intType, .nsIntegerType, .cfIndexType: + return .int(nsnumber.intValue) + case .floatType, .float32Type, .doubleType, .float64Type, .cgFloatType: + return .double(nsnumber.doubleValue) +#if swift(>=5.0) + @unknown default: + fatalError() +#endif + } } } + +private protocol OptionalProtocol { + + var anyValue: Any? { get } +} + +extension Optional: OptionalProtocol { + + var anyValue: Any? { self } +} diff --git a/Sources/OpenAPIKitCore/AnyCodable/AnyCodableEncoder.swift b/Sources/OpenAPIKitCore/AnyCodable/AnyCodableEncoder.swift new file mode 100644 index 000000000..7a020d830 --- /dev/null +++ b/Sources/OpenAPIKitCore/AnyCodable/AnyCodableEncoder.swift @@ -0,0 +1,440 @@ +import Foundation + +public extension AnyCodable { + + /// Creates a new instance from the given `Encodable` value. + /// + /// - Parameters: + /// - value: The value to encode. + /// - valueEncodingStrategies: Value encoding strategies to use. + /// - keyEncodingStrategy: The key encoding strategy to use. + /// - Returns: A new instance of `AnyCodable` or `nil` if the given value cannot be encoded. + static func encoded( + _ value: Encodable, + valueEncodingStrategies: [ValueEncodingStrategy] = [.Decimal.number, .URL.uri, .Data.base64], + keyEncodingStrategy: KeyEncodingStrategy = .default + ) throws -> AnyCodable { + let newEncoder = AnyCodableEncoder(strategies: valueEncodingStrategies, keyEncodingStrategy: keyEncodingStrategy) + return try newEncoder.encode(value) + } +} + +private final class AnyCodableEncoder: Encoder { + let codingPath: [CodingKey] + let userInfo: [CodingUserInfoKey: Any] + private var result: AnyCodable + let strategies: [ValueEncodingStrategy] + let keyEncodingStrategy: KeyEncodingStrategy + + init( + codingPath: [CodingKey] = [], + strategies: [ValueEncodingStrategy], + keyEncodingStrategy: KeyEncodingStrategy + ) { + self.codingPath = codingPath + userInfo = [:] + self.strategies = strategies + self.keyEncodingStrategy = keyEncodingStrategy + result = .object([:]) + } + + func container(keyedBy _: Key.Type) -> KeyedEncodingContainer where Key: CodingKey { + let container = AnyCodableKeyedEncodingContainer( + codingPath: codingPath, + encoder: self, + result: Ref( + get: { [self] in + guard case let .object(value) = self.result else { return [:] } + return value + }, set: { [self] newValue in + self.result = .object(newValue) + } + ) + ) + return KeyedEncodingContainer(container) + } + + func unkeyedContainer() -> UnkeyedEncodingContainer { + AnyCodableUnkeyedEncodingContainer( + codingPath: codingPath, + encoder: self, + result: Ref( + get: { [self] in + if case let .array(value) = self.result { + return value + } + return [] + }, set: { [self] newValue in + self.result = .array(newValue) + } + ) + ) + } + + func singleValueContainer() -> SingleValueEncodingContainer { + AnyCodableSingleValueEncodingContainer( + codingPath: codingPath, + encoder: self, + result: Ref(self, \.result) + ) + } + + func encode(_ value: Encodable) throws -> AnyCodable { + switch value { + case nil as Any?: + result = .null + + default: + for format in strategies { + if try format.encode(value, self) { + return result + } + } + try value.encode(to: self) + } + return result + } +} + +private struct AnyCodableSingleValueEncodingContainer: SingleValueEncodingContainer { + var codingPath: [CodingKey] + let encoder: AnyCodableEncoder + @Ref var result: AnyCodable + + mutating func encodeNil() throws {} + + mutating func encode(_ value: Bool) throws { + result = .bool(value) + } + + mutating func encode(_ value: String) throws { + result = .string(value) + } + + mutating func encode(_ value: Double) throws { + result = .double(value) + } + + mutating func encode(_ value: Float) throws { + result = .double(Double(value)) + } + + mutating func encode(_ value: Int) throws { + result = .int(value) + } + + mutating func encode(_ value: Int8) throws { + result = .int(Int(value)) + } + + mutating func encode(_ value: Int16) throws { + result = .int(Int(value)) + } + + mutating func encode(_ value: Int32) throws { + result = .int(Int(value)) + } + + mutating func encode(_ value: Int64) throws { + result = .int(Int(value)) + } + + mutating func encode(_ value: UInt) throws { + result = .int(Int(value)) + } + + mutating func encode(_ value: UInt8) throws { + result = .int(Int(value)) + } + + mutating func encode(_ value: UInt16) throws { + result = .int(Int(value)) + } + + mutating func encode(_ value: UInt32) throws { + result = .int(Int(value)) + } + + mutating func encode(_ value: UInt64) throws { + result = .int(Int(value)) + } + + mutating func encode(_ value: T) throws { + let newEncoder = AnyCodableEncoder(codingPath: codingPath, strategies: encoder.strategies, keyEncodingStrategy: encoder.keyEncodingStrategy) + result = try newEncoder.encode(value) + } +} + +private struct AnyCodableKeyedEncodingContainer: KeyedEncodingContainerProtocol { + var codingPath: [CodingKey] + let encoder: AnyCodableEncoder + @Ref var result: [String: AnyCodable] + + @inline(__always) + private func str(_ key: Key) -> String { + encoder.keyEncodingStrategy.encode(key.stringValue) + } + + mutating func encodeNil(forKey key: Key) throws { + result[str(key)] = nil + } + + mutating func encode(_ value: Bool, forKey key: Key) throws { + result[str(key)] = .bool(value) + } + + mutating func encode(_ value: String, forKey key: Key) throws { + result[str(key)] = .string(value) + } + + mutating func encode(_ value: Double, forKey key: Key) throws { + result[str(key)] = .double(value) + } + + mutating func encode(_ value: Float, forKey key: Key) throws { + result[str(key)] = .double(Double(value)) + } + + mutating func encode(_ value: Int, forKey key: Key) throws { + result[str(key)] = .int(value) + } + + mutating func encode(_ value: Int8, forKey key: Key) throws { + result[str(key)] = .int(Int(value)) + } + + mutating func encode(_ value: Int16, forKey key: Key) throws { + result[str(key)] = .int(Int(value)) + } + + mutating func encode(_ value: Int32, forKey key: Key) throws { + result[str(key)] = .int(Int(value)) + } + + mutating func encode(_ value: Int64, forKey key: Key) throws { + result[str(key)] = .int(Int(value)) + } + + mutating func encode(_ value: UInt, forKey key: Key) throws { + result[str(key)] = .int(Int(value)) + } + + mutating func encode(_ value: UInt8, forKey key: Key) throws { + result[str(key)] = .int(Int(value)) + } + + mutating func encode(_ value: UInt16, forKey key: Key) throws { + result[str(key)] = .int(Int(value)) + } + + mutating func encode(_ value: UInt32, forKey key: Key) throws { + result[str(key)] = .int(Int(value)) + } + + mutating func encode(_ value: UInt64, forKey key: Key) throws { + result[str(key)] = .int(Int(value)) + } + + mutating func encode(_ value: T, forKey key: Key) throws { + let newEncoder = AnyCodableEncoder(codingPath: nestedPath(for: key), strategies: encoder.strategies, keyEncodingStrategy: encoder.keyEncodingStrategy) + result[str(key)] = try newEncoder.encode(value) + } + + mutating func nestedContainer(keyedBy _: NestedKey.Type, forKey key: Key) -> KeyedEncodingContainer where NestedKey: CodingKey { + let strKey = str(key) + let container = AnyCodableKeyedEncodingContainer( + codingPath: nestedPath(for: key), + encoder: encoder, + result: Ref( + get: { [$result] in + guard + case let .object(value) = $result.wrappedValue[strKey] + else { return [:] } + return value + }, set: { [$result] newValue in + $result.wrappedValue[strKey] = .object(newValue) + } + ) + ) + result[strKey] = .object([:]) + return KeyedEncodingContainer(container) + } + + mutating func nestedUnkeyedContainer(forKey key: Key) -> UnkeyedEncodingContainer { + let strKey = str(key) + let container = AnyCodableUnkeyedEncodingContainer( + codingPath: nestedPath(for: key), + encoder: encoder, + result: Ref( + get: { [$result] in + guard + case let .array(value) = $result.wrappedValue[strKey] + else { return [] } + return value + }, set: { [$result] newValue in + $result.wrappedValue[strKey] = .array(newValue) + } + ) + ) + result[strKey] = .array([]) + return container + } + + mutating func superEncoder() -> Encoder { + AnyCodableEncoder(codingPath: codingPath, strategies: encoder.strategies, keyEncodingStrategy: encoder.keyEncodingStrategy) + } + + mutating func superEncoder(forKey key: Key) -> Encoder { + result[str(key)] = .object([:]) + return AnyCodableEncoder(codingPath: nestedPath(for: key), strategies: encoder.strategies, keyEncodingStrategy: encoder.keyEncodingStrategy) + } + + private func nestedPath(for key: Key) -> [CodingKey] { + codingPath + [key] + } +} + +private struct AnyCodableUnkeyedEncodingContainer: UnkeyedEncodingContainer { + var codingPath: [CodingKey] + var count: Int { result.count } + let encoder: AnyCodableEncoder + @Ref var result: [AnyCodable] + + private var nestedPath: [CodingKey] { + codingPath + [IntKey(intValue: codingPath.count)] + } + + mutating func nestedContainer(keyedBy _: NestedKey.Type) -> KeyedEncodingContainer where NestedKey: CodingKey { + let index = result.count + let container = AnyCodableKeyedEncodingContainer( + codingPath: nestedPath, + encoder: encoder, + result: Ref( + get: { [$result] in + guard + $result.wrappedValue.indices.contains(index), + case let .object(value) = $result.wrappedValue[index] + else { return [:] } + return value + }, set: { [$result] newValue in + guard $result.wrappedValue.indices.contains(index) else { + return + } + $result.wrappedValue[index] = .object(newValue) + } + ) + ) + result.append(.object([:])) + return KeyedEncodingContainer(container) + } + + mutating func nestedUnkeyedContainer() -> UnkeyedEncodingContainer { + let index = result.count + let container = AnyCodableUnkeyedEncodingContainer( + codingPath: nestedPath, + encoder: encoder, + result: Ref( + get: { [$result] in + guard + $result.wrappedValue.indices.contains(index), + case let .array(value) = $result.wrappedValue[index] + else { return [] } + return value + }, set: { [$result] newValue in + guard $result.wrappedValue.indices.contains(index) else { + return + } + $result.wrappedValue[index] = .array(newValue) + } + ) + ) + result.append(.array([])) + return container + } + + mutating func encodeNil() throws {} + + mutating func superEncoder() -> Encoder { + AnyCodableEncoder(codingPath: codingPath, strategies: encoder.strategies, keyEncodingStrategy: encoder.keyEncodingStrategy) + } + + mutating func encode(_ value: Bool) throws { + result.append(.bool(value)) + } + + mutating func encode(_ value: String) throws { + result.append(.string(value)) + } + + mutating func encode(_ value: Double) throws { + result.append(.double(value)) + } + + mutating func encode(_ value: Float) throws { + result.append(.double(Double(value))) + } + + mutating func encode(_ value: Int) throws { + result.append(.int(value)) + } + + mutating func encode(_ value: Int8) throws { + result.append(.int(Int(value))) + } + + mutating func encode(_ value: Int16) throws { + result.append(.int(Int(value))) + } + + mutating func encode(_ value: Int32) throws { + result.append(.int(Int(value))) + } + + mutating func encode(_ value: Int64) throws { + result.append(.int(Int(value))) + } + + mutating func encode(_ value: UInt) throws { + result.append(.int(Int(value))) + } + + mutating func encode(_ value: UInt8) throws { + result.append(.int(Int(value))) + } + + mutating func encode(_ value: UInt16) throws { + result.append(.int(Int(value))) + } + + mutating func encode(_ value: UInt32) throws { + result.append(.int(Int(value))) + } + + mutating func encode(_ value: UInt64) throws { + result.append(.int(Int(value))) + } + + mutating func encode(_ value: T) throws { + let newEncoder = AnyCodableEncoder( + codingPath: nestedPath, + strategies: encoder.strategies, + keyEncodingStrategy: encoder.keyEncodingStrategy + ) + try result.append(newEncoder.encode(value)) + } +} + +private struct IntKey: CodingKey { + let intValue: Int? + let stringValue: String + + init(intValue: Int) { + self.intValue = intValue + stringValue = intValue.description + } + + init?(stringValue: String) { + intValue = Int(stringValue) + self.stringValue = stringValue + } +} diff --git a/Sources/OpenAPIKitCore/AnyCodable/DataEncodingStrategies.swift b/Sources/OpenAPIKitCore/AnyCodable/DataEncodingStrategies.swift new file mode 100644 index 000000000..38db3fb57 --- /dev/null +++ b/Sources/OpenAPIKitCore/AnyCodable/DataEncodingStrategies.swift @@ -0,0 +1,25 @@ +import Foundation + +public extension ValueEncodingStrategy { + + /// Data encoding strategy to use when encoding `AnyCodable` values. + enum Data { + } +} + +public extension ValueEncodingStrategy.Data { + static var `default`: ValueEncodingStrategy = .Data.base64 + + /// Base64 string, schema: .string(format: .byte) + static var base64: ValueEncodingStrategy { + .Data.base64(options: []) + } + + /// Base64 string, schema: .string(format: .byte) + static func base64(options: Data.Base64EncodingOptions) -> ValueEncodingStrategy { + ValueEncodingStrategy(Data.self) { data, encoder in + var container = encoder.singleValueContainer() + try container.encode(data.base64EncodedString(options: options)) + } + } +} diff --git a/Sources/OpenAPIKitCore/AnyCodable/DateEncodingStrategies.swift b/Sources/OpenAPIKitCore/AnyCodable/DateEncodingStrategies.swift new file mode 100644 index 000000000..52d10072b --- /dev/null +++ b/Sources/OpenAPIKitCore/AnyCodable/DateEncodingStrategies.swift @@ -0,0 +1,63 @@ +import Foundation + +public extension ValueEncodingStrategy { + + /// Date encoding strategy to use when encoding `AnyCodable` values. + enum Date { + } +} + +public extension ValueEncodingStrategy.Date { + static var `default`: ValueEncodingStrategy = .Date.dateTime + + /// full-date notation as defined by RFC 3339, section 5.6, for example, 2017-07-21, schema: .string(format: .date) + static var date: ValueEncodingStrategy { + .Date.custom { date, encoder in + try encoder.encode(ValueEncodingStrategy.Date.date(date)) + } + } + + /// the date-time notation as defined by RFC 3339, section 5.6, for example, 2017-07-21T17:32:28Z, schema: .string(format: .dateTime) + static var dateTime: ValueEncodingStrategy { + .Date.custom { date, encoder in + try encoder.encode(ValueEncodingStrategy.Date.dateTime(date)) + } + } + + /// the interval between the date value and 00:00:00 UTC on 1 January 1970, schema: .number(format: .other("timestamp")) + static var timestamp: ValueEncodingStrategy { + .Date.custom { date, encoder in + try encoder.encode(date.timeIntervalSince1970) + } + } + + /// Custom date encoding strategy + static func custom( + encode: @escaping (Date, inout SingleValueEncodingContainer) throws -> Void + ) -> ValueEncodingStrategy { + ValueEncodingStrategy(Date.self) { + var container = $1.singleValueContainer() + try encode($0, &container) + } + } +} + +extension ValueEncodingStrategy.Date { + static func dateTime(_ date: Date) -> String { + isoFormatter.string(from: date) + } + + static func date(_ date: Date) -> String { + dateFormatter.dateFormat = "yyyy-MM-dd" + return dateFormatter.string(from: date) + } +} + +private let isoFormatter: DateFormatter = { + let formatter = DateFormatter() + formatter.locale = Locale(identifier: "en_US_POSIX") + formatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'" + formatter.timeZone = TimeZone(secondsFromGMT: 0) + return formatter +}() +private let dateFormatter = DateFormatter() diff --git a/Sources/OpenAPIKitCore/AnyCodable/DecimalEncodingStrategies.swift b/Sources/OpenAPIKitCore/AnyCodable/DecimalEncodingStrategies.swift new file mode 100644 index 000000000..3d57d58b5 --- /dev/null +++ b/Sources/OpenAPIKitCore/AnyCodable/DecimalEncodingStrategies.swift @@ -0,0 +1,28 @@ +import Foundation + +public extension ValueEncodingStrategy { + + /// Decimal encoding strategy to use when encoding `AnyCodable` values. + enum Decimal { + } +} + +public extension ValueEncodingStrategy.Decimal { + static var `default`: ValueEncodingStrategy = .Decimal.number + + /// Quoted string + static var string: ValueEncodingStrategy { + ValueEncodingStrategy(Decimal.self) { decimal, encoder in + var container = encoder.singleValueContainer() + try container.encode(decimal.description) + } + } + + /// Number + static var number: ValueEncodingStrategy { + ValueEncodingStrategy(Decimal.self) { decimal, encoder in + var container = encoder.singleValueContainer() + try container.encode((decimal as NSDecimalNumber).doubleValue) + } + } +} diff --git a/Sources/OpenAPIKitCore/AnyCodable/KeyEncodingStrategy.swift b/Sources/OpenAPIKitCore/AnyCodable/KeyEncodingStrategy.swift new file mode 100644 index 000000000..c483f382b --- /dev/null +++ b/Sources/OpenAPIKitCore/AnyCodable/KeyEncodingStrategy.swift @@ -0,0 +1,46 @@ +import Foundation + +/// Key encoding strategy to use when encoding `AnyCodable` values. +public struct KeyEncodingStrategy { + public let encode: (String) -> String +} + +public extension KeyEncodingStrategy { + static var `default`: KeyEncodingStrategy = .useDefaultKeys + + /// Does not change the key + static var useDefaultKeys: KeyEncodingStrategy = .custom { $0 } + + /// Custom key encoding strategy + static func custom(_ encode: @escaping (String) -> String) -> KeyEncodingStrategy { + KeyEncodingStrategy(encode: encode) + } + + /// Encodes from camelCase to snake_case + static var convertToSnakeCase: KeyEncodingStrategy { + .convertToSnakeCase(separator: "_") + } + + /// Encodes from camelCase to snake_case with a custom separator + static func convertToSnakeCase(separator: String) -> KeyEncodingStrategy { + .custom { + $0.toSnakeCase(separator: separator) + } + } +} + +private extension String { + func toSnakeCase(separator: String = "_") -> String { + var result = "" + + for character in self { + if character.isUppercase { + result += separator + character.lowercased() + } else { + result += String(character) + } + } + + return result + } +} diff --git a/Sources/OpenAPIKitCore/AnyCodable/Ref.swift b/Sources/OpenAPIKitCore/AnyCodable/Ref.swift new file mode 100644 index 000000000..7fb47396f --- /dev/null +++ b/Sources/OpenAPIKitCore/AnyCodable/Ref.swift @@ -0,0 +1,40 @@ +import Foundation + +@propertyWrapper +struct Ref { + + let get: () -> Value + let set: (Value) -> Void + + var wrappedValue: Value { + get { get() } + nonmutating set { set(newValue) } + } + + var projectedValue: Ref { + get { self } + set { self = newValue } + } +} + +extension Ref { + + static func constant(_ value: Value) -> Ref { + self.init( + get: { + value + }, set: { _ in + } + ) + } + + init(_ value: T, _ keyPath: ReferenceWritableKeyPath) { + self.init( + get: { + value[keyPath: keyPath] + }, set: { newValue in + value[keyPath: keyPath] = newValue + } + ) + } +} diff --git a/Sources/OpenAPIKitCore/AnyCodable/URLEncodingStrategies.swift b/Sources/OpenAPIKitCore/AnyCodable/URLEncodingStrategies.swift new file mode 100644 index 000000000..53d1e5866 --- /dev/null +++ b/Sources/OpenAPIKitCore/AnyCodable/URLEncodingStrategies.swift @@ -0,0 +1,20 @@ +import Foundation + +public extension ValueEncodingStrategy { + + /// URL encoding strategy to use when encoding `AnyCodable` values. + enum URL { + } +} + +public extension ValueEncodingStrategy.URL { + static var `default`: ValueEncodingStrategy = .URL.uri + + /// URI string, schema: .string(format: .other("uri")) + static var uri: ValueEncodingStrategy { + ValueEncodingStrategy(URL.self) { url, encoder in + var container = encoder.singleValueContainer() + try container.encode(url.absoluteString) + } + } +} diff --git a/Sources/OpenAPIKitCore/AnyCodable/ValueEncodingStrategy.swift b/Sources/OpenAPIKitCore/AnyCodable/ValueEncodingStrategy.swift new file mode 100644 index 000000000..8868ed60c --- /dev/null +++ b/Sources/OpenAPIKitCore/AnyCodable/ValueEncodingStrategy.swift @@ -0,0 +1,19 @@ +import Foundation + +public struct ValueEncodingStrategy { + + public let encode: (Encodable, Encoder) throws -> Bool + + public init( + _ type: T.Type, + encode: @escaping (T, Encoder) throws -> Void + ) { + self.encode = { + guard let value = $0 as? T else { + return false + } + try encode(value, $1) + return true + } + } +} diff --git a/Tests/AnyCodableTests/AnyCodableTests.swift b/Tests/AnyCodableTests/AnyCodableTests.swift index 9d17d7c7d..5e7004282 100644 --- a/Tests/AnyCodableTests/AnyCodableTests.swift +++ b/Tests/AnyCodableTests/AnyCodableTests.swift @@ -2,50 +2,83 @@ import XCTest class AnyCodableTests: XCTestCase { - func testInit() throws { - let _ = AnyCodable("hi") - let _: AnyCodable = nil - let _: AnyCodable = true - let _: AnyCodable = 10 - let _: AnyCodable = 3.4 - let _: AnyCodable = "hello" - let _: AnyCodable = ["hi", "there"] - let _: AnyCodable = ["hi": "there"] - } - - func testEquality() throws { - XCTAssertEqual(AnyCodable(()), AnyCodable(())) - XCTAssertEqual(AnyCodable(true), AnyCodable(true)) - XCTAssertEqual(AnyCodable(2), AnyCodable(2)) - XCTAssertEqual(AnyCodable(Int8(2)), AnyCodable(Int8(2))) - XCTAssertEqual(AnyCodable(Int16(2)), AnyCodable(Int16(2))) - XCTAssertEqual(AnyCodable(Int32(2)), AnyCodable(Int32(2))) - XCTAssertEqual(AnyCodable(Int64(2)), AnyCodable(Int64(2))) - XCTAssertEqual(AnyCodable(UInt(2)), AnyCodable(UInt(2))) - XCTAssertEqual(AnyCodable(UInt8(2)), AnyCodable(UInt8(2))) - XCTAssertEqual(AnyCodable(UInt16(2)), AnyCodable(UInt16(2))) - XCTAssertEqual(AnyCodable(UInt32(2)), AnyCodable(UInt32(2))) - XCTAssertEqual(AnyCodable(UInt64(2)), AnyCodable(UInt64(2))) - XCTAssertEqual(AnyCodable(Float(2)), AnyCodable(Float(2))) - XCTAssertEqual(AnyCodable(Double(2)), AnyCodable(Double(2))) - XCTAssertEqual(AnyCodable("hi"), AnyCodable("hi")) - XCTAssertEqual(AnyCodable(["hi": AnyCodable(2)]), AnyCodable(["hi": AnyCodable(2)])) - XCTAssertEqual(AnyCodable([AnyCodable("hi"), AnyCodable("there")]), AnyCodable([AnyCodable("hi"), AnyCodable("there")])) - XCTAssertEqual(AnyCodable(["hi":1]), AnyCodable(["hi":1])) - XCTAssertEqual(AnyCodable(["hi":1.2]), AnyCodable(["hi":1.2])) - XCTAssertEqual(AnyCodable(["hi"]), AnyCodable(["hi"])) - XCTAssertEqual(AnyCodable([1]), AnyCodable([1])) - XCTAssertEqual(AnyCodable([1.2]), AnyCodable([1.2])) - XCTAssertEqual(AnyCodable([true]), AnyCodable([true])) - - XCTAssertNotEqual(AnyCodable(()), AnyCodable(true)) - } - - func testVoidDescription() { - XCTAssertEqual(String(describing: AnyCodable(Void())), "nil") - } - - func testJSONDecoding() throws { + func test_equality() throws { + XCTAssertEqual(AnyCodable.null, AnyCodable.null) + XCTAssertEqual(AnyCodable.bool(true), AnyCodable.bool(true)) + XCTAssertEqual(AnyCodable.int(2), AnyCodable.int(2)) + XCTAssertEqual(AnyCodable.double(2), AnyCodable.double(2)) + XCTAssertEqual(AnyCodable.string("hi"), AnyCodable.string("hi")) + XCTAssertEqual(AnyCodable.object(["hi": .int(2)]), AnyCodable.object(["hi": .int(2)])) + XCTAssertEqual(AnyCodable.array([.string("hi")]), AnyCodable.array([.string("hi")])) + XCTAssertEqual(AnyCodable.array([.int(1)]), AnyCodable.array([.int(1)])) + + XCTAssertNotEqual(AnyCodable.null, AnyCodable.bool(true)) + XCTAssertNotEqual(AnyCodable.null, AnyCodable.int(2)) + XCTAssertNotEqual(AnyCodable.int(4), AnyCodable.string("hi")) + XCTAssertNotEqual(AnyCodable.string("hi"), AnyCodable.array([.string("hi")])) + XCTAssertNotEqual(AnyCodable.object(["hi": .int(2)]), AnyCodable.object(["hi": .double(3)])) + } + + func test_inits() throws { + let falseBool = false + XCTAssertEqual(AnyCodable(()), AnyCodable.null) + XCTAssertEqual(AnyCodable(true), AnyCodable.bool(true)) + XCTAssertEqual(AnyCodable(falseBool), AnyCodable.bool(false)) + XCTAssertEqual(AnyCodable(2), AnyCodable.int(2)) + XCTAssertEqual(AnyCodable(Int8(2)), AnyCodable.int(2)) + XCTAssertEqual(AnyCodable(Int16(2)), AnyCodable.int(2)) + XCTAssertEqual(AnyCodable(Int32(2)), AnyCodable.int(2)) + XCTAssertEqual(AnyCodable(Int64(2)), AnyCodable.int(2)) + XCTAssertEqual(AnyCodable(UInt(2)), AnyCodable.int(2)) + XCTAssertEqual(AnyCodable(UInt8(2)), AnyCodable.int(2)) + XCTAssertEqual(AnyCodable(UInt16(2)), AnyCodable.int(2)) + XCTAssertEqual(AnyCodable(UInt32(2)), AnyCodable.int(2)) + XCTAssertEqual(AnyCodable(UInt64(2)), AnyCodable.int(2)) + XCTAssertEqual(AnyCodable(Float(2)), AnyCodable.double(2)) + XCTAssertEqual(AnyCodable(Double(2)), AnyCodable.double(2)) + XCTAssertEqual(AnyCodable("hi"), AnyCodable.string("hi")) + XCTAssertEqual(AnyCodable(["hi": 2]), AnyCodable.object(["hi": .int(2)])) + XCTAssertEqual(AnyCodable(["hi", "there"]), AnyCodable.array([.string("hi"), .string("there")])) + XCTAssertEqual(AnyCodable(["hi": 1]), AnyCodable.object(["hi": .int(1)])) + XCTAssertEqual(AnyCodable([1]), AnyCodable.array([.int(1)])) + XCTAssertEqual(AnyCodable([1.2]), AnyCodable.array([.double(1.2)])) + XCTAssertEqual(AnyCodable([true]), AnyCodable.array([.bool(true)])) + } + + func test_expressible() throws { + XCTAssertEqual(AnyCodable.string("hi"), "hi") + XCTAssertEqual(AnyCodable.bool(true), true) + XCTAssertEqual(AnyCodable.null, nil) + XCTAssertEqual(AnyCodable.int(2), 2) + XCTAssertEqual(AnyCodable.double(3.4), 3.4) + XCTAssertEqual(AnyCodable.object(["hi": .string("there")]), ["hi": "there"]) + } + + func test_equalityFromJSON() throws { + let json = """ + { + "boolean": true, + "integer": 1, + "string": "string", + "array": [1, 2, 3], + "nested": { + "a": "alpha", + "b": "bravo", + "c": "charlie" + } + } + """.data(using: .utf8)! + let decoder = JSONDecoder() + let anyCodable0 = try decoder.decode(AnyCodable.self, from: json) + let anyCodable1 = try decoder.decode(AnyCodable.self, from: json) + XCTAssertEqual(anyCodable0, anyCodable1) + } + + func test_VoidDescription() { + XCTAssertEqual(String(describing: AnyCodable(())), "nil") + } + + func test_JSONDecodingByKeys() throws { let json = """ { "boolean": true, @@ -64,15 +97,53 @@ class AnyCodableTests: XCTestCase { let decoder = JSONDecoder() let dictionary = try decoder.decode([String: AnyCodable].self, from: json) - XCTAssertEqual(dictionary["boolean"]?.value as! Bool, true) - XCTAssertEqual(dictionary["integer"]?.value as! Int, 1) - XCTAssertEqual(dictionary["double"]?.value as! Double, 3.14159265358979323846, accuracy: 0.001) - XCTAssertEqual(dictionary["string"]?.value as! String, "string") - XCTAssertEqual(dictionary["array"]?.value as! [Int], [1, 2, 3]) - XCTAssertEqual(dictionary["nested"]?.value as! [String: String], ["a": "alpha", "b": "bravo", "c": "charlie"]) + XCTAssertEqual(dictionary["boolean"], true) + XCTAssertEqual(dictionary["integer"], 1) + XCTAssertEqual(dictionary["double"]!.double!, 3.14159265358979323846, accuracy: 0.001) + XCTAssertEqual(dictionary["string"], "string") + XCTAssertEqual(dictionary["array"], [1, 2, 3]) + XCTAssertEqual(dictionary["nested"], ["a": "alpha", "b": "bravo", "c": "charlie"]) } - func testJSONEncoding() throws { + func test_JSONDecodingFull() throws { + let json = """ + { + "boolean": true, + "integer": 1, + "string": "string", + "array": [1, 2, 3], + "nested": { + "a": "alpha", + "b": "bravo", + "c": "charlie" + } + } + """.data(using: .utf8)! + + let decoder = JSONDecoder() + let anyCodable = try decoder.decode(AnyCodable.self, from: json) + + XCTAssertEqual( + anyCodable, + .object( + [ + "boolean": .bool(true), + "integer": .int(1), + "string": .string("string"), + "array": .array([.int(1), .int(2), .int(3)]), + "nested": .object( + [ + "a": .string("alpha"), + "b": .string("bravo"), + "c": .string("charlie") + ] + ), + ] + ) + ) + } + + func test_JSONEncoding() throws { let dictionary: [String: AnyCodable] = [ "boolean": true, "integer": 1, @@ -109,27 +180,6 @@ class AnyCodableTests: XCTestCase { ) } - func testEncodeNSNumber() throws { - #if os(macOS) - let dictionary: [String: NSNumber] = [ - "boolean": true, - "integer": 1, - ] - - let result = try testStringFromEncoding(of: AnyCodable(dictionary)) - - assertJSONEquivalent( - result, - """ - { - "boolean" : true, - "integer" : 1 - } - """ - ) - #endif - } - let testEncoder: JSONEncoder = { let encoder = JSONEncoder() if #available(macOS 10.13, *) { @@ -177,8 +227,201 @@ class AnyCodableTests: XCTestCase { XCTAssertEqual(string, #"{"value":"https:\/\/hello.com"}"#) } + + func test_encodeEncodable() throws { + let anyCodable = try AnyCodable.encoded(EncodableStruct()) + let expectedAnyCodable: AnyCodable = [ + "int": 1, + "string": "hello", + "bool": true, + "array": [1, 2, 3], + "dictionary": ["a": 1, "b": 2, "c": 3], + "data": "aGVsbG8=", + "decimal": 10.0, + "url": "https://google.com" + ] + XCTAssertEqual(anyCodable, expectedAnyCodable) + + let data = try JSONEncoder().encode(anyCodable) + let decodedValue = try JSONDecoder().decode(EncodableStruct.self, from: data) + XCTAssertEqual(decodedValue, EncodableStruct()) + } + + func test_dateEncodableInit() throws { + let anyCodable = try AnyCodable.encoded( + EncodableStruct(), + keyEncodingStrategy: .default + ) + let expectedAnyCodable: AnyCodable = [ + "int": 1, + "string": "hello", + "bool": true, + "array": [1, 2, 3], + "dictionary": ["a": 1, "b": 2, "c": 3], + "data": "aGVsbG8=", + "decimal": 10.0, + "url": "https://google.com" + ] + XCTAssertEqual(anyCodable, expectedAnyCodable) + + let data = try JSONEncoder().encode(anyCodable) + let decodedValue = try JSONDecoder().decode(EncodableStruct.self, from: data) + XCTAssertEqual(decodedValue, EncodableStruct()) + } + + func test_keyEncodingStrategy() throws { + let camelCaseString = "thisIsCamelCase" + XCTAssertEqual(KeyEncodingStrategy.convertToSnakeCase.encode(camelCaseString), "this_is_camel_case") + + let anyCodable = try AnyCodable.encoded(StructWithLargeNameProperty(), keyEncodingStrategy: .convertToSnakeCase) + let expectedAnyCodable: AnyCodable = [ + "this_is_a_very_long_property_name_that_will_be_encoded": "hello", + ] + XCTAssertEqual(anyCodable, expectedAnyCodable) + } + + func test_dateEncodingStrategies() throws { + let date = Date(timeIntervalSince1970: 0) + let anyCodable = try AnyCodable.encoded(date, valueEncodingStrategies: [.Date.timestamp]) + let expectedAnyCodable: AnyCodable = 0.0 + XCTAssertEqual(anyCodable, expectedAnyCodable) + + let data = try JSONEncoder().encode([anyCodable]) // Use array for swift 5.1 support + let string = String(data: data, encoding: .utf8) + XCTAssertEqual(string, "[0]") + + let anyCodable3 = try AnyCodable.encoded(date, valueEncodingStrategies: [.Date.dateTime]) + let expectedAnyCodable3: AnyCodable = "1970-01-01T00:00:00.000Z" + XCTAssertEqual(anyCodable3, expectedAnyCodable3) + + let data3 = try JSONEncoder().encode([anyCodable3]) // Use array for swift 5.1 support + let string3 = String(data: data3, encoding: .utf8) + XCTAssertEqual(string3, #"["1970-01-01T00:00:00.000Z"]"#) + + let anyCodable4 = try AnyCodable.encoded(date, valueEncodingStrategies: [.Date.date]) + let expectedAnyCodable4: AnyCodable = "1970-01-01" + XCTAssertEqual(anyCodable4, expectedAnyCodable4) + + let data4 = try JSONEncoder().encode([anyCodable4]) // Use array for swift 5.1 support + let string4 = String(data: data4, encoding: .utf8) + XCTAssertEqual(string4, #"["1970-01-01"]"#) + } + + func test_dataEncodingStrategies() throws { + let data = Data([0x01, 0x02, 0x03]) + let anyCodable1 = try AnyCodable.encoded(data, valueEncodingStrategies: [.Data.base64]) + let expectedAnyCodable1: AnyCodable = "AQID" + XCTAssertEqual(anyCodable1, expectedAnyCodable1) + + let data1 = try JSONEncoder().encode([anyCodable1]) // Use array for swift 5.1 support + let string1 = String(data: data1, encoding: .utf8) + XCTAssertEqual(string1, #"["AQID"]"#) + + let anyCodable2 = try AnyCodable.encoded(data, valueEncodingStrategies: [.Data.base64(options: .endLineWithCarriageReturn)]) + let expectedAnyCodable2: AnyCodable = "AQID" + XCTAssertEqual(anyCodable2, expectedAnyCodable2) + + let data2 = try JSONEncoder().encode([anyCodable2]) // Use array for swift 5.1 support + let string2 = String(data: data2, encoding: .utf8) + XCTAssertEqual(string2, #"["AQID"]"#) + } + + func test_urlEncodingStrategies() throws { + let url = URL(string: "https://google.com")! + let anyCodable = try AnyCodable.encoded(url, valueEncodingStrategies: [.URL.uri]) + let expectedAnyCodable: AnyCodable = "https://google.com" + XCTAssertEqual(anyCodable, expectedAnyCodable) + + let data = try JSONEncoder().encode([anyCodable]) // Use array for swift 5.1 support + let string = String(data: data, encoding: .utf8) + XCTAssertEqual(string, #"["https:\/\/google.com"]"#) + } + + func test_decimalEncodingStrategies() throws { + let decimal = Decimal(10) + + let anyCodable1 = try AnyCodable.encoded(decimal, valueEncodingStrategies: [.Decimal.number]) + let expectedAnyCodable1: AnyCodable = 10.0 + XCTAssertEqual(anyCodable1, expectedAnyCodable1) + + let data1 = try JSONEncoder().encode([anyCodable1]) // Use array for swift 5.1 support + let string1 = String(data: data1, encoding: .utf8) + XCTAssertEqual(string1, "[10]") + + let anyCodable2 = try AnyCodable.encoded(decimal, valueEncodingStrategies: [.Decimal.string]) + let expectedAnyCodable2: AnyCodable = "10" + XCTAssertEqual(anyCodable2, expectedAnyCodable2) + + let data2 = try JSONEncoder().encode([anyCodable2]) // Use array for swift 5.1 support + let string2 = String(data: data2, encoding: .utf8) + XCTAssertEqual(string2, #"["10"]"#) + } + + func test_RefInit() { + var value = 0 + let ref = Ref(get: { + value + }, set: { + value = $0 + }) + XCTAssertEqual(ref.wrappedValue, 0) + ref.wrappedValue = 1 + XCTAssertEqual(ref.wrappedValue, 1) + } + + func test_RefConstant() { + let ref = Ref.constant(1) + XCTAssertEqual(ref.wrappedValue, 1) + ref.wrappedValue = 2 + XCTAssertEqual(ref.wrappedValue, 1) + } + + func test_RefByKeyPath() { + let ref = Ref(DateFormatter(), \.dateFormat) + ref.wrappedValue = "yyyy-MM-dd" + XCTAssertEqual(ref.wrappedValue, "yyyy-MM-dd") + ref.wrappedValue = "yyyy-MM-dd'T'HH:mm:ss.SSSZ" + XCTAssertEqual(ref.wrappedValue, "yyyy-MM-dd'T'HH:mm:ss.SSSZ") + } + + func test_description() { + XCTAssertEqual(AnyCodable(0).description, "0") + XCTAssertEqual(AnyCodable(0.0).description, "0.0") + XCTAssertEqual(AnyCodable(()).description, "nil") + XCTAssertEqual(AnyCodable("hello").description, "\"hello\"") + XCTAssertEqual(AnyCodable(true).description, "true") + XCTAssertEqual(AnyCodable(false).description, "false") + XCTAssertEqual(AnyCodable([1, 2, 3]).description, "[1, 2, 3]") + XCTAssertEqual(AnyCodable(["a": 1, "b": 2]).description, "[\"a\": 1, \"b\": 2]") + } + + func test_debugDescription() { + XCTAssertEqual(AnyCodable(0).debugDescription, "AnyCodable(0)") + XCTAssertEqual(AnyCodable(0.0).debugDescription, "AnyCodable(0.0)") + XCTAssertEqual(AnyCodable(()).debugDescription, "AnyCodable(nil)") + XCTAssertEqual(AnyCodable("hello").debugDescription, "AnyCodable(\"hello\")") + XCTAssertEqual(AnyCodable(true).debugDescription, "AnyCodable(true)") + XCTAssertEqual(AnyCodable(false).debugDescription, "AnyCodable(false)") + XCTAssertEqual(AnyCodable([1, 2, 3]).debugDescription, "AnyCodable([1, 2, 3])") + XCTAssertEqual(AnyCodable(["a": 1, "b": 2]).debugDescription, "AnyCodable([\"a\": 1, \"b\": 2])") + } } -fileprivate struct Wrapper: Codable { +private struct Wrapper: Codable { let value: AnyCodable } + +private struct EncodableStruct: Codable, Equatable { + var int = 1 + var string = "hello" + var bool = true + var array = [1, 2, 3] + var dictionary = ["a": 1, "b": 2, "c": 3] + var data = "hello".data(using: .utf8) + var decimal = Decimal(10) + var url = URL(string: "https://google.com")! +} + +private struct StructWithLargeNameProperty: Codable { + var thisIsAVeryLongPropertyNameThatWillBeEncoded: String = "hello" +} diff --git a/Tests/OpenAPIKit30Tests/Content/ContentTests.swift b/Tests/OpenAPIKit30Tests/Content/ContentTests.swift index b6c92b549..6e9af34a1 100644 --- a/Tests/OpenAPIKit30Tests/Content/ContentTests.swift +++ b/Tests/OpenAPIKit30Tests/Content/ContentTests.swift @@ -44,7 +44,7 @@ final class ContentTests: XCTestCase { XCTAssertNotNil(withExamples.examples) // we expect the example to be the first example where ordering // is the order in which the examples are given: - XCTAssertEqual(withExamples.example?.value as? String, "world") + XCTAssertEqual(withExamples.example, "world") XCTAssertEqual(withExamples.examples?["hello"]?.exampleValue, .init(value: .init("world"))) let t4 = OpenAPI.Content( @@ -255,7 +255,7 @@ extension ContentTests { XCTAssertEqual(content.schema, .init(.object(properties: ["hello": .string]))) - XCTAssertEqual(content.example?.value as? [String: String], [ "hello": "world" ]) + XCTAssertEqual(content.example?.object, [ "hello": "world" ]) } func test_examplesAndSchemaContent_encode() { @@ -318,8 +318,8 @@ extension ContentTests { XCTAssertEqual(content.schema, .init(.object(properties: ["hello": .string]))) - XCTAssertEqual(content.example?.value as? [String: String], [ "hello": "world" ]) - XCTAssertEqual(content.examples?["hello"]?.exampleValue?.value?.codableValue?.value as? [String: String], [ "hello": "world" ]) + XCTAssertEqual(content.example?.object, [ "hello": "world" ]) + XCTAssertEqual(content.examples?["hello"]?.exampleValue?.value?.codableValue?.object, [ "hello": "world" ]) } func test_decodeFailureForBothExampleAndExamples() { @@ -475,7 +475,7 @@ extension ContentTests { // created from code with a semantically equivalent AnyCodable from Data. XCTAssertEqual(content.schema, contentToMatch.schema) XCTAssertEqual(content.vendorExtensions.keys, contentToMatch.vendorExtensions.keys) - XCTAssertEqual(content.vendorExtensions["x-hello"]?.value as? [String: Int], contentToMatch.vendorExtensions["x-hello"]?.value as? [String: Int]?) + XCTAssertEqual(content.vendorExtensions["x-hello"]?.object, contentToMatch.vendorExtensions["x-hello"]?.object) } func test_nonStringKeyNonesenseDecodeFailure() { diff --git a/Tests/OpenAPIKit30Tests/ExampleTests.swift b/Tests/OpenAPIKit30Tests/ExampleTests.swift index 2343eaa23..b1b75373d 100644 --- a/Tests/OpenAPIKit30Tests/ExampleTests.swift +++ b/Tests/OpenAPIKit30Tests/ExampleTests.swift @@ -21,7 +21,7 @@ final class ExampleTests: XCTestCase { XCTAssertEqual(full1.summary, "hello") XCTAssertEqual(full1.description, "world") XCTAssertEqual(full1.value, .init(URL(string: "https://google.com")!)) - XCTAssertEqual(full1.vendorExtensions["hello"]?.value as? String, "world") + XCTAssertEqual(full1.vendorExtensions["hello"], "world") let full2 = OpenAPI.Example( summary: "hello", @@ -33,7 +33,7 @@ final class ExampleTests: XCTestCase { XCTAssertEqual(full2.summary, "hello") XCTAssertEqual(full2.description, "world") XCTAssertEqual(full2.value, .init("hello")) - XCTAssertEqual(full2.vendorExtensions["hello"]?.value as? String, "world") + XCTAssertEqual(full2.vendorExtensions["hello"], "world") let small = OpenAPI.Example(value: .init("hello")) XCTAssertNil(small.summary) diff --git a/Tests/OpenAPIKit30Tests/Operation/ResolvedEndpointTests.swift b/Tests/OpenAPIKit30Tests/Operation/ResolvedEndpointTests.swift index 43895cdfb..64a767c43 100644 --- a/Tests/OpenAPIKit30Tests/Operation/ResolvedEndpointTests.swift +++ b/Tests/OpenAPIKit30Tests/Operation/ResolvedEndpointTests.swift @@ -47,11 +47,11 @@ final class ResolvedEndpointTests: XCTestCase { XCTAssertEqual(endpoints.first?.routeSummary, "routeSummary") XCTAssertEqual(endpoints.first?.routeDescription, "routeDescription") - XCTAssertEqual(endpoints.first?.routeVendorExtensions["test"]?.value as? String, "route") + XCTAssertEqual(endpoints.first?.routeVendorExtensions["test"], "route") XCTAssertEqual(endpoints.first?.tags, ["a", "b"]) XCTAssertEqual(endpoints.first?.endpointSummary, "endpointSummary") XCTAssertEqual(endpoints.first?.endpointDescription, "endpointDescription") - XCTAssertEqual(endpoints.first?.endpointVendorExtensions["test"]?.value as? String, "endpoint") + XCTAssertEqual(endpoints.first?.endpointVendorExtensions["test"], "endpoint") XCTAssertEqual(endpoints.first?.operationId, "hi there") XCTAssertEqual(endpoints.first?.externalDocs, .init(url: URL(string: "http://website.com")!)) XCTAssertEqual(endpoints.first?.method, .get) diff --git a/Tests/OpenAPIKit30Tests/Parameter/ParameterSchemaTests.swift b/Tests/OpenAPIKit30Tests/Parameter/ParameterSchemaTests.swift index ed6121ae9..67a79828c 100644 --- a/Tests/OpenAPIKit30Tests/Parameter/ParameterSchemaTests.swift +++ b/Tests/OpenAPIKit30Tests/Parameter/ParameterSchemaTests.swift @@ -25,7 +25,7 @@ final class ParameterSchemaTests: XCTestCase { XCTAssertEqual(t1.style, .form) XCTAssertFalse(t1.explode) XCTAssertTrue(t1.allowReserved) - XCTAssertEqual(t1.example?.value as? String, "hello") + XCTAssertEqual(t1.example, "hello") XCTAssertNil(t1.examples) // init with defaults @@ -110,9 +110,9 @@ final class ParameterSchemaTests: XCTestCase { XCTAssertFalse(t7.explode) XCTAssertFalse(t7.allowReserved) XCTAssertNotNil(t7.example) - XCTAssertEqual(t7.example?.value as? String, "hello") + XCTAssertEqual(t7.example, "hello") XCTAssertNotNil(t7.examples) - XCTAssertEqual(t7.examples?["two"]?.exampleValue?.value?.codableValue?.value as? String, "world") + XCTAssertEqual(t7.examples?["two"]?.exampleValue?.value?.codableValue, "world") // straight to schema override explode multiple examples let t8 = Schema( @@ -130,9 +130,9 @@ final class ParameterSchemaTests: XCTestCase { XCTAssertTrue(t8.explode) XCTAssertFalse(t8.allowReserved) XCTAssertNotNil(t8.example) - XCTAssertEqual(t8.example?.value as? String, "hello") + XCTAssertEqual(t8.example, "hello") XCTAssertNotNil(t8.examples) - XCTAssertEqual(t8.examples?["two"]?.exampleValue?.value?.codableValue?.value as? String, "world") + XCTAssertEqual(t8.examples?["two"]?.exampleValue?.value?.codableValue, "world") // schema reference multiple examples let t9 = Schema( @@ -149,7 +149,7 @@ final class ParameterSchemaTests: XCTestCase { XCTAssertFalse(t9.explode) XCTAssertFalse(t9.allowReserved) XCTAssertNotNil(t9.example) - XCTAssertEqual(t9.example?.value as? String, "hello") + XCTAssertEqual(t9.example, "hello") XCTAssertNotNil(t9.examples) XCTAssertEqual(t9.examples?["two"]?.reference, .external(URL(string: "world.yml")!)) @@ -169,7 +169,7 @@ final class ParameterSchemaTests: XCTestCase { XCTAssertTrue(t10.explode) XCTAssertFalse(t10.allowReserved) XCTAssertNotNil(t10.example) - XCTAssertEqual(t10.example?.value as? String, "hello") + XCTAssertEqual(t10.example, "hello") XCTAssertNotNil(t10.examples) XCTAssertEqual(t10.examples?["two"]?.reference, .external(URL(string: "world.yml")!)) } diff --git a/Tests/OpenAPIKit30Tests/Path Item/ResolvedRouteTests.swift b/Tests/OpenAPIKit30Tests/Path Item/ResolvedRouteTests.swift index 680c6a96b..079d3ba49 100644 --- a/Tests/OpenAPIKit30Tests/Path Item/ResolvedRouteTests.swift +++ b/Tests/OpenAPIKit30Tests/Path Item/ResolvedRouteTests.swift @@ -65,7 +65,7 @@ final class ResolvedRouteTests: XCTestCase { XCTAssertEqual(routes.first?.summary, "routeSummary") XCTAssertEqual(routes.first?.description, "routeDescription") - XCTAssertEqual(routes.first?.vendorExtensions["test"]?.value as? String, "route") + XCTAssertEqual(routes.first?.vendorExtensions["test"], "route") XCTAssertEqual(routes.first?.path, "/hello/world/{id}") XCTAssertEqual(routes.first?.parameters.map { $0.name }, ["id"]) XCTAssertEqual(routes.first?.get?.endpointSummary, "get") diff --git a/Tests/OpenAPIKit30Tests/Schema Object/JSONSchemaTests.swift b/Tests/OpenAPIKit30Tests/Schema Object/JSONSchemaTests.swift index 673a69a2b..1a149d956 100644 --- a/Tests/OpenAPIKit30Tests/Schema Object/JSONSchemaTests.swift +++ b/Tests/OpenAPIKit30Tests/Schema Object/JSONSchemaTests.swift @@ -1066,7 +1066,7 @@ final class SchemaObjectTests: XCTestCase { XCTAssertEqual(boolean.allowedValues, [false]) XCTAssertEqual(object.allowedValues, [[:]]) - XCTAssertEqual(array.allowedValues?[0].value as! [Bool], [false]) + XCTAssertEqual(array.allowedValues?[0].array, [false]) XCTAssertEqual(number.allowedValues, [2.5]) XCTAssertEqual(integer.allowedValues, [5]) XCTAssertEqual(string.allowedValues, ["hello"]) @@ -1101,8 +1101,8 @@ final class SchemaObjectTests: XCTestCase { .with(allowedValues: ["hello"]) XCTAssertEqual(boolean.allowedValues, [false]) - XCTAssertEqual(object.allowedValues, [AnyCodable([String: String]())]) - XCTAssertEqual(array.allowedValues?[0].value as! [Bool], [false]) + XCTAssertEqual(object.allowedValues, [AnyCodable.object([:])]) + XCTAssertEqual(array.allowedValues?[0].array, [false]) XCTAssertEqual(number.allowedValues, [2.5]) XCTAssertEqual(integer.allowedValues, [5]) XCTAssertEqual(string.allowedValues, ["hello"]) @@ -1162,7 +1162,7 @@ final class SchemaObjectTests: XCTestCase { .with(defaultValue: "hello") XCTAssertEqual(boolean.defaultValue, false) - XCTAssertEqual(object.defaultValue, AnyCodable([String: String]())) + XCTAssertEqual(object.defaultValue, AnyCodable.object([:])) XCTAssertEqual(array.defaultValue, [false]) XCTAssertEqual(number.defaultValue, 2.5) XCTAssertEqual(integer.defaultValue, 5) @@ -1188,8 +1188,8 @@ final class SchemaObjectTests: XCTestCase { let not = JSONSchema.not(.string) let ref = JSONSchema.reference(.external(URL(string: "hello.yml")!)) - XCTAssertEqual(object.example?.value as? [String:String], [:]) - XCTAssertEqual(fragment.example?.value as? String, "hi") + XCTAssertEqual(object.example, [:]) + XCTAssertEqual(fragment.example, "hi") XCTAssertNil(all.example) XCTAssertNil(one.example) @@ -1228,20 +1228,20 @@ final class SchemaObjectTests: XCTestCase { XCTAssertThrowsError(try JSONSchema.reference(.external(URL(string: "hello/world.json#/hello")!)) .with(example: ["hello"])) - XCTAssertEqual(object.example?.value as? [String: String], [:]) - XCTAssertEqual(array.example?.value as? [String], ["hello"]) + XCTAssertEqual(object.example, [:]) + XCTAssertEqual(array.example, ["hello"]) - XCTAssertEqual(boolean.example?.value as? Bool, true) - XCTAssertEqual(double.example?.value as? Double, 10.5) - XCTAssertEqual(float.example?.value as? Float, 2.5 as Float) - XCTAssertEqual(integer.example?.value as? Int, 3) - XCTAssertEqual(string.example?.value as? String, "hello world") + XCTAssertEqual(boolean.example, true) + XCTAssertEqual(double.example, 10.5) + XCTAssertEqual(float.example, 2.5) + XCTAssertEqual(integer.example, 3) + XCTAssertEqual(string.example, "hello world") - XCTAssertEqual(allOf.example?.value as? [String], ["hello"]) - XCTAssertEqual(anyOf.example?.value as? [String], ["hello"]) - XCTAssertEqual(oneOf.example?.value as? [String], ["hello"]) - XCTAssertEqual(not.example?.value as? [String], ["hello"]) - XCTAssertEqual(fragment.example?.value as? String, "hi") + XCTAssertEqual(allOf.example, ["hello"]) + XCTAssertEqual(anyOf.example, ["hello"]) + XCTAssertEqual(oneOf.example, ["hello"]) + XCTAssertEqual(not.example, ["hello"]) + XCTAssertEqual(fragment.example, "hi") } func test_withDiscriminator() throws { @@ -1788,7 +1788,7 @@ extension SchemaObjectTests { XCTAssertEqual(readOnlyObject, JSONSchema.object(.init(format: .generic, permissions: .readOnly), .init(properties: [:]))) XCTAssertEqual(writeOnlyObject, JSONSchema.object(.init(format: .generic, permissions: .writeOnly), .init(properties: [:]))) XCTAssertEqual(deprecatedObject, JSONSchema.object(.init(format: .generic, deprecated: true), .init(properties: [:]))) - XCTAssertEqual(allowedValueObject.allowedValues?[0].value as! [String: Bool], ["hello": false]) + XCTAssertEqual(allowedValueObject.allowedValues?[0].object, ["hello": false]) XCTAssertEqual(allowedValueObject.jsonTypeFormat, .object(.generic)) XCTAssertEqual(defaultValueObject.defaultValue, ["hello": false]) XCTAssertEqual(discriminatorObject, JSONSchema.object(discriminator: .init(propertyName: "hello"))) @@ -1917,7 +1917,7 @@ extension SchemaObjectTests { XCTAssertEqual(object, JSONSchema.object(.init(format: .generic, title: "hello"), .init(properties: [:]))) XCTAssertEqual(nullableObject, JSONSchema.object(.init(format: .generic, nullable: true, title: "hello"), .init(properties: [:]))) - XCTAssertEqual(allowedValueObject.allowedValues?[0].value as! [String: Bool], ["hello": false]) + XCTAssertEqual(allowedValueObject.allowedValues?[0].object, ["hello": false]) XCTAssertEqual(allowedValueObject.jsonTypeFormat, .object(.generic)) XCTAssertEqual(allowedValueObject.title, "hello") @@ -2025,7 +2025,7 @@ extension SchemaObjectTests { XCTAssertEqual(object, JSONSchema.object(.init(format: .generic, description: "hello"), .init(properties: [:]))) XCTAssertEqual(nullableObject, JSONSchema.object(.init(format: .generic, nullable: true, description: "hello"), .init(properties: [:]))) - XCTAssertEqual(allowedValueObject.allowedValues?[0].value as! [String: Bool], ["hello": false]) + XCTAssertEqual(allowedValueObject.allowedValues?[0].object, ["hello": false]) XCTAssertEqual(allowedValueObject.jsonTypeFormat, .object(.generic)) XCTAssertEqual(allowedValueObject.description, "hello") @@ -2141,7 +2141,7 @@ extension SchemaObjectTests { XCTAssertEqual(object, JSONSchema.object(.init(format: .generic, externalDocs: .init(url: URL(string: "http://google.com")!)), .init(properties: [:]))) XCTAssertEqual(nullableObject, JSONSchema.object(.init(format: .generic, nullable: true, externalDocs: .init(url: URL(string: "http://google.com")!)), .init(properties: [:]))) - XCTAssertEqual(allowedValueObject.allowedValues?[0].value as! [String: Bool], ["hello": false]) + XCTAssertEqual(allowedValueObject.allowedValues?[0].object, ["hello": false]) XCTAssertEqual(allowedValueObject.jsonTypeFormat, .object(.generic)) XCTAssertEqual(allowedValueObject.externalDocs, .init(url: URL(string: "http://google.com")!)) @@ -2249,7 +2249,7 @@ extension SchemaObjectTests { XCTAssertEqual(object, JSONSchema.object(.init(format: .generic), .init(properties: [:], maxProperties: 1))) XCTAssertEqual(nullableObject, JSONSchema.object(.init(format: .generic, nullable: true), .init(properties: [:], maxProperties: 1))) - XCTAssertEqual(allowedValueObject.allowedValues?[0].value as! [String: Bool], ["hello": false]) + XCTAssertEqual(allowedValueObject.allowedValues?[0].object, ["hello": false]) XCTAssertEqual(allowedValueObject.jsonTypeFormat, .object(.generic)) guard case let .object(_, contextB) = allowedValueObject.value else { @@ -2356,7 +2356,7 @@ extension SchemaObjectTests { XCTAssertEqual(object, JSONSchema.object(.init(format: .generic), .init(properties: [:], minProperties: 1))) XCTAssertEqual(nullableObject, JSONSchema.object(.init(format: .generic, nullable: true), .init(properties: [:], minProperties: 1))) - XCTAssertEqual(allowedValueObject.allowedValues?[0].value as! [String: Bool], ["hello": false]) + XCTAssertEqual(allowedValueObject.allowedValues?[0].object, ["hello": false]) XCTAssertEqual(allowedValueObject.jsonTypeFormat, .object(.generic)) guard case let .object(_, contextB) = allowedValueObject.value else { @@ -2463,7 +2463,7 @@ extension SchemaObjectTests { XCTAssertEqual(object, JSONSchema.object(.init(format: .generic), .init(properties: [:], additionalProperties: .init(true)))) XCTAssertEqual(nullableObject, JSONSchema.object(.init(format: .generic, nullable: true), .init(properties: [:], additionalProperties: .init(true)))) - XCTAssertEqual(allowedValueObject.allowedValues?[0].value as! [String: Bool], ["hello": false]) + XCTAssertEqual(allowedValueObject.allowedValues?[0].object, ["hello": false]) XCTAssertEqual(allowedValueObject.jsonTypeFormat, .object(.generic)) guard case let .object(_, contextB) = allowedValueObject.value else { @@ -2579,7 +2579,7 @@ extension SchemaObjectTests { XCTAssertEqual(object, JSONSchema.object(.init(format: .generic), .init(properties: [:], additionalProperties: .init(.string)))) XCTAssertEqual(nullableObject, JSONSchema.object(.init(format: .generic, nullable: true), .init(properties: [:], additionalProperties: .init(.string)))) - XCTAssertEqual(allowedValueObject.allowedValues?[0].value as! [String: Bool], ["hello": false]) + XCTAssertEqual(allowedValueObject.allowedValues?[0].object, ["hello": false]) XCTAssertEqual(allowedValueObject.jsonTypeFormat, .object(.generic)) guard case let .object(_, contextB) = allowedValueObject.value else { @@ -2725,7 +2725,7 @@ extension SchemaObjectTests { XCTAssertEqual(string, JSONSchema.string(.init(format: .unspecified, example: "hello"), .init())) XCTAssertEqual(object, JSONSchema.object(.init(format: .generic, example: AnyCodable(["hello": true])), .init(properties: [:]))) XCTAssertEqual(nullableObject, JSONSchema.object(.init(format: .generic, nullable: true, example: AnyCodable(["hello": true])), .init(properties: [:]))) - XCTAssertEqual(allowedValueObject.allowedValues?[0].value as! [String: Bool], ["hello": false]) + XCTAssertEqual(allowedValueObject.allowedValues?[0].object, ["hello": false]) XCTAssertEqual(allowedValueObject.jsonTypeFormat, .object(.generic)) XCTAssertEqual(allowedValueObject.example, ["hello" : true]) @@ -2935,7 +2935,7 @@ extension SchemaObjectTests { XCTAssertEqual(object, JSONSchema.object(.init(format: .generic), .init(properties: ["hello": .boolean(.init(format: .generic))]))) XCTAssertEqual(nullableObject, JSONSchema.object(.init(format: .generic, nullable: true), .init(properties: ["hello": .boolean(.init(format: .generic))]))) - XCTAssertEqual(allowedValueObject.allowedValues?[0].value as! [String: Bool], ["hello": false]) + XCTAssertEqual(allowedValueObject.allowedValues?[0].object, ["hello": false]) XCTAssertEqual(allowedValueObject.jsonTypeFormat, .object(.generic)) guard case let .object(_, contextB) = allowedValueObject.value else { @@ -3167,7 +3167,7 @@ extension SchemaObjectTests { XCTAssertEqual(readOnlyArray, JSONSchema.array(.init(format: .generic, permissions: .readOnly), .init())) XCTAssertEqual(writeOnlyArray, JSONSchema.array(.init(format: .generic, permissions: .writeOnly), .init())) XCTAssertEqual(deprecatedArray, JSONSchema.array(.init(format: .generic, deprecated: true), .init())) - XCTAssertEqual(allowedValueArray.allowedValues?[0].value as! [Bool], [false]) + XCTAssertEqual(allowedValueArray.allowedValues?[0].array, [false]) XCTAssertEqual(defaultValueArray.defaultValue, [false]) XCTAssertEqual(discriminatorArray, JSONSchema.array(discriminator: .init(propertyName: "hello"))) @@ -3253,7 +3253,7 @@ extension SchemaObjectTests { XCTAssertEqual(array, JSONSchema.array(.init(format: .generic), .init(items: .boolean(.init(format: .generic))))) XCTAssertEqual(nullableArray, JSONSchema.array(.init(format: .generic, nullable: true), .init(items: .boolean(.init(format: .generic))))) - XCTAssertEqual(allowedValueArray.allowedValues?[0].value as! [Bool], [false]) + XCTAssertEqual(allowedValueArray.allowedValues?[0].array, [false]) guard case let .array(_, contextB) = allowedValueArray.value else { XCTFail("expected array") @@ -3331,7 +3331,7 @@ extension SchemaObjectTests { XCTAssertEqual(array, JSONSchema.array(.init(format: .generic), .init(uniqueItems: true))) XCTAssertEqual(nullableArray, JSONSchema.array(.init(format: .generic, nullable: true), .init(uniqueItems: true))) - XCTAssertEqual(allowedValueArray.allowedValues?[0].value as! [Bool], [false]) + XCTAssertEqual(allowedValueArray.allowedValues?[0].array, [false]) XCTAssertEqual(array.arrayContext?.uniqueItems, true) XCTAssertEqual(nullableArray.arrayContext?.uniqueItems, true) @@ -3393,7 +3393,7 @@ extension SchemaObjectTests { XCTAssertEqual(array, JSONSchema.array(.init(format: .generic), .init(maxItems: 3))) XCTAssertEqual(nullableArray, JSONSchema.array(.init(format: .generic, nullable: true), .init(maxItems: 3))) - XCTAssertEqual(allowedValueArray.allowedValues?[0].value as! [Bool], [false]) + XCTAssertEqual(allowedValueArray.allowedValues?[0].array, [false]) guard case let .array(_, contextB) = allowedValueArray.value else { XCTFail("expected array") @@ -3451,7 +3451,7 @@ extension SchemaObjectTests { XCTAssertEqual(array, JSONSchema.array(.init(format: .generic), .init(minItems: 2))) XCTAssertEqual(nullableArray, JSONSchema.array(.init(format: .generic, nullable: true), .init(minItems: 2))) - XCTAssertEqual(allowedValueArray.allowedValues?[0].value as! [Bool], [false]) + XCTAssertEqual(allowedValueArray.allowedValues?[0].array, [false]) guard case let .array(_, contextB) = allowedValueArray.value else { XCTFail("expected array") diff --git a/Tests/OpenAPIKit30Tests/Validator/ValidatorTests.swift b/Tests/OpenAPIKit30Tests/Validator/ValidatorTests.swift index 7ccf0caee..216a010fd 100644 --- a/Tests/OpenAPIKit30Tests/Validator/ValidatorTests.swift +++ b/Tests/OpenAPIKit30Tests/Validator/ValidatorTests.swift @@ -1335,9 +1335,8 @@ final class ValidatorTests: XCTestCase { vendorExtensions: [ "x-string": "hiya", "x-double": 10.5, - "x-dict": [ "string": "world"], - "x-array": AnyCodable(["hello", nil, "world"]), - "x-float": AnyCodable(22.5 as Float), + "x-dict": ["string": "world"], + "x-array": ["hello", nil, "world"], "x-bool": true ] ) @@ -1359,7 +1358,6 @@ final class ValidatorTests: XCTestCase { .validating("string", check: \String.self == "hiya", when: \.codingPath.last?.stringValue == "x-string") .validating("int", check: \Int.self == 3) .validating("double", check: \Double.self == 10.5) - .validating("float", check: \Float.self == 22.5) .validating("bool", check: \Bool.self == true) try document.validate(using: validator) diff --git a/Tests/OpenAPIKit30Tests/VendorExtendableTests.swift b/Tests/OpenAPIKit30Tests/VendorExtendableTests.swift index 44ea33cdf..edc7b70f9 100644 --- a/Tests/OpenAPIKit30Tests/VendorExtendableTests.swift +++ b/Tests/OpenAPIKit30Tests/VendorExtendableTests.swift @@ -26,14 +26,14 @@ final class VendorExtendableTests: XCTestCase { let test = try orderUnstableDecode(TestStruct.self, from: data) XCTAssertEqual(test.vendorExtensions.count, 3) - XCTAssertEqual(test.vendorExtensions["x-tension"]?.value as? String, "hello") + XCTAssertEqual(test.vendorExtensions["x-tension"], "hello") - XCTAssert((test.vendorExtensions["x-two"]?.value as? [String])!.contains("cool")) - XCTAssert((test.vendorExtensions["x-two"]?.value as? [String])!.contains("beans")) - XCTAssertEqual((test.vendorExtensions["x-two"]?.value as? [String])?.count, 2) + XCTAssert(test.vendorExtensions["x-two"]!.array!.contains("cool")) + XCTAssert(test.vendorExtensions["x-two"]!.array!.contains("beans")) + XCTAssertEqual(test.vendorExtensions["x-two"]?.array?.count, 2) - XCTAssertEqual((test.vendorExtensions["x-three"]?.value as? [String: Int])?.count, 1) - XCTAssertEqual((test.vendorExtensions["x-three"]?.value as? [String: Int])?["nested"], 10) + XCTAssertEqual(test.vendorExtensions["x-three"]?.object?.count, 1) + XCTAssertEqual(test.vendorExtensions["x-three"]?.object?["nested"], 10) } func test_encodeSuccess() throws { diff --git a/Tests/OpenAPIKitTests/Content/ContentTests.swift b/Tests/OpenAPIKitTests/Content/ContentTests.swift index 357759b45..dedc4338d 100644 --- a/Tests/OpenAPIKitTests/Content/ContentTests.swift +++ b/Tests/OpenAPIKitTests/Content/ContentTests.swift @@ -44,7 +44,7 @@ final class ContentTests: XCTestCase { XCTAssertNotNil(withExamples.examples) // we expect the example to be the first example where ordering // is the order in which the examples are given: - XCTAssertEqual(withExamples.example?.value as? String, "world") + XCTAssertEqual(withExamples.example, "world") XCTAssertEqual(withExamples.examples?["hello"]?.exampleValue, .init(value: .init("world"))) let t4 = OpenAPI.Content( @@ -255,7 +255,7 @@ extension ContentTests { XCTAssertEqual(content.schema, .init(.object(properties: ["hello": .string]))) - XCTAssertEqual(content.example?.value as? [String: String], [ "hello": "world" ]) + XCTAssertEqual(content.example, [ "hello": "world" ]) } func test_examplesAndSchemaContent_encode() { @@ -318,8 +318,8 @@ extension ContentTests { XCTAssertEqual(content.schema, .init(.object(properties: ["hello": .string]))) - XCTAssertEqual(content.example?.value as? [String: String], [ "hello": "world" ]) - XCTAssertEqual(content.examples?["hello"]?.exampleValue?.value?.codableValue?.value as? [String: String], [ "hello": "world" ]) + XCTAssertEqual(content.example, [ "hello": "world" ]) + XCTAssertEqual(content.examples?["hello"]?.exampleValue?.value?.codableValue, [ "hello": "world" ]) } func test_decodeFailureForBothExampleAndExamples() { @@ -475,7 +475,7 @@ extension ContentTests { // created from code with a semantically equivalent AnyCodable from Data. XCTAssertEqual(content.schema, contentToMatch.schema) XCTAssertEqual(content.vendorExtensions.keys, contentToMatch.vendorExtensions.keys) - XCTAssertEqual(content.vendorExtensions["x-hello"]?.value as? [String: Int], contentToMatch.vendorExtensions["x-hello"]?.value as? [String: Int]?) + XCTAssertEqual(content.vendorExtensions["x-hello"]?.array, contentToMatch.vendorExtensions["x-hello"]?.array) } func test_nonStringKeyNonesenseDecodeFailure() { diff --git a/Tests/OpenAPIKitTests/ExampleTests.swift b/Tests/OpenAPIKitTests/ExampleTests.swift index 1550a249c..34551117a 100644 --- a/Tests/OpenAPIKitTests/ExampleTests.swift +++ b/Tests/OpenAPIKitTests/ExampleTests.swift @@ -21,7 +21,7 @@ final class ExampleTests: XCTestCase { XCTAssertEqual(full1.summary, "hello") XCTAssertEqual(full1.description, "world") XCTAssertEqual(full1.value, .init(URL(string: "https://google.com")!)) - XCTAssertEqual(full1.vendorExtensions["hello"]?.value as? String, "world") + XCTAssertEqual(full1.vendorExtensions["hello"], "world") let full2 = OpenAPI.Example( summary: "hello", @@ -33,7 +33,7 @@ final class ExampleTests: XCTestCase { XCTAssertEqual(full2.summary, "hello") XCTAssertEqual(full2.description, "world") XCTAssertEqual(full2.value, .init("hello")) - XCTAssertEqual(full2.vendorExtensions["hello"]?.value as? String, "world") + XCTAssertEqual(full2.vendorExtensions["hello"], "world") let small = OpenAPI.Example(value: .init("hello")) XCTAssertNil(small.summary) diff --git a/Tests/OpenAPIKitTests/Operation/ResolvedEndpointTests.swift b/Tests/OpenAPIKitTests/Operation/ResolvedEndpointTests.swift index 6cd40b9ef..9a1f96763 100644 --- a/Tests/OpenAPIKitTests/Operation/ResolvedEndpointTests.swift +++ b/Tests/OpenAPIKitTests/Operation/ResolvedEndpointTests.swift @@ -47,11 +47,11 @@ final class ResolvedEndpointTests: XCTestCase { XCTAssertEqual(endpoints.first?.routeSummary, "routeSummary") XCTAssertEqual(endpoints.first?.routeDescription, "routeDescription") - XCTAssertEqual(endpoints.first?.routeVendorExtensions["test"]?.value as? String, "route") + XCTAssertEqual(endpoints.first?.routeVendorExtensions["test"], "route") XCTAssertEqual(endpoints.first?.tags, ["a", "b"]) XCTAssertEqual(endpoints.first?.endpointSummary, "endpointSummary") XCTAssertEqual(endpoints.first?.endpointDescription, "endpointDescription") - XCTAssertEqual(endpoints.first?.endpointVendorExtensions["test"]?.value as? String, "endpoint") + XCTAssertEqual(endpoints.first?.endpointVendorExtensions["test"], "endpoint") XCTAssertEqual(endpoints.first?.operationId, "hi there") XCTAssertEqual(endpoints.first?.externalDocs, .init(url: URL(string: "http://website.com")!)) XCTAssertEqual(endpoints.first?.method, .get) diff --git a/Tests/OpenAPIKitTests/Parameter/ParameterSchemaTests.swift b/Tests/OpenAPIKitTests/Parameter/ParameterSchemaTests.swift index 35ea9da84..5adc2dd65 100644 --- a/Tests/OpenAPIKitTests/Parameter/ParameterSchemaTests.swift +++ b/Tests/OpenAPIKitTests/Parameter/ParameterSchemaTests.swift @@ -25,7 +25,7 @@ final class ParameterSchemaTests: XCTestCase { XCTAssertEqual(t1.style, .form) XCTAssertFalse(t1.explode) XCTAssertTrue(t1.allowReserved) - XCTAssertEqual(t1.example?.value as? String, "hello") + XCTAssertEqual(t1.example, "hello") XCTAssertNil(t1.examples) // init with defaults @@ -110,9 +110,9 @@ final class ParameterSchemaTests: XCTestCase { XCTAssertFalse(t7.explode) XCTAssertFalse(t7.allowReserved) XCTAssertNotNil(t7.example) - XCTAssertEqual(t7.example?.value as? String, "hello") + XCTAssertEqual(t7.example, "hello") XCTAssertNotNil(t7.examples) - XCTAssertEqual(t7.examples?["two"]?.exampleValue?.value?.codableValue?.value as? String, "world") + XCTAssertEqual(t7.examples?["two"]?.exampleValue?.value?.codableValue, "world") // straight to schema override explode multiple examples let t8 = Schema( @@ -130,9 +130,9 @@ final class ParameterSchemaTests: XCTestCase { XCTAssertTrue(t8.explode) XCTAssertFalse(t8.allowReserved) XCTAssertNotNil(t8.example) - XCTAssertEqual(t8.example?.value as? String, "hello") + XCTAssertEqual(t8.example, "hello") XCTAssertNotNil(t8.examples) - XCTAssertEqual(t8.examples?["two"]?.exampleValue?.value?.codableValue?.value as? String, "world") + XCTAssertEqual(t8.examples?["two"]?.exampleValue?.value?.codableValue, "world") // schema reference multiple examples let t9 = Schema( @@ -149,7 +149,7 @@ final class ParameterSchemaTests: XCTestCase { XCTAssertFalse(t9.explode) XCTAssertFalse(t9.allowReserved) XCTAssertNotNil(t9.example) - XCTAssertEqual(t9.example?.value as? String, "hello") + XCTAssertEqual(t9.example, "hello") XCTAssertNotNil(t9.examples) XCTAssertEqual(t9.examples?["two"]?.reference, .external(URL(string: "world.yml")!)) @@ -169,7 +169,7 @@ final class ParameterSchemaTests: XCTestCase { XCTAssertTrue(t10.explode) XCTAssertFalse(t10.allowReserved) XCTAssertNotNil(t10.example) - XCTAssertEqual(t10.example?.value as? String, "hello") + XCTAssertEqual(t10.example, "hello") XCTAssertNotNil(t10.examples) XCTAssertEqual(t10.examples?["two"]?.reference, .external(URL(string: "world.yml")!)) } diff --git a/Tests/OpenAPIKitTests/Path Item/ResolvedRouteTests.swift b/Tests/OpenAPIKitTests/Path Item/ResolvedRouteTests.swift index 362b72eac..5e4e0c5bb 100644 --- a/Tests/OpenAPIKitTests/Path Item/ResolvedRouteTests.swift +++ b/Tests/OpenAPIKitTests/Path Item/ResolvedRouteTests.swift @@ -65,7 +65,7 @@ final class ResolvedRouteTests: XCTestCase { XCTAssertEqual(routes.first?.summary, "routeSummary") XCTAssertEqual(routes.first?.description, "routeDescription") - XCTAssertEqual(routes.first?.vendorExtensions["test"]?.value as? String, "route") + XCTAssertEqual(routes.first?.vendorExtensions["test"], "route") XCTAssertEqual(routes.first?.path, "/hello/world/{id}") XCTAssertEqual(routes.first?.parameters.map { $0.name }, ["id"]) XCTAssertEqual(routes.first?.get?.endpointSummary, "get") diff --git a/Tests/OpenAPIKitTests/Schema Object/JSONSchemaTests.swift b/Tests/OpenAPIKitTests/Schema Object/JSONSchemaTests.swift index e2fca0dd0..7de32bbf4 100644 --- a/Tests/OpenAPIKitTests/Schema Object/JSONSchemaTests.swift +++ b/Tests/OpenAPIKitTests/Schema Object/JSONSchemaTests.swift @@ -1230,7 +1230,7 @@ final class SchemaObjectTests: XCTestCase { XCTAssertEqual(null.allowedValues?[0].description, "nil") XCTAssertEqual(boolean.allowedValues, [false]) XCTAssertEqual(object.allowedValues, [[:]]) - XCTAssertEqual(array.allowedValues?[0].value as! [Bool], [false]) + XCTAssertEqual(array.allowedValues?[0], [false]) XCTAssertEqual(number.allowedValues, [2.5]) XCTAssertEqual(integer.allowedValues, [5]) XCTAssertEqual(string.allowedValues, ["hello"]) @@ -1267,8 +1267,8 @@ final class SchemaObjectTests: XCTestCase { XCTAssertEqual(null.allowedValues, [nil]) XCTAssertEqual(boolean.allowedValues, [false]) - XCTAssertEqual(object.allowedValues, [AnyCodable([String: String]())]) - XCTAssertEqual(array.allowedValues?[0].value as! [Bool], [false]) + XCTAssertEqual(object.allowedValues, [AnyCodable.object([:])]) + XCTAssertEqual(array.allowedValues?[0], [false]) XCTAssertEqual(number.allowedValues, [2.5]) XCTAssertEqual(integer.allowedValues, [5]) XCTAssertEqual(string.allowedValues, ["hello"]) @@ -1331,7 +1331,7 @@ final class SchemaObjectTests: XCTestCase { XCTAssertEqual(null.defaultValue!, nil) XCTAssertEqual(boolean.defaultValue, false) - XCTAssertEqual(object.defaultValue, AnyCodable([String: String]())) + XCTAssertEqual(object.defaultValue, AnyCodable.object([:])) XCTAssertEqual(array.defaultValue, [false]) XCTAssertEqual(number.defaultValue, 2.5) XCTAssertEqual(integer.defaultValue, 5) @@ -1358,12 +1358,12 @@ final class SchemaObjectTests: XCTestCase { let not = JSONSchema.not(.string) let ref = JSONSchema.reference(.external(URL(string: "hello.yml")!)) - XCTAssertEqual(object.examples[0].value as? [String:String], [:]) + XCTAssertEqual(object.examples[0], [:]) XCTAssertEqual(object.examples.count, 1) - XCTAssertEqual(fragment.examples[0].value as? String, "hi") + XCTAssertEqual(fragment.examples[0], "hi") XCTAssertEqual(fragment.examples.count, 1) XCTAssertEqual(null.examples.count, 1) - XCTAssertEqual(null.examples[0].value as? String, "null") + XCTAssertEqual(null.examples[0], "null") XCTAssertTrue(all.examples.isEmpty) XCTAssertTrue(one.examples.isEmpty) @@ -1375,7 +1375,7 @@ final class SchemaObjectTests: XCTestCase { func test_withAddedExample() throws { let null = try JSONSchema.null().with(example: nil) let object = try JSONSchema.object(.init(format: .unspecified, required: true), .init(properties: [:])) - .with(example: AnyCodable([String: String]())) + .with(example: [:]) let array = try JSONSchema.array(.init(), .init()) .with(example: ["hello"]) @@ -1383,8 +1383,6 @@ final class SchemaObjectTests: XCTestCase { .with(example: true) let double = try JSONSchema.number .with(example: 10.5) - let float = try JSONSchema.number - .with(example: AnyCodable(Float(2.5))) let integer = try JSONSchema.integer .with(example: 3) let string = try JSONSchema.string @@ -1402,33 +1400,31 @@ final class SchemaObjectTests: XCTestCase { let reference = try JSONSchema.reference(.external(URL(string: "hello/world.json#/hello")!),.init()).with(example: "hi") XCTAssertEqual(null.examples[0].description, "nil") - XCTAssertEqual(object.examples[0].value as? [String: String], [:]) + XCTAssertEqual(object.examples[0], [:]) XCTAssertEqual(object.examples.count, 1) - XCTAssertEqual(array.examples[0].value as? [String], ["hello"]) + XCTAssertEqual(array.examples[0], ["hello"]) XCTAssertEqual(array.examples.count, 1) - XCTAssertEqual(boolean.examples[0].value as? Bool, true) + XCTAssertEqual(boolean.examples[0], true) XCTAssertEqual(boolean.examples.count, 1) - XCTAssertEqual(double.examples[0].value as? Double, 10.5) + XCTAssertEqual(double.examples[0], 10.5) XCTAssertEqual(double.examples.count, 1) - XCTAssertEqual(float.examples[0].value as? Float, 2.5 as Float) - XCTAssertEqual(float.examples.count, 1) - XCTAssertEqual(integer.examples[0].value as? Int, 3) + XCTAssertEqual(integer.examples[0], 3) XCTAssertEqual(integer.examples.count, 1) - XCTAssertEqual(string.examples[0].value as? String, "hello world") + XCTAssertEqual(string.examples[0], "hello world") XCTAssertEqual(string.examples.count, 1) - XCTAssertEqual(allOf.examples[0].value as? [String], ["hello"]) + XCTAssertEqual(allOf.examples[0], ["hello"]) XCTAssertEqual(allOf.examples.count, 1) - XCTAssertEqual(anyOf.examples[0].value as? [String], ["hello"]) + XCTAssertEqual(anyOf.examples[0], ["hello"]) XCTAssertEqual(anyOf.examples.count, 1) - XCTAssertEqual(oneOf.examples[0].value as? [String], ["hello"]) + XCTAssertEqual(oneOf.examples[0], ["hello"]) XCTAssertEqual(oneOf.examples.count, 1) - XCTAssertEqual(not.examples[0].value as? [String], ["hello"]) + XCTAssertEqual(not.examples[0], ["hello"]) XCTAssertEqual(not.examples.count, 1) - XCTAssertEqual(fragment.examples[0].value as? String, "hi") + XCTAssertEqual(fragment.examples[0], "hi") XCTAssertEqual(fragment.examples.count, 1) - XCTAssertEqual(reference.examples[0].value as? String, "hi") + XCTAssertEqual(reference.examples[0], "hi") XCTAssertEqual(reference.examples.count, 1) } @@ -2171,8 +2167,8 @@ extension SchemaObjectTests { XCTAssertEqual(readOnlyObject, JSONSchema.object(.init(format: .generic, permissions: .readOnly), .init(properties: [:]))) XCTAssertEqual(writeOnlyObject, JSONSchema.object(.init(format: .generic, permissions: .writeOnly), .init(properties: [:]))) XCTAssertEqual(deprecatedObject, JSONSchema.object(.init(format: .generic, deprecated: true), .init(properties: [:]))) - XCTAssertEqual(constValueObject.allowedValues?[0].value as! [String: Bool], ["hello": false]) - XCTAssertEqual(allowedValueObject.allowedValues?[0].value as! [String: Bool], ["hello": false]) + XCTAssertEqual(constValueObject.allowedValues?[0], ["hello": false]) + XCTAssertEqual(allowedValueObject.allowedValues?[0], ["hello": false]) XCTAssertEqual(allowedValueObject.jsonTypeFormat, .object(.generic)) XCTAssertEqual(defaultValueObject.defaultValue, ["hello": false]) XCTAssertEqual(discriminatorObject, JSONSchema.object(discriminator: .init(propertyName: "hello"))) @@ -2300,7 +2296,7 @@ extension SchemaObjectTests { XCTAssertEqual(object, JSONSchema.object(.init(format: .generic, title: "hello"), .init(properties: [:]))) XCTAssertEqual(nullableObject, JSONSchema.object(.init(format: .generic, nullable: true, title: "hello"), .init(properties: [:]))) - XCTAssertEqual(allowedValueObject.allowedValues?[0].value as! [String: Bool], ["hello": false]) + XCTAssertEqual(allowedValueObject.allowedValues?[0], ["hello": false]) XCTAssertEqual(allowedValueObject.jsonTypeFormat, .object(.generic)) XCTAssertEqual(allowedValueObject.title, "hello") @@ -2407,7 +2403,7 @@ extension SchemaObjectTests { XCTAssertEqual(object, JSONSchema.object(.init(format: .generic, description: "hello"), .init(properties: [:]))) XCTAssertEqual(nullableObject, JSONSchema.object(.init(format: .generic, nullable: true, description: "hello"), .init(properties: [:]))) - XCTAssertEqual(allowedValueObject.allowedValues?[0].value as! [String: Bool], ["hello": false]) + XCTAssertEqual(allowedValueObject.allowedValues?[0], ["hello": false]) XCTAssertEqual(allowedValueObject.jsonTypeFormat, .object(.generic)) XCTAssertEqual(allowedValueObject.description, "hello") @@ -2522,7 +2518,7 @@ extension SchemaObjectTests { XCTAssertEqual(object, JSONSchema.object(.init(format: .generic, externalDocs: .init(url: URL(string: "http://google.com")!)), .init(properties: [:]))) XCTAssertEqual(nullableObject, JSONSchema.object(.init(format: .generic, nullable: true, externalDocs: .init(url: URL(string: "http://google.com")!)), .init(properties: [:]))) - XCTAssertEqual(allowedValueObject.allowedValues?[0].value as! [String: Bool], ["hello": false]) + XCTAssertEqual(allowedValueObject.allowedValues?[0], ["hello": false]) XCTAssertEqual(allowedValueObject.jsonTypeFormat, .object(.generic)) XCTAssertEqual(allowedValueObject.externalDocs, .init(url: URL(string: "http://google.com")!)) @@ -2629,7 +2625,7 @@ extension SchemaObjectTests { XCTAssertEqual(object, JSONSchema.object(.init(format: .generic), .init(properties: [:], maxProperties: 1))) XCTAssertEqual(nullableObject, JSONSchema.object(.init(format: .generic, nullable: true), .init(properties: [:], maxProperties: 1))) - XCTAssertEqual(allowedValueObject.allowedValues?[0].value as! [String: Bool], ["hello": false]) + XCTAssertEqual(allowedValueObject.allowedValues?[0], ["hello": false]) XCTAssertEqual(allowedValueObject.jsonTypeFormat, .object(.generic)) guard case let .object(_, contextB) = allowedValueObject.value else { @@ -2735,7 +2731,7 @@ extension SchemaObjectTests { XCTAssertEqual(object, JSONSchema.object(.init(format: .generic), .init(properties: [:], minProperties: 1))) XCTAssertEqual(nullableObject, JSONSchema.object(.init(format: .generic, nullable: true), .init(properties: [:], minProperties: 1))) - XCTAssertEqual(allowedValueObject.allowedValues?[0].value as! [String: Bool], ["hello": false]) + XCTAssertEqual(allowedValueObject.allowedValues?[0], ["hello": false]) XCTAssertEqual(allowedValueObject.jsonTypeFormat, .object(.generic)) guard case let .object(_, contextB) = allowedValueObject.value else { @@ -2841,7 +2837,7 @@ extension SchemaObjectTests { XCTAssertEqual(object, JSONSchema.object(.init(format: .generic), .init(properties: [:], additionalProperties: .init(true)))) XCTAssertEqual(nullableObject, JSONSchema.object(.init(format: .generic, nullable: true), .init(properties: [:], additionalProperties: .init(true)))) - XCTAssertEqual(allowedValueObject.allowedValues?[0].value as! [String: Bool], ["hello": false]) + XCTAssertEqual(allowedValueObject.allowedValues?[0], ["hello": false]) XCTAssertEqual(allowedValueObject.jsonTypeFormat, .object(.generic)) guard case let .object(_, contextB) = allowedValueObject.value else { @@ -2956,7 +2952,7 @@ extension SchemaObjectTests { XCTAssertEqual(object, JSONSchema.object(.init(format: .generic), .init(properties: [:], additionalProperties: .init(.string)))) XCTAssertEqual(nullableObject, JSONSchema.object(.init(format: .generic, nullable: true), .init(properties: [:], additionalProperties: .init(.string)))) - XCTAssertEqual(allowedValueObject.allowedValues?[0].value as! [String: Bool], ["hello": false]) + XCTAssertEqual(allowedValueObject.allowedValues?[0], ["hello": false]) XCTAssertEqual(allowedValueObject.jsonTypeFormat, .object(.generic)) guard case let .object(_, contextB) = allowedValueObject.value else { @@ -3219,7 +3215,7 @@ extension SchemaObjectTests { XCTAssertEqual(string, JSONSchema.string(.init(format: .unspecified, examples: ["hello"]), .init())) XCTAssertEqual(object, JSONSchema.object(.init(format: .generic, examples: [AnyCodable(["hello": true])]), .init(properties: [:]))) XCTAssertEqual(nullableObject, JSONSchema.object(.init(format: .generic, nullable: true, examples: [AnyCodable(["hello": true])]), .init(properties: [:]))) - XCTAssertEqual(allowedValueObject.allowedValues?[0].value as! [String: Bool], ["hello": false]) + XCTAssertEqual(allowedValueObject.allowedValues?[0], ["hello": false]) XCTAssertEqual(allowedValueObject.jsonTypeFormat, .object(.generic)) XCTAssertEqual(allowedValueObject.examples, [["hello" : true]]) @@ -3428,7 +3424,7 @@ extension SchemaObjectTests { XCTAssertEqual(object, JSONSchema.object(.init(format: .generic), .init(properties: ["hello": .boolean(.init(format: .generic))]))) XCTAssertEqual(nullableObject, JSONSchema.object(.init(format: .generic, nullable: true), .init(properties: ["hello": .boolean(.init(format: .generic))]))) - XCTAssertEqual(allowedValueObject.allowedValues?[0].value as! [String: Bool], ["hello": false]) + XCTAssertEqual(allowedValueObject.allowedValues?[0], ["hello": false]) XCTAssertEqual(allowedValueObject.jsonTypeFormat, .object(.generic)) guard case let .object(_, contextB) = allowedValueObject.value else { @@ -3660,7 +3656,7 @@ extension SchemaObjectTests { XCTAssertEqual(readOnlyArray, JSONSchema.array(.init(format: .generic, permissions: .readOnly), .init())) XCTAssertEqual(writeOnlyArray, JSONSchema.array(.init(format: .generic, permissions: .writeOnly), .init())) XCTAssertEqual(deprecatedArray, JSONSchema.array(.init(format: .generic, deprecated: true), .init())) - XCTAssertEqual(allowedValueArray.allowedValues?[0].value as! [Bool], [false]) + XCTAssertEqual(allowedValueArray.allowedValues?[0], [false]) XCTAssertEqual(defaultValueArray.defaultValue, [false]) XCTAssertEqual(discriminatorArray, JSONSchema.array(discriminator: .init(propertyName: "hello"))) @@ -3745,7 +3741,7 @@ extension SchemaObjectTests { XCTAssertEqual(array, JSONSchema.array(.init(format: .generic), .init(items: .boolean(.init(format: .generic))))) XCTAssertEqual(nullableArray, JSONSchema.array(.init(format: .generic, nullable: true), .init(items: .boolean(.init(format: .generic))))) - XCTAssertEqual(allowedValueArray.allowedValues?[0].value as! [Bool], [false]) + XCTAssertEqual(allowedValueArray.allowedValues?[0], [false]) guard case let .array(_, contextB) = allowedValueArray.value else { XCTFail("expected array") @@ -3823,7 +3819,7 @@ extension SchemaObjectTests { XCTAssertEqual(array, JSONSchema.array(.init(format: .generic), .init(uniqueItems: true))) XCTAssertEqual(nullableArray, JSONSchema.array(.init(format: .generic, nullable: true), .init(uniqueItems: true))) - XCTAssertEqual(allowedValueArray.allowedValues?[0].value as! [Bool], [false]) + XCTAssertEqual(allowedValueArray.allowedValues?[0], [false]) XCTAssertEqual(array.arrayContext?.uniqueItems, true) XCTAssertEqual(nullableArray.arrayContext?.uniqueItems, true) @@ -3885,7 +3881,7 @@ extension SchemaObjectTests { XCTAssertEqual(array, JSONSchema.array(.init(format: .generic), .init(maxItems: 3))) XCTAssertEqual(nullableArray, JSONSchema.array(.init(format: .generic, nullable: true), .init(maxItems: 3))) - XCTAssertEqual(allowedValueArray.allowedValues?[0].value as! [Bool], [false]) + XCTAssertEqual(allowedValueArray.allowedValues?[0], [false]) guard case let .array(_, contextB) = allowedValueArray.value else { XCTFail("expected array") @@ -3943,7 +3939,7 @@ extension SchemaObjectTests { XCTAssertEqual(array, JSONSchema.array(.init(format: .generic), .init(minItems: 2))) XCTAssertEqual(nullableArray, JSONSchema.array(.init(format: .generic, nullable: true), .init(minItems: 2))) - XCTAssertEqual(allowedValueArray.allowedValues?[0].value as! [Bool], [false]) + XCTAssertEqual(allowedValueArray.allowedValues?[0], [false]) guard case let .array(_, contextB) = allowedValueArray.value else { XCTFail("expected array") diff --git a/Tests/OpenAPIKitTests/Validator/ValidatorTests.swift b/Tests/OpenAPIKitTests/Validator/ValidatorTests.swift index 3f49c5e4f..a8a139361 100644 --- a/Tests/OpenAPIKitTests/Validator/ValidatorTests.swift +++ b/Tests/OpenAPIKitTests/Validator/ValidatorTests.swift @@ -1341,7 +1341,6 @@ final class ValidatorTests: XCTestCase { "x-double": 10.5, "x-dict": [ "string": "world"], "x-array": AnyCodable(["hello", nil, "world"]), - "x-float": AnyCodable(22.5 as Float), "x-bool": true ] ) @@ -1363,7 +1362,6 @@ final class ValidatorTests: XCTestCase { .validating("string", check: \String.self == "hiya", when: \.codingPath.last?.stringValue == "x-string") .validating("int", check: \Int.self == 3) .validating("double", check: \Double.self == 10.5) - .validating("float", check: \Float.self == 22.5) .validating("bool", check: \Bool.self == true) try document.validate(using: validator) diff --git a/Tests/OpenAPIKitTests/VendorExtendableTests.swift b/Tests/OpenAPIKitTests/VendorExtendableTests.swift index 5a49e5714..cf35373e7 100644 --- a/Tests/OpenAPIKitTests/VendorExtendableTests.swift +++ b/Tests/OpenAPIKitTests/VendorExtendableTests.swift @@ -26,14 +26,14 @@ final class VendorExtendableTests: XCTestCase { let test = try orderUnstableDecode(TestStruct.self, from: data) XCTAssertEqual(test.vendorExtensions.count, 3) - XCTAssertEqual(test.vendorExtensions["x-tension"]?.value as? String, "hello") + XCTAssertEqual(test.vendorExtensions["x-tension"], "hello") - XCTAssert((test.vendorExtensions["x-two"]?.value as? [String])!.contains("cool")) - XCTAssert((test.vendorExtensions["x-two"]?.value as? [String])!.contains("beans")) - XCTAssertEqual((test.vendorExtensions["x-two"]?.value as? [String])?.count, 2) + XCTAssert(test.vendorExtensions["x-two"]!.array!.contains("cool")) + XCTAssert(test.vendorExtensions["x-two"]!.array!.contains("beans")) + XCTAssertEqual(test.vendorExtensions["x-two"]?.array?.count, 2) - XCTAssertEqual((test.vendorExtensions["x-three"]?.value as? [String: Int])?.count, 1) - XCTAssertEqual((test.vendorExtensions["x-three"]?.value as? [String: Int])?["nested"], 10) + XCTAssertEqual(test.vendorExtensions["x-three"]?.object?.count, 1) + XCTAssertEqual(test.vendorExtensions["x-three"]?.object?["nested"], 10) } func test_encodeSuccess() throws {