From 0f692269bb10af18f4f0177514cfa6b59e30ce85 Mon Sep 17 00:00:00 2001 From: Kevin Wooten Date: Fri, 20 Jan 2023 12:03:00 -0700 Subject: [PATCH] Version 3 ## General * API normalization * * JSON & YAML nearly identical * * CBOR, ASN1 & AnyValue very similar * Maintains ordering during encoding/decoding * * When possible * 95% Test Coverage * Removal of fatalErrors * * Except for obvious programming errors * Universal BigInt & BigUInt Support * Universal Float16 Support * * Shimmed only on macOS x86_64 * Reduced leaky non-public API ## ASN.1 Mostly small public API changes but the allowed decoding source types and encoding destination types, as well as the Schema, are more restrictive * Small fixes for schema processing * `ASN1` accessors return `PotentASN1` types * `unwrapped` return Swift values (when possible) * Schmema descriptions better match ASN.1 notation ## CBOR Most of the changes relate to having move Float16 to PotentCodables to be shared across targets. * Uses shared PostentCodablesFloat16 when needed * Encodes & decodes most known tags ## JSON Small changes to make the API nearly identical to YAML ## YAML Small changes to make the API nearly identical to JSON --- Package.swift | 3 +- Sources/PotentASN1/ASN1.swift | 498 +-- Sources/PotentASN1/ASN1DERReader.swift | 36 +- Sources/PotentASN1/ASN1DERWriter.swift | 14 +- Sources/PotentASN1/ASN1Decoder.swift | 448 ++- Sources/PotentASN1/ASN1Encoder.swift | 149 +- Sources/PotentASN1/AnyString.swift | 4 +- Sources/PotentASN1/AnyTime.swift | 2 +- Sources/PotentASN1/BigInts.swift | 27 +- Sources/PotentASN1/BitString.swift | 3 +- .../Dates.swift | 15 +- Sources/PotentASN1/Schema.swift | 84 +- Sources/PotentASN1/SchemaState.swift | 22 +- Sources/PotentASN1/TaggedValue.swift | 2 +- Sources/PotentCBOR/CBOR.swift | 240 +- Sources/PotentCBOR/CBORDecoder.swift | 563 ++-- Sources/PotentCBOR/CBOREncoder.swift | 204 +- Sources/PotentCBOR/CBORReader.swift | 30 +- Sources/PotentCBOR/CBORSerialization.swift | 6 +- Sources/PotentCBOR/CBORWriter.swift | 4 +- Sources/PotentCodables/AnyCodingKey.swift | 43 +- .../PotentCodables/AnyValue/AnyValue.swift | 570 ++-- .../AnyValue/AnyValueDecoder.swift | 263 +- .../AnyValue/AnyValueEncoder.swift | 204 +- .../{CodableErrors.swift => Errors.swift} | 0 Sources/PotentCodables/Float16.swift | 45 + ...ecoders.swift => KeyedNestedDecoder.swift} | 100 +- ...ncoders.swift => KeyedNestedEncoder.swift} | 99 +- Sources/PotentCodables/Refs.swift | 126 +- Sources/PotentCodables/Tagged.swift | 229 -- .../TreeValueDecodingContainer.swift | 20 - Sources/PotentCodables/Value.swift | 2 - Sources/PotentCodables/ValueDecoder.swift | 184 +- Sources/PotentCodables/ValueEncoder.swift | 201 +- Sources/PotentCodables/ValueTransformer.swift | 189 +- Sources/PotentCodables/ZonedDate.swift | 99 +- Sources/PotentJSON/JSON.swift | 129 +- Sources/PotentJSON/JSONDecoder.swift | 236 +- Sources/PotentJSON/JSONEncoder.swift | 222 +- Sources/PotentJSON/JSONReader.swift | 23 +- Sources/PotentJSON/JSONSerialization.swift | 9 +- Sources/PotentJSON/JSONWriter.swift | 8 +- Sources/PotentYAML/YAML.swift | 341 +- Sources/PotentYAML/YAMLDecoder.swift | 301 +- Sources/PotentYAML/YAMLEncoder.swift | 302 +- Sources/PotentYAML/YAMLReader.swift | 188 +- Sources/PotentYAML/YAMLSerialization.swift | 25 +- Sources/PotentYAML/YAMLWriter.swift | 13 +- Tests/.swiftlint.yml | 4 +- Tests/ASN1AnyStringTests.swift | 88 + Tests/ASN1AnyValueTests.swift | 229 ++ ...ntTests.swift => ASN1DERBigIntTests.swift} | 20 +- Tests/ASN1DecoderTests.swift | 1166 +++++++ Tests/ASN1EncoderTests.swift | 372 ++ Tests/ASN1SchemaDecodeTests.swift | 1146 +++++++ Tests/ASN1SchemaEncodeTests.swift | 1144 +++++++ Tests/ASN1SchemaTests.swift | 479 +++ Tests/ASN1Tests.swift | 29 +- Tests/ASN1ValueTests.swift | 443 +++ Tests/AnyCodingKeyTests.swift | 57 + Tests/AnyValueCoderTests.swift | 433 +++ Tests/AnyValueTests.swift | 579 +++- Tests/Assertions.swift | 86 + Tests/CBORAnyValueTests.swift | 164 + Tests/CBORCodableRoundtripTests.swift | 8 +- Tests/CBORDecoderTests.swift | 1452 +++++++- Tests/CBOREncoderTests.swift | 207 +- Tests/CBORReaderTests.swift | 14 +- Tests/CBORValueTests.swift | 193 ++ Tests/CBORWriterTests.swift | 3 + Tests/Datas.swift | 42 + Tests/Dates.swift | 27 + Tests/JSONAnyValueTests.swift | 207 ++ Tests/JSONDecoderTests.swift | 2284 +++++++++++++ Tests/JSONEncoderTests.swift | 1138 +++++++ Tests/JSONOrderTests.swift | 73 + Tests/JSONReaderTests.swift | 212 ++ Tests/JSONTests.swift | 184 - Tests/JSONValueTests.swift | 155 + Tests/JSONWriterTests.swift | 81 + Tests/RefTests.swift | 239 +- Tests/ValueCoderTests.swift | 1135 +++++++ Tests/ValueTransformerTests.swift | 1529 ++++++++- Tests/YAMLAnyValueTests.swift | 189 ++ Tests/YAMLDecoderTests.swift | 3000 +++++++++++++++++ Tests/YAMLEncoderTests.swift | 1274 +++++++ Tests/YAMLOrderTests.swift | 106 + Tests/YAMLReaderTests.swift | 150 + Tests/YAMLTests.swift | 4 +- Tests/YAMLValueTests.swift | 210 ++ Tests/YAMLWriterTests.swift | 47 + 91 files changed, 23921 insertions(+), 3206 deletions(-) rename Sources/{PotentCodables => PotentASN1}/Dates.swift (83%) rename Sources/PotentCodables/{CodableErrors.swift => Errors.swift} (100%) create mode 100644 Sources/PotentCodables/Float16.swift rename Sources/PotentCodables/{NestedDecoders.swift => KeyedNestedDecoder.swift} (53%) rename Sources/PotentCodables/{NestedEncoders.swift => KeyedNestedEncoder.swift} (54%) delete mode 100644 Sources/PotentCodables/Tagged.swift delete mode 100644 Sources/PotentCodables/TreeValueDecodingContainer.swift create mode 100644 Tests/ASN1AnyStringTests.swift create mode 100644 Tests/ASN1AnyValueTests.swift rename Tests/{BigIntTests.swift => ASN1DERBigIntTests.swift} (66%) create mode 100644 Tests/ASN1DecoderTests.swift create mode 100644 Tests/ASN1EncoderTests.swift create mode 100644 Tests/ASN1SchemaDecodeTests.swift create mode 100644 Tests/ASN1SchemaEncodeTests.swift create mode 100644 Tests/ASN1SchemaTests.swift create mode 100644 Tests/ASN1ValueTests.swift create mode 100644 Tests/AnyCodingKeyTests.swift create mode 100644 Tests/AnyValueCoderTests.swift create mode 100644 Tests/Assertions.swift create mode 100644 Tests/CBORAnyValueTests.swift create mode 100644 Tests/CBORValueTests.swift create mode 100644 Tests/Datas.swift create mode 100644 Tests/Dates.swift create mode 100644 Tests/JSONAnyValueTests.swift create mode 100644 Tests/JSONDecoderTests.swift create mode 100644 Tests/JSONEncoderTests.swift create mode 100644 Tests/JSONOrderTests.swift create mode 100644 Tests/JSONReaderTests.swift delete mode 100644 Tests/JSONTests.swift create mode 100644 Tests/JSONValueTests.swift create mode 100644 Tests/JSONWriterTests.swift create mode 100644 Tests/ValueCoderTests.swift create mode 100644 Tests/YAMLAnyValueTests.swift create mode 100644 Tests/YAMLDecoderTests.swift create mode 100644 Tests/YAMLEncoderTests.swift create mode 100644 Tests/YAMLOrderTests.swift create mode 100644 Tests/YAMLReaderTests.swift create mode 100644 Tests/YAMLValueTests.swift create mode 100644 Tests/YAMLWriterTests.swift diff --git a/Package.swift b/Package.swift index e5537a52a..56224b7ca 100644 --- a/Package.swift +++ b/Package.swift @@ -33,7 +33,7 @@ let package = Package( targets: [ .target( name: "PotentCodables", - dependencies: ["BigInt"] + dependencies: ["BigInt", "Float16"] ), .target( name: "PotentJSON", @@ -46,7 +46,6 @@ let package = Package( name: "PotentCBOR", dependencies: [ "PotentCodables", - "Float16", .product(name: "Collections", package: "swift-collections") ] ), diff --git a/Sources/PotentASN1/ASN1.swift b/Sources/PotentASN1/ASN1.swift index bfc54f5ec..d0031a782 100644 --- a/Sources/PotentASN1/ASN1.swift +++ b/Sources/PotentASN1/ASN1.swift @@ -15,7 +15,7 @@ import PotentCodables /// General ASN.1 Value. /// -public indirect enum ASN1: Value { +public indirect enum ASN1 { /// ASN.1 Tag Code public typealias AnyTag = UInt8 @@ -60,12 +60,12 @@ public indirect enum ASN1: Value { case characterString = 29 case bmpString = 30 - /// Creates an ASN.1 tag from the tag code (``AnyTag``), ``Class``, and if the tag is constructed. + /// Creates an ASN.1 tag from the tag code (``ASN1/AnyTag-swift.typealias``), ``Class``, and if the tag is constructed. public static func tag(from value: UInt8, in tagClass: Class, constructed: Bool) -> AnyTag { return (value & ~0xE0) | tagClass.rawValue | (constructed ? 0x20 : 0x00) } - /// Creates an ASN.1 tag code (``AnyTag``), from an existing tag code and ``Class``. + /// Creates an ASN.1 tag code (``ASN1/AnyTag-swift.typealias``), from an existing tag code and ``Class``. public static func value(from tag: AnyTag, in tagClass: Class) -> AnyTag { return (tag & ~0xE0) & ~tagClass.rawValue } @@ -115,179 +115,8 @@ public indirect enum ASN1: Value { case tagged(UInt8, Data) case `default`(ASN1) - /// Check if this value is an ASN.1 ``null``. - public var isNull: Bool { return self == .null } - - /// Known tag for this ASN.1 value, if available. - /// - /// If the tag is not an explicitly known tag, the value will be `nil`. - /// - public var knownTag: Tag? { - switch self { - case .boolean: return Tag.boolean - case .integer: return Tag.integer - case .bitString: return Tag.bitString - case .octetString: return Tag.octetString - case .null: return Tag.null - case .objectIdentifier: return Tag.objectIdentifier - case .real: return Tag.real - case .utf8String: return Tag.utf8String - case .sequence: return Tag.sequence - case .set: return Tag.set - case .numericString: return Tag.numericString - case .printableString: return Tag.printableString - case .teletexString: return Tag.teletexString - case .videotexString: return Tag.videotexString - case .ia5String: return Tag.ia5String - case .utcTime: return Tag.utcTime - case .generalizedTime: return Tag.generalizedTime - case .graphicString: return Tag.graphicString - case .visibleString: return Tag.visibleString - case .generalString: return Tag.generalString - case .universalString: return Tag.universalString - case .characterString: return Tag.characterString - case .bmpString: return Tag.bmpString - case .default(let value): return value.knownTag - default: return nil - } - } - - /// Tag code for this ASN.1 value. - /// - public var anyTag: AnyTag { - guard case .tagged(let tag, _) = self else { - return knownTag!.universal - } - return tag - } - - /// Name of the tag for this value in ASN.1 notation. - public var tagName: String { - guard let knownTag = knownTag else { - return "IMPLICIT TAGGED (\(String(anyTag, radix: 16)))" - } - return String(describing: knownTag) - } - - public var unwrapped: Any? { - switch absolute { - case .boolean(let value): return value - case .integer(let value): return value - case .bitString(let length, let bytes): return BitString(length: length, bytes: bytes).bitFlags - case .octetString(let value): return value - case .null: return nil - case .objectIdentifier(let fields): return fields - case .real(let value): return value - case .utf8String(let value): return value - case .sequence(let values): return Array(values.map(\.unwrapped)) - case .set(let values): return Array(values.map(\.unwrapped)) - case .numericString(let value): return value - case .printableString(let value): return value - case .teletexString(let value): return value - case .videotexString(let value): return value - case .ia5String(let value): return value - case .utcTime(let value): return value.utcDate - case .generalizedTime(let value): return value.utcDate - case .graphicString(let value): return value - case .visibleString(let value): return value - case .generalString(let value): return value - case .universalString(let value): return value - case .characterString(let value): return value - case .bmpString(let value): return value - case .tagged(_, let data): return data - case .default(let value): return value.unwrapped - } - } - - /// Current value as ASN1 type. - /// - /// Returns equivalent types as the value accessors (e.g. ``booleanValue``). - /// - public var currentValue: Any? { - switch absolute { - case .boolean(let value): return value - case .integer(let value): return value - case .bitString(let length, let bytes): return BitString(length: length, bytes: bytes) - case .octetString(let value): return value - case .null: return nil - case .objectIdentifier(let fields): return ObjectIdentifier(fields) - case .real(let value): return value - case .utf8String(let value): return AnyString(value, kind: .utf8) - case .sequence(let values): return values - case .set(let values): return values - case .numericString(let value): return AnyString(value, kind: .numeric) - case .printableString(let value): return AnyString(value, kind: .printable) - case .teletexString(let value): return AnyString(value, kind: .teletex) - case .videotexString(let value): return AnyString(value, kind: .videotex) - case .ia5String(let value): return AnyString(value, kind: .ia5) - case .utcTime(let value): return AnyTime(value, kind: .utc) - case .generalizedTime(let value): return AnyTime(value, kind: .generalized) - case .graphicString(let value): return AnyString(value, kind: .graphic) - case .visibleString(let value): return AnyString(value, kind: .visible) - case .generalString(let value): return AnyString(value, kind: .general) - case .universalString(let value): return AnyString(value, kind: .universal) - case .characterString(let value): return AnyString(value, kind: .character) - case .bmpString(let value): return AnyString(value, kind: .bmp) - case .tagged: return self - case .default(let value): return value.currentValue - } - } - -} - - -public func == (lhs: ASN1.Tag, rhs: ASN1.AnyTag) -> Bool { - return lhs.rawValue == rhs -} - -public func == (lhs: ASN1.AnyTag, rhs: ASN1.Tag) -> Bool { - return lhs == rhs.rawValue -} - - -extension ASN1.Tag: CustomStringConvertible { - - /// Name of the tag in ASN.1 notation. - public var description: String { - switch self { - case .boolean: return "BOOLEAN" - case .integer: return "INTEGER" - case .bitString: return "BIT STRING" - case .octetString: return "OCTET STRING" - case .null: return "NULL" - case .objectIdentifier: return "OBJECT IDENTIFIER" - case .objectDescriptor: return "OBJECT DESCRIPTOR" - case .external: return "EXTERNAL" - case .real: return "REAL" - case .enumerated: return "ENUMERATED" - case .embedded: return "EMBEDDED" - case .utf8String: return "UTF8String" - case .relativeOID: return "RELATIVE OID" - case .sequence: return "SEQUENCE" - case .set: return "SET" - case .numericString: return "NumericString" - case .printableString: return "PrintableString" - case .teletexString: return "TeletexString" - case .videotexString: return "VideotexString" - case .ia5String: return "IA5String" - case .utcTime: return "UTCTime" - case .generalizedTime: return "GeneralizedTime" - case .graphicString: return "GraphicString" - case .visibleString: return "VisibleString" - case .generalString: return "GeneralString" - case .universalString: return "UniversalString" - case .characterString: return "CharacterString" - case .bmpString: return "BMPString" - } - } - -} - - -public extension ASN1 { - /// Unwrap the ``default(_:)`` modifier, if present. - var absolute: ASN1 { + public var absolute: ASN1 { switch self { case .default(let value): return value default: return self @@ -295,139 +124,139 @@ public extension ASN1 { } /// Values as boolean or `nil` if this is not a ``boolean(_:)``. - var booleanValue: Bool? { + public var booleanValue: Bool? { guard case .boolean(let value) = absolute else { return nil } return value } /// Values as ``ASN1/Integer`` or `nil` if this is not an ``integer(_:)``. - var integerValue: ASN1.Integer? { + public var integerValue: ASN1.Integer? { guard case .integer(let value) = absolute else { return nil } return value } /// Values as ``BitString`` or `nil` if this is not a ``bitString(_:_:)``. - var bitStringValue: BitString? { + public var bitStringValue: BitString? { guard case .bitString(let length, let bytes) = absolute else { return nil } return BitString(length: length, bytes: bytes) } /// Values as `Data` or `nil` if this is not an ``octetString(_:)``. - var octetStringValue: Data? { + public var octetStringValue: Data? { guard case .octetString(let value) = absolute else { return nil } return value } /// Values as `[UInt64]` or `nil` if this is not an ``objectIdentifier(_:)``. - var objectIdentifierValue: ObjectIdentifier? { + public var objectIdentifierValue: ObjectIdentifier? { guard case .objectIdentifier(let value) = absolute else { return nil } return ObjectIdentifier(value) } /// Values as decimal or `nil` if this is not a ``real(_:)``. - var realValue: Decimal? { + public var realValue: Decimal? { guard case .real(let value) = absolute else { return nil } return value } /// Values as ``AnyString`` or `nil` if this is not a ``utf8String(_:)``. - var utf8StringValue: AnyString? { + public var utf8StringValue: AnyString? { guard case .utf8String(let value) = absolute else { return nil } return AnyString(value, kind: .utf8) } /// Value as array of ``ASN1`` or `nil` if this is not a ``sequence(_:)``. - var sequenceValue: [ASN1]? { + public var sequenceValue: [ASN1]? { guard case .sequence(let value) = absolute else { return nil } return value } /// Value as array of ``ASN1`` or `nil` if this is not a ``set(_:)``. - var setValue: [ASN1]? { + public var setValue: [ASN1]? { guard case .set(let value) = absolute else { return nil } return value } /// Value as ``AnyString`` or `nil` if this is not a ``numericString(_:)``. - var numericStringValue: AnyString? { + public var numericStringValue: AnyString? { guard case .numericString(let value) = absolute else { return nil } return AnyString(value, kind: .numeric) } /// Value as ``AnyString`` or `nil` if this is not a ``printableString(_:)``. - var printableStringValue: AnyString? { + public var printableStringValue: AnyString? { guard case .printableString(let value) = absolute else { return nil } return AnyString(value, kind: .printable) } /// Value as ``AnyString`` or `nil` if this is not a ``teletexString(_:)``. - var teletexStringValue: AnyString? { + public var teletexStringValue: AnyString? { guard case .teletexString(let value) = absolute else { return nil } return AnyString(value, kind: .teletex) } /// Value as ``AnyString`` or `nil` if this is not a ``videotexString(_:)``. - var videotexStringValue: AnyString? { + public var videotexStringValue: AnyString? { guard case .videotexString(let value) = absolute else { return nil } return AnyString(value, kind: .videotex) } /// Value as ``AnyString`` or `nil` if this is not an ``ia5String(_:)``. - var ia5StringValue: AnyString? { + public var ia5StringValue: AnyString? { guard case .ia5String(let value) = absolute else { return nil } return AnyString(value, kind: .ia5) } /// Value as ``AnyTime`` or `nil` if this is not a ``utcTime(_:)``. - var utcTimeValue: AnyTime? { + public var utcTimeValue: AnyTime? { guard case .utcTime(let value) = absolute else { return nil } return AnyTime(value, kind: .utc) } /// Value as ``AnyTime`` or `nil` if this is not a ``generalizedTime(_:)``. - var generalizedTimeValue: AnyTime? { + public var generalizedTimeValue: AnyTime? { guard case .generalizedTime(let value) = absolute else { return nil } return AnyTime(value, kind: .generalized) } /// Value as ``AnyString`` or `nil` if this is not a ``graphicString(_:)``. - var graphicStringValue: AnyString? { + public var graphicStringValue: AnyString? { guard case .graphicString(let value) = absolute else { return nil } return AnyString(value, kind: .graphic) } /// Value as ``AnyString`` or `nil` if this is not a ``visibleString(_:)``. - var visibleStringValue: AnyString? { + public var visibleStringValue: AnyString? { guard case .visibleString(let value) = absolute else { return nil } return AnyString(value, kind: .visible) } /// Value as ``AnyString`` or `nil` if this is not a ``generalString(_:)``. - var generalStringValue: AnyString? { + public var generalStringValue: AnyString? { guard case .generalString(let value) = absolute else { return nil } return AnyString(value, kind: .general) } /// Value as ``AnyString`` or `nil` if this is not a ``universalString(_:)``. - var universalStringValue: AnyString? { + public var universalStringValue: AnyString? { guard case .universalString(let value) = absolute else { return nil } return AnyString(value, kind: .universal) } /// Value as ``AnyString`` or `nil` if this is not a ``characterString(_:)``. - var characterStringValue: AnyString? { + public var characterStringValue: AnyString? { guard case .characterString(let value) = absolute else { return nil } return AnyString(value, kind: .character) } /// Value as ``AnyString`` or `nil` if this is not a ``bmpString(_:)``. - var bmpStringValue: AnyString? { + public var bmpStringValue: AnyString? { guard case .bmpString(let value) = absolute else { return nil } return AnyString(value, kind: .bmp) } /// Value as ``AnyString`` or `nil` if this is not one of the string values. - var stringValue: AnyString? { + public var stringValue: AnyString? { switch absolute { case .utf8String(let value): return AnyString(value, kind: .utf8) case .numericString(let value): return AnyString(value, kind: .numeric) @@ -446,7 +275,7 @@ public extension ASN1 { } /// Value as ``AnyTime`` or `nil` if this is not one of the time values. - var timeValue: AnyTime? { + public var timeValue: AnyTime? { switch absolute { case .utcTime(let date): return AnyTime(date, kind: .utc) case .generalizedTime(let date): return AnyTime(date, kind: .generalized) @@ -455,7 +284,7 @@ public extension ASN1 { } /// Value as array of ``ASN1`` or `nil` if this is not a ``sequence(_:)`` or ``set(_:)``. - var collectionValue: [ASN1]? { + public var collectionValue: [ASN1]? { switch absolute { case .set(let value): return value case .sequence(let value): return value @@ -464,23 +293,209 @@ public extension ASN1 { } /// Value as ``TaggedValue`` or `nil` if this is not a ``tagged(_:_:)``. - var taggedValue: TaggedValue? { + public var taggedValue: TaggedValue? { guard case .tagged(let tag, let data) = absolute else { return nil } return TaggedValue(tag: tag, data: data) } + /// Known tag for this ASN.1 value, if available. + /// + /// If the tag is not an explicitly known tag, the value will be `nil`. + /// + public var knownTag: Tag? { + switch self { + case .boolean: return Tag.boolean + case .integer: return Tag.integer + case .bitString: return Tag.bitString + case .octetString: return Tag.octetString + case .null: return Tag.null + case .objectIdentifier: return Tag.objectIdentifier + case .real: return Tag.real + case .utf8String: return Tag.utf8String + case .sequence: return Tag.sequence + case .set: return Tag.set + case .numericString: return Tag.numericString + case .printableString: return Tag.printableString + case .teletexString: return Tag.teletexString + case .videotexString: return Tag.videotexString + case .ia5String: return Tag.ia5String + case .utcTime: return Tag.utcTime + case .generalizedTime: return Tag.generalizedTime + case .graphicString: return Tag.graphicString + case .visibleString: return Tag.visibleString + case .generalString: return Tag.generalString + case .universalString: return Tag.universalString + case .characterString: return Tag.characterString + case .bmpString: return Tag.bmpString + case .default(let value): return value.knownTag + default: return nil + } + } + + /// Tag code for this ASN.1 value. + /// + public var anyTag: AnyTag { + guard case .tagged(let tag, _) = self else { + return knownTag!.universal + } + return tag + } + + /// Name of the tag for this value in ASN.1 notation. + public var tagName: String { + guard let knownTag = knownTag else { + return "IMPLICIT TAGGED (\(String(anyTag, radix: 16)))" + } + return String(describing: knownTag) + } + } -extension ASN1: Codable { +// MARK: Conformances + +extension ASN1: Equatable {} - public init(from decoder: Decoder) throws { +public func == (lhs: ASN1.Tag, rhs: ASN1.AnyTag) -> Bool { + return lhs.rawValue == rhs +} + +public func == (lhs: ASN1.AnyTag, rhs: ASN1.Tag) -> Bool { + return lhs == rhs.rawValue +} + + +extension ASN1: Hashable {} +extension ASN1: Value { + + /// Check if this value is an ASN.1 ``null``. + public var isNull: Bool { + return self == .null + } + +} + +extension ASN1.Tag: CustomStringConvertible { + + /// Name of the tag in ASN.1 notation. + public var description: String { + switch self { + case .boolean: return "BOOLEAN" + case .integer: return "INTEGER" + case .bitString: return "BIT STRING" + case .octetString: return "OCTET STRING" + case .null: return "NULL" + case .objectIdentifier: return "OBJECT IDENTIFIER" + case .objectDescriptor: return "OBJECT DESCRIPTOR" + case .external: return "EXTERNAL" + case .real: return "REAL" + case .enumerated: return "ENUMERATED" + case .embedded: return "EMBEDDED" + case .utf8String: return "UTF8String" + case .relativeOID: return "RELATIVE OID" + case .sequence: return "SEQUENCE" + case .set: return "SET" + case .numericString: return "NumericString" + case .printableString: return "PrintableString" + case .teletexString: return "TeletexString" + case .videotexString: return "VideotexString" + case .ia5String: return "IA5String" + case .utcTime: return "UTCTime" + case .generalizedTime: return "GeneralizedTime" + case .graphicString: return "GraphicString" + case .visibleString: return "VisibleString" + case .generalString: return "GeneralString" + case .universalString: return "UniversalString" + case .characterString: return "CharacterString" + case .bmpString: return "BMPString" + } + } + +} + + +// MARK: Wrapping + +extension ASN1 { + + public var unwrapped: Any? { + switch self { + case .boolean(let value): return value + case .integer(let value): return value + case .bitString(let length, let bytes): return BitString(length: length, bytes: bytes).bitFlags + case .octetString(let value): return value + case .null: return nil + case .objectIdentifier(let fields): return fields + case .real(let value): return value + case .utf8String(let value): return value + case .sequence(let values): return Array(values.map(\.unwrapped)) + case .set(let values): return Array(values.map(\.unwrapped)) + case .numericString(let value): return value + case .printableString(let value): return value + case .teletexString(let value): return value + case .videotexString(let value): return value + case .ia5String(let value): return value + case .utcTime(let value): return value.utcDate + case .generalizedTime(let value): return value.utcDate + case .graphicString(let value): return value + case .visibleString(let value): return value + case .generalString(let value): return value + case .universalString(let value): return value + case .characterString(let value): return value + case .bmpString(let value): return value + case .tagged(let tag, let data): return try? ASN1DERReader.parseItem(data, as: tag).unwrapped + case .default(let value): return value.unwrapped + } + } + + /// Current value as ASN1 type. + /// + /// Returns equivalent types as the value accessors (e.g. ``booleanValue``). + /// + public var currentValue: Any? { + switch self { + case .boolean(let value): return value + case .integer(let value): return value + case .bitString(let length, let bytes): return BitString(length: length, bytes: bytes) + case .octetString(let value): return value + case .null: return nil + case .objectIdentifier(let fields): return ObjectIdentifier(fields) + case .real(let value): return value + case .utf8String(let value): return AnyString(value, kind: .utf8) + case .sequence(let values): return values + case .set(let values): return values + case .numericString(let value): return AnyString(value, kind: .numeric) + case .printableString(let value): return AnyString(value, kind: .printable) + case .teletexString(let value): return AnyString(value, kind: .teletex) + case .videotexString(let value): return AnyString(value, kind: .videotex) + case .ia5String(let value): return AnyString(value, kind: .ia5) + case .utcTime(let value): return AnyTime(value, kind: .utc) + case .generalizedTime(let value): return AnyTime(value, kind: .generalized) + case .graphicString(let value): return AnyString(value, kind: .graphic) + case .visibleString(let value): return AnyString(value, kind: .visible) + case .generalString(let value): return AnyString(value, kind: .general) + case .universalString(let value): return AnyString(value, kind: .universal) + case .characterString(let value): return AnyString(value, kind: .character) + case .bmpString(let value): return AnyString(value, kind: .bmp) + case .tagged(let tag, let data): return TaggedValue(tag: tag, data: data) + case .default(let value): return value.currentValue + } + } + +} + + +// MARK: Codable + +extension ASN1: Decodable { + + public init(from decoder: Swift.Decoder) throws { var container = try decoder.unkeyedContainer() let tagValue = try container.decode(UInt8.self) guard let tag = Tag(rawValue: tagValue) else { - let taggedValue = try container.decode(TaggedValue.self) - self = .tagged(taggedValue.tag, taggedValue.data) + let data = try container.decode(Data.self) + self = .tagged(tagValue, data) return } @@ -497,7 +512,7 @@ extension ASN1: Codable { case .null: self = .null case .objectIdentifier: - self = .objectIdentifier(try container.decode([UInt64].self)) + self = .objectIdentifier(try container.decode(ObjectIdentifier.self).fields) case .real: self = .real(try container.decode(Decimal.self)) case .utf8String: @@ -517,9 +532,17 @@ extension ASN1: Codable { case .ia5String: self = .ia5String(try container.decode(String.self)) case .utcTime: - self = .utcTime(try container.decode(ZonedDate.self)) + guard let time = ZonedDate(iso8601Encoded: try container.decode(String.self)) else { + throw DecodingError.dataCorruptedError(in: container, + debugDescription: "Invalid ISO8601 formatted date") + } + self = .utcTime(time) case .generalizedTime: - self = .generalizedTime(try container.decode(ZonedDate.self)) + guard let time = ZonedDate(iso8601Encoded: try container.decode(String.self)) else { + throw DecodingError.dataCorruptedError(in: container, + debugDescription: "Invalid ISO8601 formatted date") + } + self = .generalizedTime(time) case .graphicString: self = .graphicString(try container.decode(String.self)) case .visibleString: @@ -540,61 +563,104 @@ extension ASN1: Codable { } } - public func encode(to encoder: Encoder) throws { +} + +extension ASN1: Encodable { + + public func encode(to encoder: Swift.Encoder) throws { var container = encoder.unkeyedContainer() - try container.encode(anyTag) switch absolute { case .boolean(let value): + try container.encode(anyTag) try container.encode(value) case .integer(let value): + try container.encode(anyTag) try container.encode(value) case .bitString(let length, let bytes): + try container.encode(anyTag) try container.encode(BitString(length: length, bytes: bytes)) case .octetString(let data): + try container.encode(anyTag) try container.encode(data) case .null: + try container.encode(anyTag) try container.encodeNil() case .objectIdentifier(let fields): - try container.encode(fields) + try container.encode(anyTag) + try container.encode(ObjectIdentifier(fields)) case .real(let value): + try container.encode(anyTag) try container.encode(value) case .utf8String(let value): + try container.encode(anyTag) try container.encode(value) case .sequence(let elements): + try container.encode(anyTag) try container.encode(elements) case .set(let elements): + try container.encode(anyTag) try container.encode(elements) case .numericString(let value): + try container.encode(anyTag) try container.encode(value) case .printableString(let value): + try container.encode(anyTag) try container.encode(value) case .teletexString(let value): + try container.encode(anyTag) try container.encode(value) case .videotexString(let value): + try container.encode(anyTag) try container.encode(value) case .ia5String(let value): + try container.encode(anyTag) try container.encode(value) case .utcTime(let value): - try container.encode(value) + try container.encode(anyTag) + try container.encode(value.iso8601EncodedString()) case .generalizedTime(let value): - try container.encode(value) + try container.encode(anyTag) + try container.encode(value.iso8601EncodedString()) case .graphicString(let value): + try container.encode(anyTag) try container.encode(value) case .visibleString(let value): + try container.encode(anyTag) try container.encode(value) case .generalString(let value): + try container.encode(anyTag) try container.encode(value) case .universalString(let value): + try container.encode(anyTag) try container.encode(value) case .characterString(let value): + try container.encode(anyTag) try container.encode(value) case .bmpString(let value): + try container.encode(anyTag) try container.encode(value) - case .tagged(let tag, let data): - try container.encode(TaggedValue(tag: tag, data: data)) + case .tagged(let tagValue, let data): + if let tag = Tag(rawValue: tagValue) { + let value = try ASN1DERReader.parseItem(data, as: tag.rawValue) + try value.encode(to: encoder) + } + else { + try container.encode(tagValue) + try container.encode(data) + } case .default: - fatalError() + break } } } + + +// Make encoders/decoders available in AnyValue namespace + +public extension ASN1 { + + typealias Encoder = ASN1Encoder + typealias Decoder = ASN1Decoder + +} diff --git a/Sources/PotentASN1/ASN1DERReader.swift b/Sources/PotentASN1/ASN1DERReader.swift index c8f7b862c..f5d446cbf 100644 --- a/Sources/PotentASN1/ASN1DERReader.swift +++ b/Sources/PotentASN1/ASN1DERReader.swift @@ -51,8 +51,11 @@ public enum ASN1DERReader { /// - Parameter data: Data to be parsed. /// - Returns: Parsed tag and data. /// - Throws: ``ASN1DERReader/Error`` if `data` is unparsable or corrupted. - public static func parseTagged(data: Data) throws -> TaggedValue { - try data.withUnsafeBytes { ptr in + public static func parseTagged(data: Data) throws -> TaggedValue? { + if data.isEmpty { + return nil + } + return try data.withUnsafeBytes { ptr in var buffer = ptr.bindMemory(to: UInt8.self) let (tag, itemBuffer) = try parseTagged(&buffer) return TaggedValue(tag: tag, data: Data(itemBuffer)) @@ -129,8 +132,11 @@ public enum ASN1DERReader { return .boolean(try itemBuffer.pop() != 0) case .integer: + if itemBuffer.isEmpty { + return .integer(BigInt.zero) + } let data = Data(itemBuffer.popAll()) - return .integer(BigInt(serialized: data)) + return .integer(BigInt(derEncoded: data)) case .bitString: let unusedBits = try itemBuffer.pop() @@ -214,8 +220,7 @@ public enum ASN1DERReader { if lead & 0x40 == 0x40 { return lead & 0x1 == 0 ? Decimal(Double.infinity) : Decimal(-Double.infinity) } - else if lead & 0x3F == 0 { - // Choose ISO-6093 NR3 + else if lead & 0xC0 == 0 { let bytes = buffer.popAll() return Decimal(string: String(bytes: bytes, encoding: .ascii) ?? "") ?? .zero } @@ -317,13 +322,6 @@ public enum ASN1DERReader { private extension UnsafeBufferPointer { - func peek() throws -> Element { - guard let byte = baseAddress?.pointee else { - throw ASN1DERReader.Error.unexpectedEOF - } - return byte - } - mutating func popAll() -> UnsafeRawBufferPointer { let buffer = UnsafeRawBufferPointer(start: baseAddress, count: count) self = UnsafeBufferPointer(start: baseAddress?.advanced(by: count), count: 0) @@ -340,6 +338,9 @@ private extension UnsafeBufferPointer { } mutating func pop() throws -> Element { + guard self.count >= 1 else { + throw ASN1DERReader.Error.unexpectedEOF + } defer { self = UnsafeBufferPointer(start: baseAddress?.advanced(by: 1), count: self.count - 1) } @@ -348,11 +349,8 @@ private extension UnsafeBufferPointer { } +private let utcFormatter = + SuffixedDateFormatter(basePattern: "yyMMddHHmm", secondsPattern: "ss") { $0.count > 10 } -private extension String { - var hasFractionalSeconds: Bool { contains(".") } - var hasZone: Bool { contains("+") || contains("-") || contains("Z") } -} - -private let utcFormatter = SuffixedDateFormatter(basePattern: "yyMMddHHmm", secondsPattern: "ss") { $0.count > 10 } -private let generalizedFormatter = SuffixedDateFormatter.optionalFractionalSeconds(basePattern: "yyyyMMddHHmmss") +private let generalizedFormatter = + SuffixedDateFormatter(basePattern: "yyyyMMddHHmmss", secondsPattern: ".S") { $0.contains(".") } diff --git a/Sources/PotentASN1/ASN1DERWriter.swift b/Sources/PotentASN1/ASN1DERWriter.swift index 284ab8a50..d166d5c1e 100644 --- a/Sources/PotentASN1/ASN1DERWriter.swift +++ b/Sources/PotentASN1/ASN1DERWriter.swift @@ -46,18 +46,10 @@ public class ASN1DERWriter { public private(set) var data: Data - /// Default initializer. public required init() { data = Data(capacity: 256) } - /// Initialize with pre-existing data to append written data to. - /// - /// - Parameter data: Pre-existing data to append to. - public init(data: Data) { - self.data = data - } - private func append(byte: UInt8) { data.append(byte) } @@ -78,7 +70,7 @@ public class ASN1DERWriter { append(byte: UInt8(value & 0xFF)) default: - let bytes = BigUInt(value).serialized() + let bytes = BigUInt(value).derEncoded() guard let byteCount = UInt8(exactly: bytes.count), byteCount <= 127 else { throw Error.lengthOverflow } @@ -110,7 +102,7 @@ public class ASN1DERWriter { append(byte: value ? 0xFF : 0x00) case .integer(let value): - let bytes = value.serialized() + let bytes = value.derEncoded() try append(tag: .integer, length: max(1, bytes.count)) append(data: bytes.isEmpty ? Self.zero : bytes) @@ -170,7 +162,7 @@ public class ASN1DERWriter { } // Choose ISO-6093 NR3 var data = String(describing: value).data(using: .ascii) ?? Data() - data.insert(0x3, at: 0) + data.insert(0x00, at: 0) try append(tag: .real, length: data.count) append(data: data) diff --git a/Sources/PotentASN1/ASN1Decoder.swift b/Sources/PotentASN1/ASN1Decoder.swift index 9430a3887..e80c5657a 100644 --- a/Sources/PotentASN1/ASN1Decoder.swift +++ b/Sources/PotentASN1/ASN1Decoder.swift @@ -8,7 +8,9 @@ // Distributed under the MIT License, See LICENSE for details. // +import BigInt import Foundation +import OrderedCollections import PotentCodables @@ -82,7 +84,6 @@ public class ASN1Decoder: ValueDecoder, DecodesFromD public struct ASN1DecoderTransform: InternalDecoderTransform, InternalValueDeserializer { public typealias Value = ASN1 - public typealias Decoder = InternalValueDecoder public typealias State = SchemaState public static let nilValue = ASN1.null @@ -94,8 +95,7 @@ public struct ASN1DecoderTransform: InternalDecoderTransform, InternalValueDeser public let userInfo: [CodingUserInfoKey: Any] } - static func decode(_ value: ASN1, decoder: Decoder) throws -> Any? { -// print("codingPath =", decoder.codingPath.map { $0.stringValue }) + static func decode(_ value: ASN1, decoder: IVD) throws -> Any? { if decoder.state == nil { decoder.state = try SchemaState(initial: decoder.options.schema) @@ -116,16 +116,131 @@ public struct ASN1DecoderTransform: InternalDecoderTransform, InternalValueDeser return try decoder.state.decode(value) } + public static func intercepts(_ type: Decodable.Type) -> Bool { + if type as? Tagged.Type != nil { + return true + } + return type == AnyString.self || type == AnyTime.self + || type == BitString.self || type == ObjectIdentifier.self + || type == ASN1.Integer.self + || type == Date.self || type == NSDate.self + || type == Data.self || type == NSData.self + || type == URL.self || type == NSURL.self + || type == UUID.self || type == NSUUID.self + || type == Decimal.self || type == NSDecimalNumber.self + || type == BigInt.self + || type == BigUInt.self + || type == AnyValue.self + } + + public static func unbox(_ value: ASN1, interceptedType: Decodable.Type, decoder: IVD) throws -> Any? { + if let taggedType = interceptedType as? Tagged.Type { + return try unbox(value, as: taggedType, decoder: decoder) + } + else if interceptedType == AnyString.self { + return try unbox(value, as: AnyString.self, decoder: decoder) + } + else if interceptedType == AnyTime.self { + return try unbox(value, as: AnyTime.self, decoder: decoder) + } + else if interceptedType == BitString.self { + return try unbox(value, as: BitString.self, decoder: decoder) + } + else if interceptedType == ObjectIdentifier.self { + return try unbox(value, as: ObjectIdentifier.self, decoder: decoder) + } + else if interceptedType == Date.self || interceptedType == NSDate.self { + return try unbox(value, as: Date.self, decoder: decoder) + } + else if interceptedType == Data.self || interceptedType == NSData.self { + return try unbox(value, as: Data.self, decoder: decoder) + } + else if interceptedType == URL.self || interceptedType == URL.self { + return try unbox(value, as: URL.self, decoder: decoder) + } + else if interceptedType == UUID.self || interceptedType == UUID.self { + return try unbox(value, as: UUID.self, decoder: decoder) + } + else if interceptedType == Decimal.self || interceptedType == NSDecimalNumber.self { + return try unbox(value, as: Decimal.self, decoder: decoder) + } + else if interceptedType == BigInt.self { + return try unbox(value, as: BigInt.self, decoder: decoder) + } + else if interceptedType == BigUInt.self { + return try unbox(value, as: BigUInt.self, decoder: decoder) + } + else if interceptedType == AnyValue.self { + return try unbox(value, as: AnyValue.self, decoder: decoder) + } + else { + fatalError("type not valid for intercept") + } + } + + public static func unbox(_ value: ASN1, as type: Tagged.Type, decoder: IVD) throws -> Tagged? { + guard let decoded = try decode(value, decoder: decoder) else { return nil } + return type.init(tag: value.anyTag, value: decoded) + } + + public static func unbox(_ value: ASN1, as type: AnyString.Type, decoder: IVD) throws -> AnyString? { + switch try decode(value, decoder: decoder) { + case let string as AnyString: return string + case let invalid: + throw DecodingError.typeMismatch( + at: decoder.codingPath, + expectation: type, + reality: invalid as Any + ) + } + } + + public static func unbox(_ value: ASN1, as type: AnyTime.Type, decoder: IVD) throws -> AnyTime? { + switch try decode(value, decoder: decoder) { + case let time as AnyTime: return time + case let invalid: + throw DecodingError.typeMismatch( + at: decoder.codingPath, + expectation: type, + reality: invalid as Any + ) + } + } + + public static func unbox(_ value: ASN1, as type: BitString.Type, decoder: IVD) throws -> BitString? { + switch try decode(value, decoder: decoder) { + case let bitString as BitString: return bitString + case let data as Data: return BitString(length: data.count * 8, bytes: data) + case let invalid: + throw DecodingError.typeMismatch( + at: decoder.codingPath, + expectation: type, + reality: invalid as Any + ) + } + } + + public static func unbox(_ value: ASN1, as type: ObjectIdentifier.Type, decoder: IVD) throws -> ObjectIdentifier? { + switch try decode(value, decoder: decoder) { + case let oid as ObjectIdentifier: return oid + case let invalid: + throw DecodingError.typeMismatch( + at: decoder.codingPath, + expectation: type, + reality: invalid as Any + ) + } + } + /// Returns the given value unboxed from a container. - public static func unbox(_ value: ASN1, as type: Bool.Type, decoder: Decoder) throws -> Bool? { + public static func unbox(_ value: ASN1, as type: Bool.Type, decoder: IVD) throws -> Bool? { switch try decode(value, decoder: decoder) { case let bool as Bool: return bool - case .none: return nil case let invalid: throw DecodingError.typeMismatch( at: decoder.codingPath, expectation: type, - reality: invalid! + reality: invalid as Any ) } } @@ -151,319 +266,338 @@ public struct ASN1DecoderTransform: InternalDecoderTransform, InternalValueDeser return result } - public static func unbox(_ value: ASN1, as type: Int.Type, decoder: Decoder) throws -> Int? { + static func coerce(_ from: ASN1.Integer, at codingPath: [CodingKey]) throws -> T + where T: BinaryFloatingPoint & LosslessStringConvertible { + guard let result = T(from.description) else { + throw overflow(T.self, value: from, at: codingPath) + } + return result + } + + public static func unbox(_ value: ASN1, as type: Int.Type, decoder: IVD) throws -> Int? { switch try decode(value, decoder: decoder) { case let integer as ASN1.Integer: return try coerce(integer, at: decoder.codingPath) case let bitString as BitString: return bitString.integer(Int.self) - case .none: return nil case let invalid: throw DecodingError.typeMismatch( at: decoder.codingPath, expectation: type, - reality: invalid! + reality: invalid as Any ) } } - public static func unbox(_ value: ASN1, as type: Int8.Type, decoder: Decoder) throws -> Int8? { + public static func unbox(_ value: ASN1, as type: Int8.Type, decoder: IVD) throws -> Int8? { switch try decode(value, decoder: decoder) { case let integer as ASN1.Integer: return try coerce(integer, at: decoder.codingPath) case let bitString as BitString: return bitString.integer(Int8.self) - case .none: return nil case let invalid: throw DecodingError.typeMismatch( at: decoder.codingPath, expectation: type, - reality: invalid! + reality: invalid as Any ) } } - public static func unbox(_ value: ASN1, as type: Int16.Type, decoder: Decoder) throws -> Int16? { + public static func unbox(_ value: ASN1, as type: Int16.Type, decoder: IVD) throws -> Int16? { switch try decode(value, decoder: decoder) { case let integer as ASN1.Integer: return try coerce(integer, at: decoder.codingPath) case let bitString as BitString: return bitString.integer(Int16.self) - case .none: return nil case let invalid: throw DecodingError.typeMismatch( at: decoder.codingPath, expectation: type, - reality: invalid! + reality: invalid as Any ) } } - public static func unbox(_ value: ASN1, as type: Int32.Type, decoder: Decoder) throws -> Int32? { + public static func unbox(_ value: ASN1, as type: Int32.Type, decoder: IVD) throws -> Int32? { switch try decode(value, decoder: decoder) { case let integer as ASN1.Integer: return try coerce(integer, at: decoder.codingPath) case let bitString as BitString: return bitString.integer(Int32.self) - case .none: return nil case let invalid: throw DecodingError.typeMismatch( at: decoder.codingPath, expectation: type, - reality: invalid! + reality: invalid as Any ) } } - public static func unbox(_ value: ASN1, as type: Int64.Type, decoder: Decoder) throws -> Int64? { + public static func unbox(_ value: ASN1, as type: Int64.Type, decoder: IVD) throws -> Int64? { switch try decode(value, decoder: decoder) { case let integer as ASN1.Integer: return try coerce(integer, at: decoder.codingPath) case let bitString as BitString: return bitString.integer(Int64.self) - case .none: return nil case let invalid: throw DecodingError.typeMismatch( at: decoder.codingPath, expectation: type, - reality: invalid! + reality: invalid as Any ) } } - public static func unbox(_ value: ASN1, as type: UInt.Type, decoder: Decoder) throws -> UInt? { + public static func unbox(_ value: ASN1, as type: UInt.Type, decoder: IVD) throws -> UInt? { switch try decode(value, decoder: decoder) { case let integer as ASN1.Integer: return try coerce(integer, at: decoder.codingPath) case let bitString as BitString: return bitString.integer(UInt.self) - case .none: return nil case let invalid: throw DecodingError.typeMismatch( at: decoder.codingPath, expectation: type, - reality: invalid! + reality: invalid as Any ) } } - public static func unbox(_ value: ASN1, as type: UInt8.Type, decoder: Decoder) throws -> UInt8? { + public static func unbox(_ value: ASN1, as type: UInt8.Type, decoder: IVD) throws -> UInt8? { switch try decode(value, decoder: decoder) { case let integer as ASN1.Integer: return try coerce(integer, at: decoder.codingPath) case let bitString as BitString: return bitString.integer(UInt8.self) - case .none: return nil case let invalid: throw DecodingError.typeMismatch( at: decoder.codingPath, expectation: type, - reality: invalid! + reality: invalid as Any ) } } - public static func unbox(_ value: ASN1, as type: UInt16.Type, decoder: Decoder) throws -> UInt16? { + public static func unbox(_ value: ASN1, as type: UInt16.Type, decoder: IVD) throws -> UInt16? { switch try decode(value, decoder: decoder) { case let integer as ASN1.Integer: return try coerce(integer, at: decoder.codingPath) case let bitString as BitString: return bitString.integer(UInt16.self) - case .none: return nil case let invalid: throw DecodingError.typeMismatch( at: decoder.codingPath, expectation: type, - reality: invalid! + reality: invalid as Any ) } } - public static func unbox(_ value: ASN1, as type: UInt32.Type, decoder: Decoder) throws -> UInt32? { + public static func unbox(_ value: ASN1, as type: UInt32.Type, decoder: IVD) throws -> UInt32? { switch try decode(value, decoder: decoder) { case let integer as ASN1.Integer: return try coerce(integer, at: decoder.codingPath) case let bitString as BitString: return bitString.integer(UInt32.self) - case .none: return nil case let invalid: throw DecodingError.typeMismatch( at: decoder.codingPath, expectation: type, - reality: invalid! + reality: invalid as Any ) } } - public static func unbox(_ value: ASN1, as type: UInt64.Type, decoder: Decoder) throws -> UInt64? { + public static func unbox(_ value: ASN1, as type: UInt64.Type, decoder: IVD) throws -> UInt64? { switch try decode(value, decoder: decoder) { case let integer as ASN1.Integer: return try coerce(integer, at: decoder.codingPath) case let bitString as BitString: return bitString.integer(UInt64.self) - case .none: return nil case let invalid: throw DecodingError.typeMismatch( at: decoder.codingPath, expectation: type, - reality: invalid! + reality: invalid as Any ) } } - public static func unbox(_ value: ASN1, as type: Float.Type, decoder: Decoder) throws -> Float? { + public static func unbox(_ value: ASN1, as type: BigInt.Type, decoder: IVD) throws -> BigInt? { + switch try decode(value, decoder: decoder) { + case let integer as ASN1.Integer: return integer + case let bitString as BitString: + if bitString.bytes.isEmpty { + return BigInt.zero + } + var data = Data(count: bitString.bytes.count) + bitString.copyOctets(into: &data) + return BigInt(derEncoded: data) + case let invalid: + throw DecodingError.typeMismatch( + at: decoder.codingPath, + expectation: type, + reality: invalid as Any + ) + } + } + + public static func unbox(_ value: ASN1, as type: BigUInt.Type, decoder: IVD) throws -> BigUInt? { + switch try decode(value, decoder: decoder) { + case let integer as ASN1.Integer: + if integer.sign == .minus { + throw overflow(BigUInt.self, value: integer, at: decoder.codingPath) + } + return integer.magnitude + case let bitString as BitString: + if bitString.bytes.isEmpty { + return BigUInt.zero + } + var data = Data(count: bitString.bytes.count) + bitString.copyOctets(into: &data) + return BigUInt(derEncoded: data) + case let invalid: + throw DecodingError.typeMismatch( + at: decoder.codingPath, + expectation: type, + reality: invalid as Any + ) + } + } + + public static func unbox(_ value: ASN1, as type: Float.Type, decoder: IVD) throws -> Float? { switch try decode(value, decoder: decoder) { case let number as Decimal: return try coerce(number, at: decoder.codingPath) - case .none: return nil + case let integer as ASN1.Integer: return try coerce(integer, at: decoder.codingPath) case let invalid: throw DecodingError.typeMismatch( at: decoder.codingPath, expectation: type, - reality: invalid! + reality: invalid as Any ) } } - public static func unbox(_ value: ASN1, as type: Double.Type, decoder: Decoder) throws -> Double? { + public static func unbox(_ value: ASN1, as type: Double.Type, decoder: IVD) throws -> Double? { switch try decode(value, decoder: decoder) { case let number as Decimal: return try coerce(number, at: decoder.codingPath) - case .none: return nil + case let integer as ASN1.Integer: return try coerce(integer, at: decoder.codingPath) case let invalid: throw DecodingError.typeMismatch( at: decoder.codingPath, expectation: type, - reality: invalid! + reality: invalid as Any ) } } - public static func unbox(_ value: ASN1, as type: Decimal.Type, decoder: Decoder) throws -> Decimal? { + public static func unbox(_ value: ASN1, as type: Decimal.Type, decoder: IVD) throws -> Decimal? { switch try decode(value, decoder: decoder) { case let number as Decimal: return number - case .none: return nil + case let integer as ASN1.Integer: return Decimal(string: integer.description) case let invalid: throw DecodingError.typeMismatch( at: decoder.codingPath, expectation: type, - reality: invalid! + reality: invalid as Any ) } } - public static func unbox(_ value: ASN1, as type: String.Type, decoder: Decoder) throws -> String? { + public static func unbox(_ value: ASN1, as type: String.Type, decoder: IVD) throws -> String? { switch try decode(value, decoder: decoder) { case let string as AnyString: return string.storage - case .none: return nil case let invalid: throw DecodingError.typeMismatch( at: decoder.codingPath, expectation: type, - reality: invalid! + reality: invalid as Any ) } } - public static func unbox(_ value: ASN1, as type: UUID.Type, decoder: Decoder) throws -> UUID? { + public static func unbox(_ value: ASN1, as type: UUID.Type, decoder: IVD) throws -> UUID? { switch try decode(value, decoder: decoder) { - case let string as AnyString: return UUID(uuidString: string.storage) - case let data as Data where data.count == 16: + case let string as AnyString: + guard let uuid = UUID(uuidString: string.storage) else { + throw DecodingError.dataCorruptedError( + in: decoder, + debugDescription: "Failed to decode UUID from invalid UUID string" + ) + } + return uuid + case let data as Data: + if data.count != 16 { + throw DecodingError.dataCorruptedError( + in: decoder, + debugDescription: "Failed to decode UUID from binary data that is not 16 bytes" + ) + } return UUID(uuid: data.withUnsafeBytes { $0.bindMemory(to: uuid_t.self).first ?? UUID_NULL }) - case .none: return nil case let invalid: throw DecodingError.typeMismatch( at: decoder.codingPath, expectation: type, - reality: invalid! + reality: invalid as Any ) } } - public static func unbox(_ value: ASN1, as type: Date.Type, decoder: Decoder) throws -> Date? { + public static func unbox(_ value: ASN1, as type: URL.Type, decoder: IVD) throws -> URL? { switch try decode(value, decoder: decoder) { - case let date as Date: return date - case .none: return nil + case let string as AnyString: return URL(string: string.storage) case let invalid: throw DecodingError.typeMismatch( at: decoder.codingPath, expectation: type, - reality: invalid! + reality: invalid as Any ) } } - public static func unbox(_ value: ASN1, as type: Data.Type, decoder: Decoder) throws -> Data? { + public static func unbox(_ value: ASN1, as type: Date.Type, decoder: IVD) throws -> Date? { switch try decode(value, decoder: decoder) { - case let data as Data: return data - case let bitString as BitString: return bitString.bytes - case .none: return nil + case let time as AnyTime: + return time.zonedDate.utcDate case let invalid: throw DecodingError.typeMismatch( at: decoder.codingPath, expectation: type, - reality: invalid! + reality: invalid as Any ) } } - public static func intercepts(_ type: Decodable.Type) -> Bool { - return - type == ASN1.self || type is Tagged.Type || - type == AnyString.self || type == AnyTime.self || - type == BitString.self || type == ObjectIdentifier.self || type == ASN1.Integer.self - } - - public static func unbox(_ value: ASN1, interceptedType: Decodable.Type, decoder: Decoder) throws -> Any? { - if interceptedType == ASN1.self { - return value - } - - guard let decoded = try decode(value, decoder: decoder) else { return nil } - if interceptedType == ASN1.Integer.self { - guard let int = decoded as? ASN1.Integer else { - throw DecodingError.typeMismatch( - at: decoder.codingPath, - expectation: interceptedType, - reality: decoded - ) - } - return int - } - else if interceptedType == AnyString.self { - guard let string = decoded as? AnyString else { - throw DecodingError.typeMismatch( - at: decoder.codingPath, - expectation: interceptedType, - reality: decoded - ) - } - return string - } - else if interceptedType == AnyTime.self { - guard let time = decoded as? AnyTime else { - throw DecodingError.typeMismatch( - at: decoder.codingPath, - expectation: interceptedType, - reality: decoded - ) - } - return time - } - else if interceptedType == BitString.self { - switch decoded { - case let bitString as BitString: return bitString - case let data as Data: return BitString(length: data.count * 8, bytes: data) - default: - throw DecodingError.typeMismatch( - at: decoder.codingPath, - expectation: interceptedType, - reality: decoded - ) - } - } - else if interceptedType == ObjectIdentifier.self { - guard let oid = decoded as? ObjectIdentifier else { - throw DecodingError.typeMismatch( - at: decoder.codingPath, - expectation: interceptedType, - reality: decoded - ) - } - return oid - } - else if let taggedType = interceptedType as? Tagged.Type { - return taggedType.init(tag: value.anyTag, value: decoded) - } - else { - fatalError("Unhandled type") + public static func unbox(_ value: ASN1, as type: Data.Type, decoder: IVD) throws -> Data? { + switch try decode(value, decoder: decoder) { + case let data as Data: return data + case let bitString as BitString: return bitString.bytes + case let invalid: + throw DecodingError.typeMismatch( + at: decoder.codingPath, + expectation: type, + reality: invalid as Any + ) } } - public static func valueToUnkeyedValues(_ value: ASN1, decoder: Decoder) throws -> [ASN1]? { + public static func unbox(_ value: ASN1, as type: AnyValue.Type, decoder: IVD) throws -> AnyValue { + switch value.absolute { + case .boolean(let value): return .bool(value) + case .integer(let value): return .integer(value) + case .bitString(let length, let bytes): return .string(BitString(length: length, bytes: bytes).bitFlagString) + case .octetString(let value): return .data(value) + case .null: return .nil + case .objectIdentifier(let fields): return .array(fields.map(AnyValue.uint64)) + case .real(let value): return .decimal(value) + case .utf8String(let value): return .string(value) + case .sequence(let values): return .array(try values.map { try unbox($0, as: AnyValue.self, decoder: decoder) }) + case .set(let values): return .array(try values.map { try unbox($0, as: AnyValue.self, decoder: decoder) }) + case .numericString(let value): return .string(value) + case .printableString(let value): return .string(value) + case .teletexString(let value): return .string(value) + case .videotexString(let value): return .string(value) + case .ia5String(let value): return .string(value) + case .utcTime(let value): return .date(value.utcDate) + case .generalizedTime(let value): return .date(value.utcDate) + case .graphicString(let value): return .string(value) + case .visibleString(let value): return .string(value) + case .generalString(let value): return .string(value) + case .universalString(let value): return .string(value) + case .characterString(let value): return .string(value) + case .bmpString(let value): return .string(value) + default: + fatalError("unexpected value") + } + } + + public static func valueToUnkeyedValues(_ value: ASN1, decoder: IVD) throws -> UnkeyedValues? { guard let decoded = try decode(value, decoder: decoder) else { return nil } - guard let collection = decoded as? [ASN1] else { - fatalError() + guard let collection = decoded as? UnkeyedValues else { + return nil } decoder.state.save(container: UnkeyedContainer(backing: collection), forCodingPath: decoder.codingPath) @@ -471,12 +605,12 @@ public struct ASN1DecoderTransform: InternalDecoderTransform, InternalValueDeser return collection } - public static func valueToKeyedValues(_ value: ASN1, decoder: Decoder) throws -> [String: ASN1]? { + public static func valueToKeyedValues(_ value: ASN1, decoder: IVD) throws -> KeyedValues? { guard let decoded = try decode(value, decoder: decoder) else { return nil } - guard let structure = decoded as? [String: ASN1] else { - fatalError() + guard let structure = decoded as? KeyedValues else { + return nil } decoder.state.save(container: KeyedContainer(backing: structure), forCodingPath: decoder.codingPath) @@ -530,28 +664,21 @@ extension SchemaState { continue } - var results = [String: ASN1]() + var results = OrderedDictionary() var valueIdx = 0 for (fieldName, fieldSchema) in fields { let possibleValue: ASN1? = valueIdx < values.count ? values[valueIdx] : nil guard let value = possibleValue else { - // No value... asign default (if available) - if fieldSchema.defaultValue != nil { - var fieldSchemaState = try SchemaState(initial: fieldSchema.unwrapDirectives) - results[fieldName] = try fieldSchemaState.encode(nil) - } + // No value... assign default (if available) + results[fieldName] = try fieldSchema.defaultValueEncoded() continue } if let fieldSchemaPossibleTags = fieldSchema.possibleTags, !fieldSchemaPossibleTags.contains(value.anyTag) { - // No value... assign default (if available) - if fieldSchema.defaultValue != nil { - var fieldSchemaState = try SchemaState(initial: fieldSchema.unwrapDirectives) - results[fieldName] = try fieldSchemaState.encode(nil) - } - // value tag not in schema's possible tags... skip to next field and try value again + // Value tag not in schema's possible tags... assign defaul and skip to next field + results[fieldName] = try fieldSchema.defaultValueEncoded() continue } @@ -712,11 +839,10 @@ extension SchemaState { case .implicit(let schemaTag, in: let schemaTagClass, let implicitSchema): - guard let tagged = value.taggedValue, ASN1.Tag.tag( - from: schemaTag, - in: schemaTagClass, - constructed: implicitSchema.isCollection - ) == tagged.tag else { + guard + let tagged = value.taggedValue, + ASN1.Tag.tag(from: schemaTag, in: schemaTagClass, constructed: implicitSchema.isCollection) == tagged.tag + else { // try next possible schema continue } @@ -731,18 +857,24 @@ extension SchemaState { return item.setValue! default: + if tagged.data.isEmpty { + if let defaultValue = implicitSchema.defaultValue { + return defaultValue.currentValue + } + throw DecodingError.badValue(value, errorContext("Implicit tagged value contains no data")) + } guard let nextPossibleTags = implicitSchema.possibleTags, nextPossibleTags.count == 1 else { throw SchemaError.ambiguousImplicitTag(errorContext("Implicit schema is ambiguous")) } - return try ASN1DERReader.parseItem(tagged.data, as: nextPossibleTags[0]).currentValue } case .explicit(let schemaTag, in: let schemaTagClass, let explicitSchema): - guard let tagged = value.taggedValue, - ASN1.Tag.tag(from: schemaTag, in: schemaTagClass, constructed: true) == tagged.tag + guard + let tagged = value.taggedValue, + ASN1.Tag.tag(from: schemaTag, in: schemaTagClass, constructed: true) == tagged.tag else { // try next possible schema continue diff --git a/Sources/PotentASN1/ASN1Encoder.swift b/Sources/PotentASN1/ASN1Encoder.swift index ca2028568..7f4fbb373 100644 --- a/Sources/PotentASN1/ASN1Encoder.swift +++ b/Sources/PotentASN1/ASN1Encoder.swift @@ -8,7 +8,9 @@ // Distributed under the MIT License, See LICENSE for details. // +import BigInt import Foundation +import OrderedCollections import PotentCodables @@ -78,7 +80,6 @@ public class ASN1Encoder: ValueEncoder, EncodesToDat public struct ASN1EncoderTransform: InternalEncoderTransform, InternalValueSerializer { public typealias Value = ASN1 - public typealias Encoder = InternalValueEncoder public struct Options: InternalEncoderOptions { public var schema: Schema @@ -91,9 +92,7 @@ public struct ASN1EncoderTransform: InternalEncoderTransform, InternalValueSeria public static var emptyKeyedContainer = ASN1.sequence([]) public static var emptyUnkeyedContainer = ASN1.sequence([]) - static func encode(_ value: Any?, encoder: Encoder) throws -> ASN1 { -// print("codingPath =", encoder.codingPath.map { $0.stringValue }) -// print("containers =", encoder.containerTypes, "\n\n") + static func encode(_ value: Any?, encoder: IVE) throws -> ASN1 { if encoder.state == nil { encoder.state = try SchemaState(initial: encoder.options.schema) @@ -111,50 +110,108 @@ public struct ASN1EncoderTransform: InternalEncoderTransform, InternalValueSeria return try encoder.state.encode(value) } - public static func boxNil(encoder: Encoder) throws -> ASN1 { try encode(nil, encoder: encoder) } - public static func box(_ value: Bool, encoder: Encoder) throws -> ASN1 { try encode(value, encoder: encoder) } - public static func box(_ value: Int, encoder: Encoder) throws -> ASN1 { try encode(value, encoder: encoder) } - public static func box(_ value: Int8, encoder: Encoder) throws -> ASN1 { try encode(value, encoder: encoder) } - public static func box(_ value: Int16, encoder: Encoder) throws -> ASN1 { try encode(value, encoder: encoder) } - public static func box(_ value: Int32, encoder: Encoder) throws -> ASN1 { try encode(value, encoder: encoder) } - public static func box(_ value: Int64, encoder: Encoder) throws -> ASN1 { try encode(value, encoder: encoder) } - public static func box(_ value: UInt, encoder: Encoder) throws -> ASN1 { try encode(value, encoder: encoder) } - public static func box(_ value: UInt8, encoder: Encoder) throws -> ASN1 { try encode(value, encoder: encoder) } - public static func box(_ value: UInt16, encoder: Encoder) throws -> ASN1 { try encode(value, encoder: encoder) } - public static func box(_ value: UInt32, encoder: Encoder) throws -> ASN1 { try encode(value, encoder: encoder) } - public static func box(_ value: UInt64, encoder: Encoder) throws -> ASN1 { try encode(value, encoder: encoder) } - public static func box(_ value: String, encoder: Encoder) throws -> ASN1 { try encode(value, encoder: encoder) } - public static func box(_ value: URL, encoder: Encoder) throws -> ASN1 { try encode(value, encoder: encoder) } - public static func box(_ value: UUID, encoder: Encoder) throws -> ASN1 { try encode(value, encoder: encoder) } - public static func box(_ value: Float, encoder: Encoder) throws -> ASN1 { try encode(value, encoder: encoder) } - public static func box(_ value: Double, encoder: Encoder) throws -> ASN1 { try encode(value, encoder: encoder) } - public static func box(_ value: Decimal, encoder: Encoder) throws -> ASN1 { try encode(value, encoder: encoder) } - public static func box(_ value: Data, encoder: Encoder) throws -> ASN1 { try encode(value, encoder: encoder) } - public static func box(_ value: Date, encoder: Encoder) throws -> ASN1 { try encode(value, encoder: encoder) } - public static func intercepts(_ type: Encodable.Type) -> Bool { - return - type == ASN1.self || type is Tagged.Type || - type == AnyString.self || type == AnyTime.self || - type == BitString.self || type == ObjectIdentifier.self || type == ASN1.Integer.self + if type as? Tagged.Type != nil { + return true + } + return type == ASN1.self + || type == AnyString.self || type == AnyTime.self || type == ZonedDate.self + || type == BitString.self || type == ObjectIdentifier.self || type == ASN1.Integer.self + || type == Date.self || type == NSDate.self + || type == Data.self || type == NSData.self + || type == URL.self || type == NSURL.self + || type == UUID.self || type == NSUUID.self + || type == Decimal.self || type == NSDecimalNumber.self + || type == BigInt.self + || type == BigUInt.self + || type == AnyValue.self } - public static func box(_ value: Any, interceptedType: Encodable.Type, encoder: Encoder) throws -> ASN1 { - - if let value = value as? ASN1 { - return value + public static func box(_ value: Any, interceptedType: Encodable.Type, encoder: IVE) throws -> ASN1 { + var value: Any? = value + if let anyValue = value as? AnyValue { + return try box(anyValue, encoder: encoder) + } + if let uuid = value as? UUID { + value = uuid.uuidString + } + else if let url = value as? URL { + value = url.absoluteString } - return try encode(value, encoder: encoder) } - public static func unkeyedValuesToValue(_ values: [ASN1], encoder: Encoder) throws -> ASN1 { + public static func boxNil(encoder: IVE) throws -> ASN1 { try encode(nil, encoder: encoder) } + public static func box(_ value: Bool, encoder: IVE) throws -> ASN1 { try encode(value, encoder: encoder) } + public static func box(_ value: Int, encoder: IVE) throws -> ASN1 { try encode(value, encoder: encoder) } + public static func box(_ value: Int8, encoder: IVE) throws -> ASN1 { try encode(value, encoder: encoder) } + public static func box(_ value: Int16, encoder: IVE) throws -> ASN1 { try encode(value, encoder: encoder) } + public static func box(_ value: Int32, encoder: IVE) throws -> ASN1 { try encode(value, encoder: encoder) } + public static func box(_ value: Int64, encoder: IVE) throws -> ASN1 { try encode(value, encoder: encoder) } + public static func box(_ value: UInt, encoder: IVE) throws -> ASN1 { try encode(value, encoder: encoder) } + public static func box(_ value: UInt8, encoder: IVE) throws -> ASN1 { try encode(value, encoder: encoder) } + public static func box(_ value: UInt16, encoder: IVE) throws -> ASN1 { try encode(value, encoder: encoder) } + public static func box(_ value: UInt32, encoder: IVE) throws -> ASN1 { try encode(value, encoder: encoder) } + public static func box(_ value: UInt64, encoder: IVE) throws -> ASN1 { try encode(value, encoder: encoder) } + public static func box(_ value: Float, encoder: IVE) throws -> ASN1 { try encode(value, encoder: encoder) } + public static func box(_ value: Double, encoder: IVE) throws -> ASN1 { try encode(value, encoder: encoder) } + public static func box(_ value: String, encoder: IVE) throws -> ASN1 { try encode(value, encoder: encoder) } + + public static func box(_ value: AnyValue, encoder: IVE) throws -> ASN1 { + switch value { + case .nil: return try encode(nil, encoder: encoder) + case .bool(let value): return try encode(value, encoder: encoder) + case .string(let value): return try encode(value, encoder: encoder) + case .int8(let value): return try encode(value, encoder: encoder) + case .int16(let value): return try encode(value, encoder: encoder) + case .int32(let value): return try encode(value, encoder: encoder) + case .int64(let value): return try encode(value, encoder: encoder) + case .uint8(let value): return try encode(value, encoder: encoder) + case .uint16(let value): return try encode(value, encoder: encoder) + case .uint32(let value): return try encode(value, encoder: encoder) + case .uint64(let value): return try encode(value, encoder: encoder) + case .integer(let value): return try encode(value, encoder: encoder) + case .unsignedInteger(let value): return try encode(value, encoder: encoder) + case .float16(let value): return try encode(Double(value), encoder: encoder) + case .float(let value): return try encode(value, encoder: encoder) + case .double(let value): return try encode(value, encoder: encoder) + case .decimal(let value): return try encode(value, encoder: encoder) + case .data(let value): return try encode(value, encoder: encoder) + case .url(let value): return try encode(value.absoluteString, encoder: encoder) + case .uuid(let value): return try encode(value.uuidString, encoder: encoder) + case .date(let value): return try encode(value, encoder: encoder) + case .array(let value): + return try encoder.subEncode { subEncoder in + + let container = subEncoder.unkeyedContainer() + + for (index, element) in value.enumerated() { + try subEncoder.withCodingKey(AnyCodingKey(intValue: index)) { + container.append(try box(element, encoder: encoder)) + } + } - return try encode(values, encoder: encoder) + } ?? .null + case .dictionary(let value): + return try encoder.subEncode { subEncoder in + + let container = subEncoder.keyedContainer() + + for (key, value) in value { + try subEncoder.withCodingKey(AnyCodingKey(stringValue: key.stringValue!)) { + container[key.stringValue!] = try box(value, encoder: encoder) + } + } + + } ?? .null + } } - public static func keyedValuesToValue(_ values: [String: ASN1], encoder: Encoder) throws -> ASN1 { + public static func unkeyedValuesToValue(_ values: UnkeyedValues, encoder: IVE) throws -> ASN1 { + return try encode(Array(values), encoder: encoder) + } + public static func keyedValuesToValue(_ values: KeyedValues, encoder: IVE) throws -> ASN1 { return try encode(values, encoder: encoder) } @@ -162,16 +219,6 @@ public struct ASN1EncoderTransform: InternalEncoderTransform, InternalValueSeria return try ASN1Serialization.der(from: value) } - private static func doubleFrom(_ decimal: Decimal, _ encoder: Encoder) throws -> Double { - guard let value = Double(decimal.description) else { - throw Swift.EncodingError.invalidValue(decimal, .init( - codingPath: encoder.codingPath, - debugDescription: "" - )) - } - return value - } - } @@ -186,7 +233,8 @@ extension SchemaState { mutating func encode(_ value: Any?) throws -> ASN1 { guard let encoded = try tryEncode(value) else { - throw EncodingError.badValue(value as Any, errorContext("No schemas \(currentPossibleStates) match value")) + let stateOptions = currentPossibleStates.map { $0.description }.joined(separator: " or ") + throw EncodingError.badValue(value as Any, errorContext("No schemas match value: \(stateOptions)")) } return encoded @@ -208,7 +256,7 @@ extension SchemaState { case .sequence(let fields): - guard let values = value as? [String: ASN1] else { + guard let values = value as? OrderedDictionary else { // try next possible schema continue } @@ -432,6 +480,7 @@ extension SchemaState { switch value { case let int as ASN1.Integer: return try check(int) + case let int as BigUInt: return try check(ASN1.Integer(int)) case let int as Int8: return try check(ASN1.Integer(int)) case let int as Int16: return try check(ASN1.Integer(int)) case let int as Int32: return try check(ASN1.Integer(int)) @@ -566,7 +615,7 @@ extension SchemaState { } encoded = value } - let encodedData = try ASN1DERReader.parseTagged(data: ASN1DERWriter.write(encoded)).data + let encodedData = try ASN1DERReader.parseTagged(data: ASN1DERWriter.write(encoded))?.data ?? Data() return .tagged(ASN1.Tag.tag(from: tag, in: tagClass, constructed: implicitSchema.isCollection), encodedData) diff --git a/Sources/PotentASN1/AnyString.swift b/Sources/PotentASN1/AnyString.swift index fbb2cfdc9..486fb522d 100644 --- a/Sources/PotentASN1/AnyString.swift +++ b/Sources/PotentASN1/AnyString.swift @@ -63,7 +63,7 @@ public struct AnyString: Equatable, Hashable { case bmp } - /// Specifies the exact ``Kind`` of string. + /// Specifies the exact ``Kind-swift.enum`` of string. /// /// When encoding, selects the specific kind when the schema allows /// for multiple kinds of strings. @@ -75,7 +75,7 @@ public struct AnyString: Equatable, Hashable { public var storage: String /// Initialize with a Swift String and explicit ASN.1 kind. - public init(_ value: String, kind: Kind? = nil) { + public init(_ value: String, kind: Kind?) { self.kind = kind storage = value } diff --git a/Sources/PotentASN1/AnyTime.swift b/Sources/PotentASN1/AnyTime.swift index 80a7f25b3..1fabd1395 100644 --- a/Sources/PotentASN1/AnyTime.swift +++ b/Sources/PotentASN1/AnyTime.swift @@ -31,7 +31,7 @@ public struct AnyTime: Equatable, Hashable, Codable { } - /// Specifies the exact ``Kind`` of time. + /// Specifies the exact ``Kind-swift.enum`` of time. /// /// When encoding, selects the specific kind when the schema allows /// for multiple kinds of times. diff --git a/Sources/PotentASN1/BigInts.swift b/Sources/PotentASN1/BigInts.swift index 7ab5e4f24..6bef8921b 100644 --- a/Sources/PotentASN1/BigInts.swift +++ b/Sources/PotentASN1/BigInts.swift @@ -14,29 +14,29 @@ import Foundation public extension BigUInt { - /// Initializes an integer from the provided `Data`. + /// Initializes an integer from the provided `Data` in ASN.1 DER format. /// /// - Parameter data: Base-256 representation, in network (big-endian) byte order. /// - init(serialized data: Data) { + init(derEncoded data: Data) { self.init(data) } - /// Serializes the integer to `Data`. + /// Encodes the integer to `Data` in ASN.1 DER format. /// /// - Returns: Base-256 representation, in network (big-endian) byte order. /// - func serialized() -> Data { serialize() } + func derEncoded() -> Data { serialize() } } public extension BigInt { - /// Initializes an integer from the provided `Data`. + /// Initializes an integer from the provided `Data` in ASN.1 DER format. /// /// - Parameter data: Two's compliment, base-256 representation, in network (big-endian) byte order. /// - init(serialized data: Data) { + init(derEncoded data: Data) { let sign: Sign let magnitude: BigUInt if (data[0] & 0x80) == 0x80 { @@ -50,11 +50,11 @@ public extension BigInt { self.init(sign: sign, magnitude: magnitude) } - /// Serializes the integer to `Data`. + /// Encodes the integer to `Data` in ASN.1 DER format. /// /// - Returns: Two's compliment, base-256 representation, in network (big-endian) byte order. /// - func serialized() -> Data { + func derEncoded() -> Data { var bytes = magnitude.serialize() if bytes.isEmpty || (bytes[0] & 0x80) == 0x80 { // Make room for sign @@ -63,13 +63,22 @@ public extension BigInt { if sign == .plus { return bytes } - return bytes.twosCompliment() + var twos = bytes.twosCompliment() + // Compact + while twos.canRemoveLeadingByte() { + twos.remove(at: 0) + } + return twos } } private extension Data { + func canRemoveLeadingByte() -> Bool { + count > 1 && ((self[0] == 0xff && self[1] & 0x80 == 0x80) || (self[0] == 0x00 && self[1] & 0x80 == 0x00)) + } + /// Two's compliment of _big endian_ integer func twosCompliment() -> Data { var data = self diff --git a/Sources/PotentASN1/BitString.swift b/Sources/PotentASN1/BitString.swift index f663c81e0..7a9bc8c0c 100644 --- a/Sources/PotentASN1/BitString.swift +++ b/Sources/PotentASN1/BitString.swift @@ -29,6 +29,7 @@ public struct BitString: Equatable, Hashable, CustomStringConvertible { /// - bytes: Byte storage for bits. /// public init(length: Int, bytes: Data) { + precondition(bytes.count * 8 >= length) self.bytes = bytes self.length = length } @@ -194,7 +195,7 @@ extension BitString: Codable { /// public func encode(to encoder: Encoder) throws { var container = encoder.singleValueContainer() - try container.encode(description) + try container.encode(bitFlagString) } } diff --git a/Sources/PotentCodables/Dates.swift b/Sources/PotentASN1/Dates.swift similarity index 83% rename from Sources/PotentCodables/Dates.swift rename to Sources/PotentASN1/Dates.swift index 254ffc9e5..e5af27356 100644 --- a/Sources/PotentCodables/Dates.swift +++ b/Sources/PotentASN1/Dates.swift @@ -9,12 +9,10 @@ // import Foundation +import PotentCodables -public struct SuffixedDateFormatter { - public static func optionalFractionalSeconds(basePattern: String) -> SuffixedDateFormatter { - SuffixedDateFormatter(basePattern: basePattern, secondsPattern: ".S") { $0.contains(".") } - } +public struct SuffixedDateFormatter { private let noSuffixes: DateFormatter private let zoneSuffix: DateFormatter @@ -64,13 +62,14 @@ public struct SuffixedDateFormatter { public func date(from string: String) -> ZonedDate? { let parsedDate: Date? - let zoneStartIndex = string.firstIndex { (ch: Character) in ch == "-" || ch == "+" || ch == "Z" } ?? string.endIndex + let zoneStartIndex = string.firstIndex { char in char == "-" || char == "+" || char == "Z" } ?? string.endIndex let stringWithoutZone = String(string[string.startIndex ..< zoneStartIndex]) + let hasSeconds = checkHasSeconds(stringWithoutZone) let hasZone = string != stringWithoutZone - if checkHasSeconds(stringWithoutZone) && hasZone { + if hasSeconds && hasZone { parsedDate = zoneAndSecondsSuffixes.date(from: string) } - else if checkHasSeconds(stringWithoutZone) { + else if hasSeconds { parsedDate = secondsSuffix.date(from: string) } else if hasZone { @@ -82,7 +81,7 @@ public struct SuffixedDateFormatter { guard parsedDate != nil else { return nil } - let parsedTimeZone = TimeZone.timeZone(from: string) ?? .current + let parsedTimeZone = TimeZone.timeZone(from: String(string[zoneStartIndex...])) ?? .current return ZonedDate(date: parsedDate!, timeZone: parsedTimeZone) } diff --git a/Sources/PotentASN1/Schema.swift b/Sources/PotentASN1/Schema.swift index 4d2962def..e9df16bbf 100644 --- a/Sources/PotentASN1/Schema.swift +++ b/Sources/PotentASN1/Schema.swift @@ -47,12 +47,12 @@ import PotentCodables /// .decode(TBSCertificate.self, from: data) /// ``` /// -public indirect enum Schema { +public indirect enum Schema: Equatable, Hashable { public typealias DynamicMap = [ASN1: Schema] public typealias StructureSequenceMap = OrderedDictionary - public enum Size: CustomStringConvertible { + public enum Size: Equatable, Hashable { case min(Int) case max(Int) case `is`(Int) @@ -67,19 +67,10 @@ public indirect enum Schema { } } - public func `in`(_ range: ClosedRange) -> Size { + public static func `in`(_ range: ClosedRange) -> Size { return .range(range.lowerBound, range.upperBound) } - public var description: String { - switch self { - case .min(let min): return "\(min)..MAX" - case .max(let max): return "0..\(max)" - case .is(let size): return "\(size)..\(size)" - case .range(let min, let max): return "\(min)..\(max)" - } - } - } case sequence(StructureSequenceMap = [:]) @@ -167,6 +158,14 @@ public indirect enum Schema { } } + public func defaultValueEncoded() throws -> ASN1? { + guard defaultValue != nil else { + return nil + } + var schemaState = try SchemaState(initial: unwrapDirectives) + return try schemaState.encode(nil) + } + public var unwrapDirectives: Schema { switch self { case .type(let schema): return schema.unwrapDirectives @@ -264,9 +263,9 @@ public indirect enum Schema { } -extension Schema: CustomDebugStringConvertible { +extension Schema: CustomStringConvertible { - public var debugDescription: String { + public var description: String { var result = "" var indent = "" @@ -288,7 +287,7 @@ extension Schema: CustomDebugStringConvertible { append("SEQUENCE ::= {") for (field, fieldSchema) in fields { - append("\n\(field) \(fieldSchema.debugDescription)") + append("\n\(field) \(fieldSchema.description)") } case .sequenceOf(let schema, size: let size): @@ -303,7 +302,7 @@ extension Schema: CustomDebugStringConvertible { append("SIZE (\(size)) ") } - append("OF\n\(schema.debugDescription)") + append("OF\n\(schema.description)") case .setOf(let schema, size: let size): indent += " " @@ -317,7 +316,7 @@ extension Schema: CustomDebugStringConvertible { append("SIZE (\(size)) ") } - append("OF\n\(schema.debugDescription)") + append("OF\n\(schema.description)") case .choiceOf(let schemas): indent += " " @@ -328,19 +327,19 @@ extension Schema: CustomDebugStringConvertible { append("CHOICE ::= {") for schema in schemas { - append("\n\(schema.debugDescription)") + append("\n\(schema.description)") } case .optional(let schema): - append("\(schema.debugDescription) OPTIONAL") + append("\(schema.description) OPTIONAL") case .implicit(let tag, let inClass, let schema): let tagPrefix = inClass != .contextSpecific ? String(describing: inClass).uppercased() : "" - append("[\(tagPrefix)\(tag)] \(schema.debugDescription) ") + append("[\(tagPrefix)\(tag)] IMPLICIT \(schema.description)") case .explicit(let tag, let inClass, let schema): let tagPrefix = inClass != .contextSpecific ? String(describing: inClass).uppercased() : "" - append("[\(tagPrefix)\(tag)] EXPLICIT \(schema.debugDescription) ") + append("[\(tagPrefix)\(tag)] EXPLICIT \(schema.description)") case .boolean(let def): append("BOOLEAN") @@ -351,7 +350,7 @@ extension Schema: CustomDebugStringConvertible { case .integer(let allowed, let def): append("INTEGER") if let allowed = allowed { - append(" { \(allowed.map { String($0) }.joined()) }") + append(" { \(allowed.map { String($0) }.joined(separator: ", ")) }") } if let def = def { append(" DEFAULT \(String(def).uppercased())") @@ -369,8 +368,11 @@ extension Schema: CustomDebugStringConvertible { append(" (\(size))") } - case .objectIdentifier: + case .objectIdentifier(let allowed): append("OBJECT IDENTIFIER") + if let allowed = allowed { + append(" { \(allowed.map { $0.description }.sorted().joined(separator: " , ")) }") + } case .real: append("REAL") @@ -392,19 +394,49 @@ extension Schema: CustomDebugStringConvertible { case .null: append("NULL") - case .dynamic, .any: + case .any: append("ANY") + case .dynamic(let unknownSchema, let map): + indent += " " + defer { + indent.removeLast(2) + append("\n}") + } + + append("DYNAMIC {") + + for (key, schema) in map { + append("\nCASE \(key.unwrapped ?? key) (\(key.tagName)): \(schema.description)") + } + + if let unknownSchema = unknownSchema { + append("\nELSE: \(unknownSchema.description)") + } + case .versioned(let versionRange, let versionedSchema): append( - "\n\(versionedSchema.debugDescription) -- for version (\(versionRange.lowerBound)..\(versionRange.upperBound))" + "\(versionedSchema.description) -- for version (\(versionRange.lowerBound)..\(versionRange.upperBound))" ) case .type(let schema), .version(let schema): - append(schema.debugDescription) + append(schema.description) } return result } } + +extension Schema.Size: CustomStringConvertible { + + public var description: String { + switch self { + case .min(let min): return "\(min)..MAX" + case .max(let max): return "0..\(max)" + case .is(let size): return "\(size)" + case .range(let min, let max): return "\(min)..\(max)" + } + } + +} diff --git a/Sources/PotentASN1/SchemaState.swift b/Sources/PotentASN1/SchemaState.swift index 65c9a7e5f..904e44291 100644 --- a/Sources/PotentASN1/SchemaState.swift +++ b/Sources/PotentASN1/SchemaState.swift @@ -33,8 +33,10 @@ public struct SchemaState { case undefinedField(String, SchemaError.Context) case structureMismatch(SchemaError.Context) case versionCheck(SchemaError.Context) - case unknownDynamicType(SchemaError.Context) + case unknownDynamicValue(SchemaError.Context) case ambiguousImplicitTag(SchemaError.Context) + case noScopeAvailable + case notCollection } class Scope { @@ -54,9 +56,9 @@ public struct SchemaState { var description: String { switch self { - case .schema(let schema): return String(describing: schema) + case .schema(let schema): return "\(schema)" case .nothing: return "NONE" - case .disallowed(let schema): return String(describing: schema) + case .disallowed(let schema): return "\(schema) DISALLOWED" } } } @@ -140,7 +142,7 @@ public struct SchemaState { case .sequence(let fields): guard currentKey.intValue == nil else { - fatalError("Not a collection") + throw SchemaError.notCollection } let fieldName = currentKey.stringValue @@ -172,7 +174,7 @@ public struct SchemaState { return try step(into: schema) default: - fatalError("state stack corrupt, stepping into scalar schema") + return [] } } @@ -193,14 +195,14 @@ public struct SchemaState { case .type(let typeSchema): - guard let scope = nearestScope else { fatalError("no scope") } + guard let scope = nearestScope else { throw SchemaError.noScopeAvailable } scope.typeFieldName = currentKey.stringValue return try expand(schema: typeSchema) case .dynamic(let unknownTypeSchema, let dynamicSchemaMappings): - guard let nearestScope = nearestScope else { fatalError("no scope") } + guard let nearestScope = nearestScope else { throw SchemaError.noScopeAvailable } guard let typeFieldName = nearestScope.typeFieldName else { throw SchemaError.noDynamicTypeDefined("Dynamic type referenced but none marked in structure's schema") @@ -211,14 +213,14 @@ public struct SchemaState { } guard let dynamicSchema = (dynamicSchemaMappings[dynamicType] ?? unknownTypeSchema) else { - throw SchemaError.unknownDynamicType(errorContext("Dynamic type not found in mapping")) + throw SchemaError.unknownDynamicValue(errorContext("Dynamic value not found in mapping")) } return try expand(schema: dynamicSchema) case .version(let versionSchema): - guard let scope = nearestScope else { fatalError("no scope") } + guard let scope = nearestScope else { throw SchemaError.noScopeAvailable } scope.versionField = (currentKey.stringValue, versionSchema) @@ -226,7 +228,7 @@ public struct SchemaState { case .versioned(range: let allowedRange, let versionedSchema): - guard let nearestScope = nearestScope else { fatalError("no scope") } + guard let nearestScope = nearestScope else { throw SchemaError.noScopeAvailable } guard let (versionFieldName, versionFieldSchema) = nearestScope.versionField else { throw SchemaError.noVersionDefined("Version referenced but none marked in structure's schema") diff --git a/Sources/PotentASN1/TaggedValue.swift b/Sources/PotentASN1/TaggedValue.swift index 2cc7d15bb..36482f8a7 100644 --- a/Sources/PotentASN1/TaggedValue.swift +++ b/Sources/PotentASN1/TaggedValue.swift @@ -13,7 +13,7 @@ import Foundation /// ASN.1 encoded data with tag describing its contents. /// -public struct TaggedValue: Codable { +public struct TaggedValue: Codable, Equatable, Hashable { var tag: UInt8 var data: Data diff --git a/Sources/PotentCBOR/CBOR.swift b/Sources/PotentCBOR/CBOR.swift index c678472fd..d96c3a5e7 100644 --- a/Sources/PotentCBOR/CBOR.swift +++ b/Sources/PotentCBOR/CBOR.swift @@ -11,9 +11,6 @@ import Foundation import OrderedCollections import PotentCodables -#if arch(x86_64) -import Float16 -#endif /// General CBOR value. @@ -35,7 +32,7 @@ import Float16 /// cborArray[0] /// @dynamicMemberLookup -public indirect enum CBOR: Equatable, Hashable { +public indirect enum CBOR { /// A CBOR `tag` for tagged values supported by the specification. /// @@ -48,11 +45,7 @@ public indirect enum CBOR: Equatable, Hashable { } -#if arch(x86_64) - public typealias Half = float16 -#else public typealias Half = Float16 -#endif public typealias Float = Float32 public typealias Double = Float64 @@ -74,74 +67,6 @@ public indirect enum CBOR: Equatable, Hashable { case float(Float) case double(Double) - public var isNull: Bool { - if case .null = self { return true } - return false - } - - public var booleanValue: Bool? { - guard case .boolean(let bool) = untagged else { return nil } - return bool - } - - public var bytesStringValue: Data? { - guard case .byteString(let data) = untagged else { return nil } - return data - } - - public var utf8StringValue: String? { - guard case .utf8String(let string) = untagged else { return nil } - return string - } - - public var arrayValue: Array? { - guard case .array(let array) = untagged else { return nil } - return array - } - - public var mapValue: Map? { - guard case .map(let map) = untagged else { return nil } - return map - } - - public var halfValue: Half? { - guard case .half(let half) = untagged else { return nil } - return half - } - - public var floatValue: Float? { - guard case .float(let float) = untagged else { return nil } - return float - } - - public var doubleValue: Double? { - guard case .double(let double) = untagged else { return nil } - return double - } - - public var simpleValue: UInt8? { - guard case .simple(let simple) = untagged else { return nil } - return simple - } - - public var isNumber: Bool { - switch untagged { - case .unsignedInt, .negativeInt, .half, .float, .double: return true - default: return false - } - } - - public var numberValue: Double? { - switch untagged { - case .unsignedInt(let uint): return Double(exactly: uint) - case .negativeInt(let nint): return Double(exactly: nint).map { -1 - $0 } - case .half(let half): return Double(half) - case .float(let float): return Double(float) - case .double(let double): return double - default: return nil - } - } - public init(_ value: Bool) { self = .boolean(value) } @@ -160,7 +85,7 @@ public indirect enum CBOR: Equatable, Hashable { public init(_ value: Int64) { if value < 0 { - self = .negativeInt(~UInt64(bitPattern: Int64(value))) + self = .negativeInt(UInt64(bitPattern: ~value)) } else { self = .unsignedInt(UInt64(value)) @@ -197,7 +122,7 @@ public indirect enum CBOR: Equatable, Hashable { public subscript(position: CBOR) -> CBOR? { get { switch (self, position) { - case (let .array(array), .unsignedInt(let index)): return array[Int(index)] + case (let .array(array), .unsignedInt(let index)) where index < array.count: return array[Int(index)] case (let .map(map), let key): return map[key] default: return nil } @@ -215,59 +140,84 @@ public indirect enum CBOR: Equatable, Hashable { } } - /// Returns the CBOR value stripped of all `tagged` values. - /// - /// # Map/Array - /// If the value is a compex value, like `map` or `array` all values - /// contained in them are untagged recursively. - /// - public var untagged: CBOR { - guard case .tagged(_, let value) = self else { - return self - } - return value.untagged + public var booleanValue: Bool? { + guard case .boolean(let bool) = untagged else { return nil } + return bool } -} + public var bytesStringValue: Data? { + guard case .byteString(let data) = untagged else { return nil } + return data + } -extension CBOR: ExpressibleByNilLiteral, ExpressibleByIntegerLiteral, ExpressibleByStringLiteral, - ExpressibleByArrayLiteral, ExpressibleByDictionaryLiteral, ExpressibleByBooleanLiteral, - ExpressibleByFloatLiteral { + public var utf8StringValue: String? { + guard case .utf8String(let string) = untagged else { return nil } + return string + } - public init(nilLiteral: ()) { - self = .null + public var arrayValue: Array? { + guard case .array(let array) = untagged else { return nil } + return array } - public init(integerLiteral value: Int) { - self.init(value) + public var mapValue: Map? { + guard case .map(let map) = untagged else { return nil } + return map } - public init(extendedGraphemeClusterLiteral value: String) { - self = .utf8String(value) + public var halfValue: Half? { + guard case .half(let half) = untagged else { return nil } + return half } - public init(unicodeScalarLiteral value: String) { - self = .utf8String(value) + public var floatValue: Float? { + guard case .float(let float) = untagged else { return nil } + return float } - public init(stringLiteral value: String) { - self = .utf8String(value) + public var doubleValue: Double? { + guard case .double(let double) = untagged else { return nil } + return double } - public init(arrayLiteral elements: CBOR...) { - self = .array(elements) + public var simpleValue: UInt8? { + guard case .simple(let simple) = untagged else { return nil } + return simple } - public init(dictionaryLiteral elements: (CBOR, CBOR)...) { - self = .map(Map(uniqueKeysWithValues: elements)) + @available(*, deprecated, message: "Use floatingPointValue or integerValue instead") + public var numberValue: Double? { floatingPointValue() } + + public func integerValue() -> T? { + switch untagged { + case .unsignedInt(let uint): return T(uint) + case .negativeInt(let nint): return T(Int64(bitPattern: ~nint)) + default: return nil + } } - public init(booleanLiteral value: Bool) { - self = .boolean(value) + public func floatingPointValue() -> T? { + switch untagged { + case .unsignedInt(let uint): return T(uint) + case .negativeInt(let nint): return T(Int64(bitPattern: ~nint)) + case .half(let half): return T(half) + case .float(let float): return T(float) + case .double(let double): return T(double) + default: return nil + } } - public init(floatLiteral value: Double) { - self = .double(value) + /// Returns the CBOR value stripped of all `tagged` values. + /// + /// # Map/Array + /// If the value is a compex value, like `map` or `array` all values + /// contained in them are untagged recursively. + /// + public var untagged: CBOR { + guard case .tagged(_, let value) = self else { + return self + } + return value.untagged } } @@ -301,8 +251,25 @@ public extension CBOR.Tag { static let selfDescribeCBOR = CBOR.Tag(rawValue: 55799) } + +// MARK: Conformances + +extension CBOR: Equatable {} +extension CBOR: Hashable {} extension CBOR: Value { + public var isNull: Bool { + if case .null = self { return true } + return false + } + +} + + +// MARK: Wrapping + +extension CBOR { + public var unwrapped: Any? { switch self { case .null: return nil @@ -312,7 +279,7 @@ extension CBOR: Value { case .byteString(let value): return value case .simple(let value): return value case .unsignedInt(let value): return value - case .negativeInt(let value): return -1 - Int(value) + case .negativeInt(let value): return Int64(bitPattern: ~value) case .float(let value): return value case .half(let value): return value case .double(let value): return value @@ -327,8 +294,53 @@ extension CBOR: Value { } -/// Make encoders/decoders available in CBOR namespace -/// +// MARK: Literals + +extension CBOR: ExpressibleByNilLiteral, ExpressibleByIntegerLiteral, ExpressibleByStringLiteral, + ExpressibleByArrayLiteral, ExpressibleByDictionaryLiteral, ExpressibleByBooleanLiteral, + ExpressibleByFloatLiteral { + + public init(nilLiteral: ()) { + self = .null + } + + public init(integerLiteral value: Int) { + self.init(value) + } + + public init(extendedGraphemeClusterLiteral value: String) { + self = .utf8String(value) + } + + public init(unicodeScalarLiteral value: String) { + self = .utf8String(value) + } + + public init(stringLiteral value: String) { + self = .utf8String(value) + } + + public init(arrayLiteral elements: CBOR...) { + self = .array(elements) + } + + public init(dictionaryLiteral elements: (CBOR, CBOR)...) { + self = .map(Map(uniqueKeysWithValues: elements)) + } + + public init(booleanLiteral value: Bool) { + self = .boolean(value) + } + + public init(floatLiteral value: Double) { + self = .double(value) + } + +} + + +// Make encoders/decoders available in AnyValue namespace + public extension CBOR { typealias Encoder = CBOREncoder diff --git a/Sources/PotentCBOR/CBORDecoder.swift b/Sources/PotentCBOR/CBORDecoder.swift index 0495f1587..fa4d60182 100644 --- a/Sources/PotentCBOR/CBORDecoder.swift +++ b/Sources/PotentCBOR/CBORDecoder.swift @@ -8,7 +8,9 @@ // Distributed under the MIT License, See LICENSE for details. // +import BigInt import Foundation +import OrderedCollections import PotentCodables @@ -16,6 +18,8 @@ import PotentCodables /// public class CBORDecoder: ValueDecoder, DecodesFromData { + public static let `default` = CBORDecoder() + /// The strategy to use for decoding untagged `Date` values. /// /// - Important: The strategy is only used when untagged numeric values are decoded as @@ -54,7 +58,6 @@ public class CBORDecoder: ValueDecoder, DecodesFromD public struct CBORDecoderTransform: InternalDecoderTransform, InternalValueDeserializer { public typealias Value = CBOR - public typealias Decoder = InternalValueDecoder public typealias State = Void public static let nilValue = CBOR.null @@ -66,8 +69,51 @@ public struct CBORDecoderTransform: InternalDecoderTransform, InternalValueDeser public let userInfo: [CodingUserInfoKey: Any] } + public static func intercepts(_ type: Decodable.Type) -> Bool { + return type == CBOR.Half.self + || type == Date.self || type == NSDate.self + || type == Data.self || type == NSData.self + || type == URL.self || type == NSURL.self + || type == UUID.self || type == NSUUID.self + || type == Decimal.self || type == NSDecimalNumber.self + || type == BigInt.self + || type == BigUInt.self + || type == AnyValue.self + } + + public static func unbox(_ value: CBOR, interceptedType: Decodable.Type, decoder: IVD) throws -> Any? { + if interceptedType == CBOR.Half.self { + return try unbox(value, as: CBOR.Half.self, decoder: decoder) + } + else if interceptedType == Date.self || interceptedType == NSDate.self { + return try unbox(value, as: Date.self, decoder: decoder) + } + else if interceptedType == Data.self || interceptedType == NSData.self { + return try unbox(value, as: Data.self, decoder: decoder) + } + else if interceptedType == URL.self || interceptedType == NSURL.self { + return try unbox(value, as: URL.self, decoder: decoder) + } + else if interceptedType == UUID.self || interceptedType == NSUUID.self { + return try unbox(value, as: UUID.self, decoder: decoder) + } + else if interceptedType == Decimal.self || interceptedType == NSDecimalNumber.self { + return try unbox(value, as: Decimal.self, decoder: decoder) + } + else if interceptedType == BigInt.self { + return try unbox(value, as: BigInt.self, decoder: decoder) + } + else if interceptedType == BigUInt.self { + return try unbox(value, as: BigUInt.self, decoder: decoder) + } + else if interceptedType == AnyValue.self { + return try unbox(value, as: AnyValue.self, decoder: decoder) + } + fatalError("type not valid for intercept") + } + /// Returns the given value unboxed from a container. - public static func unbox(_ value: CBOR, as type: Bool.Type, decoder: Decoder) throws -> Bool? { + public static func unbox(_ value: CBOR, as type: Bool.Type, decoder: IVD) throws -> Bool? { switch value { case .boolean(let value): return value case .null: return nil @@ -88,284 +134,396 @@ public struct CBORDecoderTransform: InternalDecoderTransform, InternalValueDeser return result } - static func coerce(_ from: F, at codingPath: [CodingKey]) throws -> T where T: BinaryInteger, - F: BinaryFloatingPoint { - guard let result = T(exactly: round(from)) else { - throw overflow(T.self, value: from, at: codingPath) - } - return result - } - - static func coerce(_ from: F, at codingPath: [CodingKey]) throws -> T where T: BinaryFloatingPoint, - F: BinaryInteger { + static func coerce(_ from: F, at codingPath: [CodingKey]) throws -> T + where T: BinaryFloatingPoint, F: BinaryInteger { guard let result = T(exactly: from) else { throw overflow(T.self, value: from, at: codingPath) } return result } - static func coerce(_ from: F, at codingPath: [CodingKey]) throws -> T where T: BinaryFloatingPoint, - F: BinaryFloatingPoint { + static func coerce(_ from: F, at codingPath: [CodingKey]) throws -> T + where T: BinaryFloatingPoint, F: BinaryFloatingPoint { return T(from) } - public static func unbox(_ value: CBOR, as type: Int.Type, decoder: Decoder) throws -> Int? { - switch value.untagged { - case .double(let dbl): return try coerce(dbl, at: decoder.codingPath) - case .float(let flt): return try coerce(flt, at: decoder.codingPath) - case .unsignedInt(let uint): return try coerce(uint, at: decoder.codingPath) - case .negativeInt(let nint): return try -1 - coerce(nint, at: decoder.codingPath) + public static func unbox(_ value: CBOR, type: T.Type, decoder: IVD) throws -> T? { + switch value { + case .simple(let int): + return try coerce(int, at: decoder.codingPath) + case .unsignedInt(let uint): + return try coerce(uint, at: decoder.codingPath) + case .negativeInt(let nint): + return try coerce(Int64(bitPattern: ~nint), at: decoder.codingPath) + case .tagged(.positiveBignum, .byteString(let data)): + return try coerce(BigInt(sign: .plus, magnitude: BigUInt(data)), at: decoder.codingPath) + case .tagged(.negativeBignum, .byteString(let data)): + return try coerce(BigInt(sign: .minus, magnitude: BigUInt(data) + 1), at: decoder.codingPath) + case .tagged(_, let untagged): + return try unbox(untagged, type: type, decoder: decoder) case .null: return nil - case let cbor: - throw DecodingError.typeMismatch(at: decoder.codingPath, expectation: type, reality: cbor) + default: + throw DecodingError.typeMismatch(at: decoder.codingPath, expectation: type, reality: value) } } - public static func unbox(_ value: CBOR, as type: Int8.Type, decoder: Decoder) throws -> Int8? { - switch value.untagged { - case .double(let dbl): return try coerce(dbl, at: decoder.codingPath) - case .float(let flt): return try coerce(flt, at: decoder.codingPath) - case .unsignedInt(let uint): return try coerce(uint, at: decoder.codingPath) - case .negativeInt(let nint): return try -1 - coerce(nint, at: decoder.codingPath) + public static func unbox(_ value: CBOR, type: T.Type, decoder: IVD) throws -> T? + where T: BinaryFloatingPoint, T: LosslessStringConvertible { + switch value { + case .simple(let int): + return try coerce(int, at: decoder.codingPath) + case .unsignedInt(let uint): + return try coerce(uint, at: decoder.codingPath) + case .negativeInt(let nint): + return try coerce(Int64(bitPattern: ~nint), at: decoder.codingPath) + case .tagged(.positiveBignum, .byteString(let data)): + return try coerce(BigInt(sign: .plus, magnitude: BigUInt(data)), at: decoder.codingPath) + case .tagged(.negativeBignum, .byteString(let data)): + return try coerce(BigInt(sign: .minus, magnitude: BigUInt(data) + 1), at: decoder.codingPath) + case .half(let hlf): + return try coerce(hlf, at: decoder.codingPath) + case .float(let flt): + return try coerce(flt, at: decoder.codingPath) + case .double(let dbl): + return try coerce(dbl, at: decoder.codingPath) + case .tagged(.decimalFraction, .array(let items)) where items.count == 2: + guard + let exp = try? unbox(items[0], type: Int.self, decoder: decoder), + let man = try? unbox(items[1], type: BigInt.self, decoder: decoder) + else { + throw DecodingError.typeMismatch(at: decoder.codingPath, expectation: type, reality: value) + } + guard let sig = Decimal(string: man.magnitude.description) else { + throw overflow(type, value: value, at: decoder.codingPath) + } + let dec = Decimal(sign: man.sign == .plus ? .plus : .minus, exponent: exp, significand: sig) + guard let result = T(dec.description) else { + throw overflow(type, value: value, at: decoder.codingPath) + } + return result + case .tagged(_, let untagged): + return try unbox(untagged, type: type, decoder: decoder) case .null: return nil - case let cbor: - throw DecodingError.typeMismatch(at: decoder.codingPath, expectation: type, reality: cbor) + default: + throw DecodingError.typeMismatch(at: decoder.codingPath, expectation: type, reality: value) } } - public static func unbox(_ value: CBOR, as type: Int16.Type, decoder: Decoder) throws -> Int16? { - switch value.untagged { - case .double(let dbl): return try coerce(dbl, at: decoder.codingPath) - case .float(let flt): return try coerce(flt, at: decoder.codingPath) - case .unsignedInt(let uint): return try coerce(uint, at: decoder.codingPath) - case .negativeInt(let nint): return try -1 - coerce(nint, at: decoder.codingPath) + public static func unbox(_ value: CBOR, type: Decimal.Type, decoder: IVD) throws -> Decimal? { + switch value { + case .simple(let int): + return Decimal(int) + case .unsignedInt(let uint): + return Decimal(uint) + case .negativeInt(let nint): + return Decimal(Int64(bitPattern: ~nint)) + case .tagged(.positiveBignum, .byteString(let data)): + guard let result = Decimal(string: BigInt(sign: .plus, magnitude: BigUInt(data)).description) else { + throw overflow(Decimal.self, value: value, at: decoder.codingPath) + } + return result + case .tagged(.negativeBignum, .byteString(let data)): + guard let result = Decimal(string: BigInt(sign: .minus, magnitude: BigUInt(data) + 1).description) else { + throw overflow(Decimal.self, value: value, at: decoder.codingPath) + } + return result + case .half(let hlf): + return Decimal(Double(hlf)) + case .float(let flt): + return Decimal(Double(flt)) + case .double(let dbl): + return Decimal(dbl) + case .tagged(.decimalFraction, .array(let items)) where items.count == 2: + guard + let exp = try? unbox(items[0], type: Int.self, decoder: decoder), + let man = try? unbox(items[1], type: BigInt.self, decoder: decoder) + else { + throw DecodingError.typeMismatch(at: decoder.codingPath, expectation: type, reality: value) + } + // CHECK: Decimal(exactly:) is a fatal error for all inputs? + guard let sig = Decimal(string: man.magnitude.description) else { + throw overflow(Decimal.self, value: value, at: decoder.codingPath) + } + return Decimal(sign: man.sign == .plus ? .plus : .minus, exponent: exp, significand: sig) + case .tagged(_, let untagged): + return try unbox(untagged, type: type, decoder: decoder) case .null: return nil - case let cbor: - throw DecodingError.typeMismatch(at: decoder.codingPath, expectation: type, reality: cbor) + default: + throw DecodingError.typeMismatch(at: decoder.codingPath, expectation: type, reality: value) } } - public static func unbox(_ value: CBOR, as type: Int32.Type, decoder: Decoder) throws -> Int32? { - switch value.untagged { - case .double(let dbl): return try coerce(dbl, at: decoder.codingPath) - case .float(let flt): return try coerce(flt, at: decoder.codingPath) - case .unsignedInt(let uint): return try coerce(uint, at: decoder.codingPath) - case .negativeInt(let nint): return try -1 - coerce(nint, at: decoder.codingPath) - case .null: return nil - case let cbor: - throw DecodingError.typeMismatch(at: decoder.codingPath, expectation: type, reality: cbor) - } + public static func unbox(_ value: CBOR, as type: Int.Type, decoder: IVD) throws -> Int? { + return try unbox(value, type: type, decoder: decoder) } - public static func unbox(_ value: CBOR, as type: Int64.Type, decoder: Decoder) throws -> Int64? { - switch value.untagged { - case .double(let dbl): return try coerce(dbl, at: decoder.codingPath) - case .float(let flt): return try coerce(flt, at: decoder.codingPath) - case .unsignedInt(let uint): return try coerce(uint, at: decoder.codingPath) - case .negativeInt(let nint): return try -1 - coerce(nint, at: decoder.codingPath) - case .null: return nil - case let cbor: - throw DecodingError.typeMismatch(at: decoder.codingPath, expectation: type, reality: cbor) - } + public static func unbox(_ value: CBOR, as type: Int8.Type, decoder: IVD) throws -> Int8? { + return try unbox(value, type: type, decoder: decoder) } - public static func unbox(_ value: CBOR, as type: UInt.Type, decoder: Decoder) throws -> UInt? { - switch value.untagged { - case .double(let dbl): return try coerce(dbl, at: decoder.codingPath) - case .float(let flt): return try coerce(flt, at: decoder.codingPath) - case .unsignedInt(let uint): return try coerce(uint, at: decoder.codingPath) - case .null: return nil - case let cbor: - throw DecodingError.typeMismatch(at: decoder.codingPath, expectation: type, reality: cbor) - } + public static func unbox(_ value: CBOR, as type: Int16.Type, decoder: IVD) throws -> Int16? { + return try unbox(value, type: type, decoder: decoder) } - public static func unbox(_ value: CBOR, as type: UInt8.Type, decoder: Decoder) throws -> UInt8? { - switch value.untagged { - case .double(let dbl): return try coerce(dbl, at: decoder.codingPath) - case .float(let flt): return try coerce(flt, at: decoder.codingPath) - case .unsignedInt(let uint): return try coerce(uint, at: decoder.codingPath) - case .null: return nil - case let cbor: - throw DecodingError.typeMismatch(at: decoder.codingPath, expectation: type, reality: cbor) - } + public static func unbox(_ value: CBOR, as type: Int32.Type, decoder: IVD) throws -> Int32? { + return try unbox(value, type: type, decoder: decoder) } - public static func unbox(_ value: CBOR, as type: UInt16.Type, decoder: Decoder) throws -> UInt16? { - switch value.untagged { - case .double(let dbl): return try coerce(dbl, at: decoder.codingPath) - case .float(let flt): return try coerce(flt, at: decoder.codingPath) - case .unsignedInt(let uint): return try coerce(uint, at: decoder.codingPath) - case .null: return nil - case let cbor: - throw DecodingError.typeMismatch(at: decoder.codingPath, expectation: type, reality: cbor) - } + public static func unbox(_ value: CBOR, as type: Int64.Type, decoder: IVD) throws -> Int64? { + return try unbox(value, type: type, decoder: decoder) } - public static func unbox(_ value: CBOR, as type: UInt32.Type, decoder: Decoder) throws -> UInt32? { - switch value.untagged { - case .double(let dbl): return try coerce(dbl, at: decoder.codingPath) - case .float(let flt): return try coerce(flt, at: decoder.codingPath) - case .unsignedInt(let uint): return try coerce(uint, at: decoder.codingPath) - case .null: return nil - case let cbor: - throw DecodingError.typeMismatch(at: decoder.codingPath, expectation: type, reality: cbor) - } + public static func unbox(_ value: CBOR, as type: UInt.Type, decoder: IVD) throws -> UInt? { + return try unbox(value, type: type, decoder: decoder) } - public static func unbox(_ value: CBOR, as type: UInt64.Type, decoder: Decoder) throws -> UInt64? { - switch value.untagged { - case .double(let dbl): return try coerce(dbl, at: decoder.codingPath) - case .float(let flt): return try coerce(flt, at: decoder.codingPath) - case .unsignedInt(let uint): return uint - case .null: return nil - case let cbor: - throw DecodingError.typeMismatch(at: decoder.codingPath, expectation: type, reality: cbor) - } + public static func unbox(_ value: CBOR, as type: UInt8.Type, decoder: IVD) throws -> UInt8? { + return try unbox(value, type: type, decoder: decoder) } - public static func unbox(_ value: CBOR, as type: Float.Type, decoder: Decoder) throws -> Float? { - switch value.untagged { - case .double(let dbl): return try coerce(dbl, at: decoder.codingPath) - case .float(let flt): return flt - case .unsignedInt(let uint): return try coerce(uint, at: decoder.codingPath) - case .negativeInt(let nint): return try -1 - coerce(nint, at: decoder.codingPath) - case .null: return nil - case let cbor: - throw DecodingError.typeMismatch(at: decoder.codingPath, expectation: type, reality: cbor) - } + public static func unbox(_ value: CBOR, as type: UInt16.Type, decoder: IVD) throws -> UInt16? { + return try unbox(value, type: type, decoder: decoder) } - public static func unbox(_ value: CBOR, as type: Double.Type, decoder: Decoder) throws -> Double? { - switch value.untagged { - case .double(let dbl): return dbl - case .float(let flt): return try coerce(flt, at: decoder.codingPath) - case .unsignedInt(let uint): return try coerce(uint, at: decoder.codingPath) - case .negativeInt(let nint): return try -1 - coerce(nint, at: decoder.codingPath) - case .null: return nil - case let cbor: - throw DecodingError.typeMismatch(at: decoder.codingPath, expectation: type, reality: cbor) - } + public static func unbox(_ value: CBOR, as type: UInt32.Type, decoder: IVD) throws -> UInt32? { + return try unbox(value, type: type, decoder: decoder) + } + + public static func unbox(_ value: CBOR, as type: UInt64.Type, decoder: IVD) throws -> UInt64? { + return try unbox(value, type: type, decoder: decoder) + } + + public static func unbox(_ value: CBOR, as type: CBOR.Half.Type, decoder: IVD) throws -> CBOR.Half? { + return try unbox(value, type: type, decoder: decoder) + } + + public static func unbox(_ value: CBOR, as type: Float.Type, decoder: IVD) throws -> Float? { + return try unbox(value, type: type, decoder: decoder) + } + + public static func unbox(_ value: CBOR, as type: Double.Type, decoder: IVD) throws -> Double? { + return try unbox(value, type: type, decoder: decoder) + } + + public static func unbox(_ value: CBOR, as type: Decimal.Type, decoder: IVD) throws -> Decimal? { + return try unbox(value, type: type, decoder: decoder) } - public static func unbox(_ value: CBOR, as type: String.Type, decoder: Decoder) throws -> String? { - switch value.untagged { + public static func unbox(_ value: CBOR, as type: String.Type, decoder: IVD) throws -> String? { + switch value { + case .utf8String(let string), .tagged(_, .utf8String(let string)): return string case .null: return nil - case .utf8String(let string): - return string - case let cbor: - throw DecodingError.typeMismatch(at: decoder.codingPath, expectation: type, reality: cbor) + default: + throw DecodingError.typeMismatch(at: decoder.codingPath, expectation: type, reality: value) } } - public static func unbox(_ value: CBOR, as type: UUID.Type, decoder: Decoder) throws -> UUID? { + public static func unbox(_ value: CBOR, as type: UUID.Type, decoder: IVD) throws -> UUID? { switch value { - case .null: return nil - case .utf8String(let string): - return UUID(uuidString: string) - case .byteString(let data): - var uuid = UUID_NULL - withUnsafeMutableBytes(of: &uuid) { ptr in - _ = data.copyBytes(to: ptr) + case .utf8String(let string), .tagged(.uuid, .utf8String(let string)): + guard let result = UUID(uuidString: string) else { + throw DecodingError.typeMismatch(type, .init(codingPath: decoder.codingPath, + debugDescription: "Expected properly formatted UUID string")) } - return UUID(uuid: uuid) - case .tagged(.uuid, let tagged): - guard case .byteString(let data) = tagged else { - throw DecodingError.typeMismatch(at: decoder.codingPath, expectation: type, reality: tagged) + return result + case .byteString(let data), .tagged(.uuid, .byteString(let data)): + guard data.count == MemoryLayout.size else { + throw DecodingError.typeMismatch(at: decoder.codingPath, expectation: type, reality: value) } var uuid = UUID_NULL withUnsafeMutableBytes(of: &uuid) { ptr in _ = data.copyBytes(to: ptr) } return UUID(uuid: uuid) - case .tagged(_, let tagged): - return try unbox(tagged, as: UUID.self, decoder: decoder) - case let cbor: - throw DecodingError.typeMismatch(at: decoder.codingPath, expectation: type, reality: cbor) + case .tagged(_, let tagged): return try unbox(tagged, as: type, decoder: decoder) + case .null: return nil + default: + throw DecodingError.typeMismatch(at: decoder.codingPath, expectation: type, reality: value) } } - public static func unbox(_ value: CBOR, as type: Date.Type, decoder: Decoder) throws -> Date? { + public static func unbox(_ value: CBOR, as type: URL.Type, decoder: IVD) throws -> URL? { + switch value { + case .utf8String(let string), .tagged(.uri, .utf8String(let string)): + guard let url = URL(string: string) else { + throw DecodingError.dataCorruptedError(in: decoder, debugDescription: "Expected URL string") + } + return url + case .tagged(_, let tagged): return try unbox(tagged, as: type, decoder: decoder) + case .null: return nil + default: + throw DecodingError.typeMismatch(at: decoder.codingPath, expectation: type, reality: value) + } + } - func decodeUntaggedNumericDate(from value: Double, unitsPerSeconds: Double) -> Date { + public static func unbox(_ value: CBOR, as type: Date.Type, decoder: IVD) throws -> Date? { + + func decodeUntaggedNumericDate(from value: TimeInterval, unitsPerSeconds: Double) -> Date { switch decoder.options.untaggedDateDecodingStrategy { case .unitsSince1970: - return Date(timeIntervalSince1970: Double(value) * unitsPerSeconds) + return Date(timeIntervalSince1970: value / unitsPerSeconds) case .millisecondsSince1970: - return Date(timeIntervalSince1970: Double(value) / 1000.0) + return Date(timeIntervalSince1970: value / 1000.0) case .secondsSince1970: - return Date(timeIntervalSince1970: Double(value)) + return Date(timeIntervalSince1970: value) } } switch value { - case .null: return nil - case .utf8String(let string): - return _iso8601Formatter.date(from: string)?.utcDate - case .double(let double): - return decodeUntaggedNumericDate(from: double, unitsPerSeconds: 1.0) - case .float(let float): - return decodeUntaggedNumericDate(from: Double(float), unitsPerSeconds: 1.0) - case .half(let half): - return decodeUntaggedNumericDate(from: Double(half), unitsPerSeconds: 1.0) - case .unsignedInt(let uint): - return decodeUntaggedNumericDate(from: Double(uint), unitsPerSeconds: 1000.0) - case .negativeInt(let nint): - return decodeUntaggedNumericDate(from: Double(-1 - Int(nint)), unitsPerSeconds: 1000.0) - case .tagged(.iso8601DateTime, let tagged): - guard case .utf8String(let string) = tagged else { - throw DecodingError.typeMismatch(at: decoder.codingPath, expectation: type, reality: tagged) + case .utf8String(let string), .tagged(.iso8601DateTime, .utf8String(let string)): + guard let date = ZonedDate(iso8601Encoded: string) else { + throw DecodingError.dataCorrupted(.init(codingPath: decoder.codingPath, + debugDescription: "Expected date string to be ISO8601-formatted.")) } - guard let zonedDate = _iso8601Formatter.date(from: string) else { - throw DecodingError.dataCorruptedError(in: decoder, debugDescription: "Invalid ISO8601 Date/Time") + return date.utcDate + case .tagged(.epochDateTime, let number): + return try unbox(number, type: TimeInterval.self, decoder: decoder).map { Date(timeIntervalSince1970: $0) } + case .unsignedInt, .negativeInt, .simple, .tagged(.positiveBignum, _), .tagged(.negativeBignum, _): + return try unbox(value, type: Int64.self, decoder: decoder).map { + decodeUntaggedNumericDate(from: TimeInterval($0), unitsPerSeconds: 1000.0) } - return zonedDate.utcDate - case .tagged(.epochDateTime, let tagged): - guard tagged.isNumber else { - throw DecodingError.typeMismatch(at: decoder.codingPath, expectation: type, reality: tagged) + case .double, .float, .half, .tagged(.decimalFraction, _): + return try unbox(value, type: TimeInterval.self, decoder: decoder).map { + decodeUntaggedNumericDate(from: $0, unitsPerSeconds: 1.0) } - guard let secondsDec = tagged.numberValue, let seconds = Double(secondsDec.description) else { - throw DecodingError.dataCorruptedError(in: decoder, debugDescription: "Invalid Numeric Date/Time") + case .tagged(_, let tagged): + return try unbox(tagged, as: type, decoder: decoder) + case .null: return nil + default: + throw DecodingError.typeMismatch(at: decoder.codingPath, expectation: type, reality: value) + } + } + + public static func unbox(_ value: CBOR, as type: Data.Type, decoder: IVD) throws -> Data? { + switch value { + case .byteString(let data), .tagged(_, .byteString(let data)): return data + case .tagged(.base64, .utf8String(let string)): + guard let data = Data(base64Encoded: string) else { + throw DecodingError.dataCorruptedError(in: decoder, debugDescription: "Expected Base64 encoded string") } - return Date(timeIntervalSince1970: seconds) - case let cbor: - throw DecodingError.typeMismatch(at: decoder.codingPath, expectation: type, reality: cbor) + return data + case .tagged(.base64Url, .utf8String(let string)): + guard let data = Data(base64UrlEncoded: string) else { + throw DecodingError.dataCorruptedError(in: decoder, debugDescription: "Expected URL Safe Base64 encoded string") + } + return data + case .null: return nil + default: + throw DecodingError.typeMismatch(at: decoder.codingPath, expectation: type, reality: value) } } - public static func unbox(_ value: CBOR, as type: Data.Type, decoder: Decoder) throws -> Data? { - switch value.untagged { + public static func unbox(_ value: CBOR, as type: BigInt.Type, decoder: IVD) throws -> BigInt? { + switch value { + case .simple(let value): return BigInt(exactly: value) + case .unsignedInt(let value): return BigInt(exactly: value) + case .negativeInt(let value): return BigInt(exactly: Int64(bitPattern: ~value)) + case .tagged(.positiveBignum, .byteString(let data)): + return try coerce(BigInt(sign: .plus, magnitude: BigUInt(data)), at: decoder.codingPath) + case .tagged(.negativeBignum, .byteString(let data)): + return try coerce(BigInt(sign: .minus, magnitude: BigUInt(data) + 1), at: decoder.codingPath) + case .tagged(_, let tagged): return try unbox(tagged, as: type, decoder: decoder) case .null: return nil - case .byteString(let data): return data - case let cbor: - throw DecodingError.typeMismatch(at: decoder.codingPath, expectation: type, reality: cbor) + default: + throw DecodingError.typeMismatch(at: decoder.codingPath, expectation: type, reality: value) } } - public static func unbox(_ value: CBOR, as type: Decimal.Type, decoder: Decoder) throws -> Decimal? { - guard !value.isNull else { return nil } - let doubleValue = try unbox(value, as: Double.self, decoder: decoder)! - return Decimal(doubleValue) + public static func unbox(_ value: CBOR, as type: BigUInt.Type, decoder: IVD) throws -> BigUInt? { + switch value { + case .simple(let value): return BigUInt(exactly: value) + case .unsignedInt(let value): return BigUInt(exactly: value) + case .tagged(.positiveBignum, .byteString(let data)): + return try coerce(BigInt(sign: .plus, magnitude: BigUInt(data)), at: decoder.codingPath) + case .tagged(_, let untagged): return try unbox(untagged, as: type, decoder: decoder) + case .null: return nil + default: + throw DecodingError.typeMismatch(at: decoder.codingPath, expectation: type, reality: value) + } } - public static func valueToUnkeyedValues(_ value: CBOR, decoder: Decoder) throws -> [CBOR]? { + public static func unbox(_ value: CBOR, as type: AnyValue.Type, decoder: IVD) throws -> AnyValue { + switch value { + case .null, .undefined: + return .nil + case .boolean(let value): + return .bool(value) + case .utf8String(let value): + return .string(value) + case .byteString(let value): + return .data(value) + case .simple(let value): + return .uint8(value) + case .unsignedInt(let value): + if value <= Int64.max { + return .int64(Int64(value)) + } + else { + return .uint64(value) + } + case .negativeInt(let value): + return .int64(Int64(bitPattern: ~value)) + case .float(let value): + return .float(value) + case .half(let value): + return .float16(value) + case .double(let value): + return .double(value) + case .array(let value): + return .array(try value.map { try unbox($0, as: AnyValue.self, decoder: decoder) }) + case .map(let value): + return .dictionary(.init(uniqueKeysWithValues: try value.map { key, value in + let key = try unbox(key, as: AnyValue.self, decoder: decoder) + let value = try unbox(value, as: AnyValue.self, decoder: decoder) + return (key, value) + })) + case .tagged(.positiveBignum, _), .tagged(.negativeBignum, _): + guard let int = try unbox(value, as: BigInt.self, decoder: decoder) else { + throw DecodingError.typeMismatch(at: decoder.codingPath, expectation: BigInt.self, reality: value) + } + return .integer(int) + case .tagged(.decimalFraction, _): + guard let decimal = try unbox(value, as: Decimal.self, decoder: decoder) else { + throw DecodingError.typeMismatch(at: decoder.codingPath, expectation: Decimal.self, reality: value) + } + return .decimal(decimal) + case .tagged(.iso8601DateTime, _), .tagged(.epochDateTime, _): + guard let date = try unbox(value, as: Date.self, decoder: decoder) else { + throw DecodingError.typeMismatch(at: decoder.codingPath, expectation: Date.self, reality: value) + } + return .date(date) + case .tagged(.uuid, _): + return try unbox(value, as: UUID.self, decoder: decoder).map { .uuid($0) } ?? .nil + case .tagged(.uri, _): + return try unbox(value, as: URL.self, decoder: decoder).map { .url($0) } ?? .nil + case .tagged(.base64, _), .tagged(.base64Url, _): + return try unbox(value, as: Data.self, decoder: decoder).map { .data($0) } ?? .nil + case .tagged(_, let untagged): + return try unbox(untagged, as: AnyValue.self, decoder: decoder) + } + } + + public static func valueToUnkeyedValues(_ value: CBOR, decoder: IVD) throws -> UnkeyedValues? { guard case .array(let array) = value else { return nil } return array } - public static func valueToKeyedValues(_ value: CBOR, decoder: Decoder) throws -> [String: CBOR]? { + public static func valueToKeyedValues(_ value: CBOR, decoder: IVD) throws -> KeyedValues? { guard case .map(let map) = value else { return nil } - return try mapToKeyedValues(map, decoder: decoder) - } - - public static func mapToKeyedValues(_ map: CBOR.Map, decoder: Decoder) throws -> [String: CBOR] { - return try Dictionary( - map.compactMap { key, value in - switch key.untagged { - case .utf8String(let str): return (str, value) - case .unsignedInt(let uint): return (String(uint), value) - case .negativeInt(let nint): return (String(-1 - Int(nint)), value) - default: return nil + return try KeyedValues( + map.map { key, value in + switch key { + case .utf8String(let stringKey), .tagged(_, .utf8String(let stringKey)): + return (stringKey, value) + case .unsignedInt(let intKey), .tagged(_, .unsignedInt(let intKey)): + return (String(intKey), value) + case .negativeInt(let nintKey), .tagged(_, .negativeInt(let nintKey)): + return (String(Int64(bitPattern: ~nintKey)), value) + default: + throw DecodingError.dataCorrupted(.init( + codingPath: decoder.codingPath, + debugDescription: "Map contains unsupported keys" + )) } }, uniquingKeysWith: { _, _ in @@ -383,8 +541,13 @@ public struct CBORDecoderTransform: InternalDecoderTransform, InternalValueDeser } +extension Data { + + init?(base64UrlEncoded string: String) { + self.init(base64Encoded: string.replacingOccurrences(of: "-", with: "+").replacingOccurrences(of: "_", with: "/")) + } -private let _iso8601Formatter = SuffixedDateFormatter.optionalFractionalSeconds(basePattern: "yyyy-MM-dd'T'HH:mm:ss") +} #if canImport(Combine) diff --git a/Sources/PotentCBOR/CBOREncoder.swift b/Sources/PotentCBOR/CBOREncoder.swift index 4276c9a26..6d9035011 100644 --- a/Sources/PotentCBOR/CBOREncoder.swift +++ b/Sources/PotentCBOR/CBOREncoder.swift @@ -8,6 +8,7 @@ // Distributed under the MIT License, See LICENSE for details. // +import BigInt import Foundation import PotentCodables @@ -15,6 +16,9 @@ import PotentCodables /// `CBOREncoder` facilitates the encoding of `Encodable` values into CBOR values. /// public class CBOREncoder: ValueEncoder, EncodesToData { + + public static let `default` = CBOREncoder() + // MARK: Options /// The strategy to use for encoding `Date` values. @@ -52,7 +56,6 @@ public class CBOREncoder: ValueEncoder, EncodesToDat public struct CBOREncoderTransform: InternalEncoderTransform, InternalValueSerializer { public typealias Value = CBOR - public typealias Encoder = InternalValueEncoder public typealias State = Void public static var emptyKeyedContainer = CBOR.map([:]) @@ -64,51 +67,174 @@ public struct CBOREncoderTransform: InternalEncoderTransform, InternalValueSeria public let userInfo: [CodingUserInfoKey: Any] } - public static func boxNil(encoder: Encoder) throws -> CBOR { return nil } - public static func box(_ value: Bool, encoder: Encoder) throws -> CBOR { return CBOR(value) } - public static func box(_ value: Int, encoder: Encoder) throws -> CBOR { return CBOR(Int64(value)) } - public static func box(_ value: Int8, encoder: Encoder) throws -> CBOR { return CBOR(Int64(value)) } - public static func box(_ value: Int16, encoder: Encoder) throws -> CBOR { return CBOR(Int64(value)) } - public static func box(_ value: Int32, encoder: Encoder) throws -> CBOR { return CBOR(Int64(value)) } - public static func box(_ value: Int64, encoder: Encoder) throws -> CBOR { return CBOR(Int64(value)) } - public static func box(_ value: UInt, encoder: Encoder) throws -> CBOR { return CBOR(UInt64(value)) } - public static func box(_ value: UInt8, encoder: Encoder) throws -> CBOR { return CBOR(UInt64(value)) } - public static func box(_ value: UInt16, encoder: Encoder) throws -> CBOR { return CBOR(UInt64(value)) } - public static func box(_ value: UInt32, encoder: Encoder) throws -> CBOR { return CBOR(UInt64(value)) } - public static func box(_ value: UInt64, encoder: Encoder) throws -> CBOR { return CBOR(UInt64(value)) } - public static func box(_ value: String, encoder: Encoder) throws -> CBOR { return CBOR(value) } - public static func box(_ value: Float, encoder: Encoder) throws -> CBOR { return CBOR(value) } - public static func box(_ value: Double, encoder: Encoder) throws -> CBOR { return CBOR(value) } - public static func box( - _ value: Decimal, - encoder: Encoder - ) throws -> CBOR { return CBOR((value as NSDecimalNumber).doubleValue) } - public static func box(_ value: Data, encoder: Encoder) throws -> CBOR { return CBOR(value) } - public static func box( - _ value: URL, - encoder: Encoder - ) throws -> CBOR { return .tagged(.uri, .utf8String(value.absoluteString)) } - - public static func box(_ value: UUID, encoder: Encoder) throws -> CBOR { + public static func intercepts(_ type: Encodable.Type) -> Bool { + return type == CBOR.Half.self + || type == Date.self || type == NSDate.self + || type == Data.self || type == NSData.self + || type == URL.self || type == NSURL.self + || type == UUID.self || type == NSUUID.self + || type == Decimal.self || type == NSDecimalNumber.self + || type == BigInt.self + || type == BigUInt.self + || type == AnyValue.self + } + + public static func box(_ value: Any, interceptedType: Encodable.Type, encoder: IVE) throws -> CBOR { + if let value = value as? CBOR.Half { + return try box(value, encoder: encoder) + } + if let value = value as? Date { + return try box(value, encoder: encoder) + } + else if let value = value as? Data { + return try box(value, encoder: encoder) + } + else if let value = value as? URL { + return try box(value, encoder: encoder) + } + else if let value = value as? UUID { + return try box(value, encoder: encoder) + } + else if let value = value as? Decimal { + return try box(value, encoder: encoder) + } + else if let value = value as? BigInt { + return try box(value, encoder: encoder) + } + else if let value = value as? BigUInt { + return try box(value, encoder: encoder) + } + else if let value = value as? AnyValue { + return try box(value, encoder: encoder) + } + fatalError("type not valid for intercept") + } + + public static func boxNil(encoder: IVE) throws -> CBOR { return nil } + public static func box(_ value: Bool, encoder: IVE) throws -> CBOR { return CBOR(value) } + public static func box(_ value: Int, encoder: IVE) throws -> CBOR { return CBOR(Int64(value)) } + public static func box(_ value: Int8, encoder: IVE) throws -> CBOR { return CBOR(Int64(value)) } + public static func box(_ value: Int16, encoder: IVE) throws -> CBOR { return CBOR(Int64(value)) } + public static func box(_ value: Int32, encoder: IVE) throws -> CBOR { return CBOR(Int64(value)) } + public static func box(_ value: Int64, encoder: IVE) throws -> CBOR { return CBOR(Int64(value)) } + public static func box(_ value: UInt, encoder: IVE) throws -> CBOR { return CBOR(UInt64(value)) } + public static func box(_ value: UInt8, encoder: IVE) throws -> CBOR { return CBOR(UInt64(value)) } + public static func box(_ value: UInt16, encoder: IVE) throws -> CBOR { return CBOR(UInt64(value)) } + public static func box(_ value: UInt32, encoder: IVE) throws -> CBOR { return CBOR(UInt64(value)) } + public static func box(_ value: UInt64, encoder: IVE) throws -> CBOR { return CBOR(UInt64(value)) } + public static func box(_ value: Float, encoder: IVE) throws -> CBOR { return .float(value) } + public static func box(_ value: Double, encoder: IVE) throws -> CBOR { return .double(value) } + public static func box(_ value: String, encoder: IVE) throws -> CBOR { return .utf8String(value) } + + public static func box(_ value: CBOR.Half, encoder: IVE) throws -> CBOR { return .half(value) } + public static func box(_ value: Data, encoder: IVE) throws -> CBOR { return .byteString(value) } + + public static func box(_ value: BigInt, encoder: IVE) throws -> CBOR { + if value.sign == .plus { + return .tagged(.positiveBignum, .byteString(value.magnitude.serialize())) + } + else { + return .tagged(.negativeBignum, .byteString((value.magnitude - 1).serialize())) + } + } + + public static func box(_ value: BigUInt, encoder: IVE) throws -> CBOR { + return .tagged(.positiveBignum, .byteString(value.magnitude.serialize())) + } + + public static func box(_ value: Decimal, encoder: IVE) throws -> CBOR { + let exp: CBOR = value.exponent < 0 + ? .negativeInt(UInt64(bitPattern: ~Int64(value.exponent))) + : .unsignedInt(UInt64(value.exponent)) + let sig = BigInt(value.significand.description)! + let man: CBOR = value.sign == .plus + ? .tagged(.positiveBignum, .byteString(sig.magnitude.serialize())) + : .tagged(.negativeBignum, .byteString((sig.magnitude - 1).serialize())) + return .tagged(.decimalFraction, .array([exp, man])) + } + + public static func box(_ value: URL, encoder: IVE) throws -> CBOR { + return .tagged(.uri, .utf8String(value.absoluteString)) + } + + public static func box(_ value: UUID, encoder: IVE) throws -> CBOR { return withUnsafeBytes(of: value) { ptr in let bytes = Data(ptr.bindMemory(to: UInt8.self)) return .tagged(.uuid, .byteString(bytes)) } } - public static func box(_ value: Date, encoder: Encoder) throws -> CBOR { + public static func box(_ value: Date, encoder: IVE) throws -> CBOR { switch encoder.options.dateEncodingStrategy { - case .iso8601: return .tagged(.iso8601DateTime, .utf8String(_iso8601Formatter.string(from: value))) - case .secondsSince1970: return .tagged(.epochDateTime, CBOR(value.timeIntervalSince1970)) - case .millisecondsSince1970: return .tagged(.epochDateTime, CBOR(Int64(value.timeIntervalSince1970 * 1000.0))) + case .iso8601: + return .tagged(.iso8601DateTime, .utf8String(ZonedDate(date: value, timeZone: .utc).iso8601EncodedString())) + case .secondsSince1970: + return .tagged(.epochDateTime, CBOR(value.timeIntervalSince1970)) + case .millisecondsSince1970: + return .tagged(.epochDateTime, CBOR(Int64(value.timeIntervalSince1970 * 1000.0))) + } + } + + public static func box(_ value: AnyValue, encoder: IVE) throws -> CBOR { + switch value { + case .nil: + return .null + case .bool(let value): + return .boolean(value) + case .string(let value): + return .utf8String(value) + case .int8(let value): + return try box(value, encoder: encoder) + case .int16(let value): + return try box(value, encoder: encoder) + case .int32(let value): + return try box(value, encoder: encoder) + case .int64(let value): + return try box(value, encoder: encoder) + case .uint8(let value): + return try box(value, encoder: encoder) + case .uint16(let value): + return try box(value, encoder: encoder) + case .uint32(let value): + return try box(value, encoder: encoder) + case .uint64(let value): + return try box(value, encoder: encoder) + case .integer(let value): + return try box(value, encoder: encoder) + case .unsignedInteger(let value): + return try box(value, encoder: encoder) + case .float16(let value): + return try box(value, encoder: encoder) + case .float(let value): + return try box(value, encoder: encoder) + case .double(let value): + return try box(value, encoder: encoder) + case .decimal(let value): + return try box(value, encoder: encoder) + case .data(let value): + return try box(value, encoder: encoder) + case .url(let value): + return try box(value, encoder: encoder) + case .uuid(let value): + return try box(value, encoder: encoder) + case .date(let value): + return try box(value, encoder: encoder) + case .array(let value): + return .array(.init(try value.map { try box($0, encoder: encoder) })) + case .dictionary(let value): + return .map(.init(uniqueKeysWithValues: try value.map { key, value in + let key = try box(key, encoder: encoder) + let value = try box(value, encoder: encoder) + return (key, value) + })) } } - public static func unkeyedValuesToValue(_ values: [CBOR], encoder: Encoder) -> CBOR { - return .array(values) + public static func unkeyedValuesToValue(_ values: UnkeyedValues, encoder: IVE) -> CBOR { + return .array(CBOR.Array(values)) } - public static func keyedValuesToValue(_ values: [String: CBOR], encoder: Encoder) -> CBOR { + public static func keyedValuesToValue(_ values: KeyedValues, encoder: IVE) -> CBOR { return .map(CBOR.Map(uniqueKeysWithValues: values.map { key, value in (CBOR(key), value) })) } @@ -119,16 +245,6 @@ public struct CBOREncoderTransform: InternalEncoderTransform, InternalValueSeria } -private let _iso8601Formatter: DateFormatter = { - let formatter = DateFormatter() - formatter.calendar = Calendar(identifier: .iso8601) - formatter.locale = Locale(identifier: "en_US_POSIX") - formatter.timeZone = TimeZone(secondsFromGMT: 0) - formatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSSXXXXX" - return formatter -}() - - #if canImport(Combine) import Combine diff --git a/Sources/PotentCBOR/CBORReader.swift b/Sources/PotentCBOR/CBORReader.swift index 42a8038e6..66cc4c5db 100644 --- a/Sources/PotentCBOR/CBORReader.swift +++ b/Sources/PotentCBOR/CBORReader.swift @@ -21,22 +21,22 @@ public struct CBORReader { self.stream = stream } - private func readBinaryNumber(_ type: CBOR.Half.Type) throws -> CBOR.Half { + private func readHalf() throws -> CBOR.Half { return CBOR.Half(bitPattern: try stream.readInt(UInt16.self)) } - private func readBinaryNumber(_ type: CBOR.Float.Type) throws -> CBOR.Float { + private func readFloat() throws -> CBOR.Float { return CBOR.Float(bitPattern: try stream.readInt(UInt32.self)) } - private func readBinaryNumber(_ type: CBOR.Double.Type) throws -> CBOR.Double { + private func readDouble() throws -> CBOR.Double { return CBOR.Double(bitPattern: try stream.readInt(UInt64.self)) } private func readVarUInt(_ initByte: UInt8, base: UInt8) throws -> UInt64 { guard initByte > base + 0x17 else { return UInt64(initByte - base) } - switch VarUIntSize(rawValue: initByte) { + switch try VarUIntSize.from(serialized: initByte) { case .uint8: return UInt64(try stream.readInt(UInt8.self)) case .uint16: return UInt64(try stream.readInt(UInt16.self)) case .uint32: return UInt64(try stream.readInt(UInt32.self)) @@ -208,11 +208,11 @@ public struct CBORReader { case 0xF8: return .simple(try stream.readByte()) case 0xF9: - return .half(try readBinaryNumber(CBOR.Half.self)) + return .half(try readHalf()) case 0xFA: - return .float(try readBinaryNumber(CBOR.Float.self)) + return .float(try readFloat()) case 0xFB: - return .double(try readBinaryNumber(CBOR.Double.self)) + return .double(try readDouble()) case 0xFF: return nil default: throw CBORError.invalidItemType @@ -227,13 +227,15 @@ private enum VarUIntSize: UInt8 { case uint32 = 2 case uint64 = 3 - init(rawValue: UInt8) { - switch rawValue & 0b11 { - case 0: self = .uint8 - case 1: self = .uint16 - case 2: self = .uint32 - case 3: self = .uint64 - default: fatalError() // mask only allows values from 0-3 + static func from(serialized value: UInt8) throws -> VarUIntSize { + switch value & 0b11 { + case 0: return .uint8 + case 1: return .uint16 + case 2: return .uint32 + case 3: return .uint64 + default: + // mask only allows values from 0-3 + throw CBORError.invalidIntegerSize } } } diff --git a/Sources/PotentCBOR/CBORSerialization.swift b/Sources/PotentCBOR/CBORSerialization.swift index cfec4a36f..a74d690bd 100644 --- a/Sources/PotentCBOR/CBORSerialization.swift +++ b/Sources/PotentCBOR/CBORSerialization.swift @@ -16,7 +16,7 @@ import Foundation /// The API is simple and should fit most user's needs, if /// required users can drop down and use `CBORWriter`/ /// `CBORReader` directly. -public struct CBORSerialization { +public enum CBORSerialization { /// Errors throws during serialization and deserialization /// @@ -40,6 +40,8 @@ public struct CBORSerialization { case sequenceTooLong /// An invalid UTF-8 `string` sequence was encountered during deserialization case invalidUTF8String + /// Invalid integer size indicator + case invalidIntegerSize } /// Serialize `CBOR` value into a byte data. @@ -77,6 +79,4 @@ public struct CBORSerialization { return try CBORReader(stream: stream).decodeRequiredItem() } - private init() {} - } diff --git a/Sources/PotentCBOR/CBORWriter.swift b/Sources/PotentCBOR/CBORWriter.swift index e9f7e76c0..a467cf292 100644 --- a/Sources/PotentCBOR/CBORWriter.swift +++ b/Sources/PotentCBOR/CBORWriter.swift @@ -35,7 +35,7 @@ public struct CBORWriter { case .null: try encodeNull() case .undefined: try encodeUndefined() case .unsignedInt(let uint): try encodeVarUInt(uint) - case .negativeInt(let nint): try encodeNegativeInt(~Int64(bitPattern: nint)) + case .negativeInt(let nint): try encodeNegativeInt(Int64(bitPattern: ~nint)) case .byteString(let str): try encodeByteString(str) case .utf8String(let str): try encodeString(str) case .array(let array): try encodeArray(array) @@ -263,7 +263,7 @@ public struct CBORWriter { /// - Throws: /// - `Swift.Error`: If any I/O error occurs public func encodeHalf(_ val: CBOR.Half) throws { - try stream.writeByte(0xFA) + try stream.writeByte(0xF9) try stream.writeInt(val.bitPattern) } diff --git a/Sources/PotentCodables/AnyCodingKey.swift b/Sources/PotentCodables/AnyCodingKey.swift index be3548d5f..0c566ffcc 100644 --- a/Sources/PotentCodables/AnyCodingKey.swift +++ b/Sources/PotentCodables/AnyCodingKey.swift @@ -12,24 +12,21 @@ public struct AnyCodingKey: CodingKey, Equatable, Hashable { public var stringValue: String public var intValue: Int? - public init(stringValue: String) { + public init(stringValue: String, intValue: Int?) { self.stringValue = stringValue - intValue = nil + self.intValue = intValue } - public init(intValue: Int) { - stringValue = "\(intValue)" - self.intValue = intValue + public init(stringValue: String) { + self.init(stringValue: stringValue, intValue: nil) } - public init(stringValue: String, intValue: Int?) { - self.stringValue = stringValue - self.intValue = intValue + public init(intValue: Int) { + self.init(stringValue: "\(intValue)", intValue: intValue) } public init(index: Int) { - stringValue = "\(index)" - intValue = index + self.init(intValue: index) } public init(_ base: Key) { @@ -55,32 +52,6 @@ public struct AnyCodingKey: CodingKey, Equatable, Hashable { } -extension AnyCodingKey: Encodable { - public func encode(to encoder: Encoder) throws { - var container = encoder.singleValueContainer() - if let intValue = self.intValue { - try container.encode(intValue) - } - else { - try container.encode(stringValue) - } - } -} - -extension AnyCodingKey: Decodable { - public init(from decoder: Decoder) throws { - let value = try decoder.singleValueContainer() - if let intValue = try? value.decode(Int.self) { - stringValue = "\(intValue)" - self.intValue = intValue - } - else { - stringValue = try value.decode(String.self) - intValue = nil - } - } -} - extension AnyCodingKey: ExpressibleByStringLiteral, ExpressibleByIntegerLiteral { public init(stringLiteral value: String) { stringValue = value diff --git a/Sources/PotentCodables/AnyValue/AnyValue.swift b/Sources/PotentCodables/AnyValue/AnyValue.swift index a6ef8303e..65eb3e82c 100644 --- a/Sources/PotentCodables/AnyValue/AnyValue.swift +++ b/Sources/PotentCodables/AnyValue/AnyValue.swift @@ -10,6 +10,7 @@ import Foundation import BigInt +import OrderedCollections /// A `Codable` value that allows encoding/decoding values of any type or structure. @@ -29,13 +30,16 @@ import BigInt /// anyArray[0] /// @dynamicMemberLookup -public enum AnyValue: Hashable { +public enum AnyValue { public enum Error: Swift.Error { case unsupportedType case unsupportedValue(Any) } + public typealias AnyArray = [AnyValue] + public typealias AnyDictionary = OrderedDictionary + case `nil` case bool(Bool) case string(String) @@ -49,6 +53,7 @@ public enum AnyValue: Hashable { case uint64(UInt64) case integer(BigInt) case unsignedInteger(BigUInt) + case float16(Float16) case float(Float) case double(Double) case decimal(Decimal) @@ -56,64 +61,34 @@ public enum AnyValue: Hashable { case url(URL) case uuid(UUID) case date(Date) - case array([AnyValue]) - case dictionary([String: AnyValue]) + case array(AnyArray) + case dictionary(AnyDictionary) - /// Wraps the value into an equivalent `AnyValue` tree - public static func wrapped(_ value: Any?) throws -> AnyValue { - guard let value = value else { return .nil } - switch value { - case let val as String: return .string(val) - case let val as Bool: return .bool(val) - case let val as NSNumber: - /// Use NSNumber's type identifier to determine exact numeric type because - /// Swift's `as`, `is` and `type(of:)` all coerce numeric types into the - /// requested type if they are compatible - switch UnicodeScalar(Int(val.objCType.pointee)) { - case "c": return .int8(val.int8Value) - case "C": return .uint8(val.uint8Value) - case "s": return .int16(val.int16Value) - case "S": return .uint16(val.uint16Value) - case "i": return .int32(val.int32Value) - case "I": return .uint32(val.uint32Value) - case "l": return MemoryLayout.size == 8 ? .int64(Int64(val.intValue)) : .int32(Int32(val.intValue)) - case "L": return MemoryLayout.size == 8 ? .uint64(UInt64(val.uintValue)) : .uint32(UInt32(val.intValue)) - case "q": return .int64(val.int64Value) - case "Q": return .uint64(val.uint64Value) - case "f": return .float(val.floatValue) - case "d": return .double(val.doubleValue) - default: fatalError("Invalid NSNumber type identifier") - } - case let val as BigInt: return .integer(val) - case let val as BigUInt: return .unsignedInteger(val) - case let val as Decimal: return .decimal(val) - case let val as Data: return .data(val) - case let val as URL: return .url(val) - case let val as UUID: return .uuid(val) - case let val as Date: return .date(val) - case let val as [Any]: return .array(try val.map { try wrapped($0) }) - case let val as [String: Any]: return .dictionary(try val.mapValues { try wrapped($0) }) - default: throw Error.unsupportedValue(value) - } + public static func int(_ value: Int) -> AnyValue { + return MemoryLayout.size == 8 ? .int64(Int64(value)) : .int32(Int32(value)) } - public subscript(dynamicMember member: String) -> Any? { + public static func uint(_ value: UInt) -> AnyValue { + return MemoryLayout.size == 8 ? .uint64(UInt64(value)) : .uint32(UInt32(value)) + } + + public subscript(dynamicMember member: String) -> AnyValue? { if case .dictionary(let dict) = self { - return dict[member] + return dict[.string(member)] } return nil } - public subscript(member: String) -> Any? { + public subscript(member: AnyValue) -> AnyValue? { if case .dictionary(let dict) = self { return dict[member] } return nil } - public subscript(position: Int) -> Any? { - if case .array(let array) = self { - return array[position] + public subscript(index: Int) -> AnyValue? { + if case .array(let array) = self, index < array.count { + return array[index] } return nil } @@ -128,6 +103,16 @@ public enum AnyValue: Hashable { return value } + public var urlValue: URL? { + guard case .url(let value) = self else { return nil } + return value + } + + public var uuidValue: UUID? { + guard case .uuid(let value) = self else { return nil } + return value + } + public var dataValue: Data? { guard case .data(let value) = self else { return nil } return value @@ -138,80 +123,170 @@ public enum AnyValue: Hashable { return value } - public var arrayValue: [AnyValue]? { + public var arrayValue: AnyArray? { guard case .array(let value) = self else { return nil } return value } - public var dictionaryValue: [String: AnyValue]? { + public var dictionaryValue: AnyDictionary? { guard case .dictionary(let value) = self else { return nil } return value } - public func integerValue(as type: I.Type) -> I? { - switch self { - case .int8(let value): return I(value) - case .int16(let value): return I(value) - case .int32(let value): return I(value) - case .int64(let value): return I(value) - case .uint8(let value): return I(value) - case .uint16(let value): return I(value) - case .uint32(let value): return I(value) - case .uint64(let value): return I(value) - case .float(let value): return I(value) - case .double(let value): return I(value) - case .decimal(let value): return I(value.description) - default: + public var intValue: Int? { + if MemoryLayout.size == 8 { + guard case .int64(let value) = self else { + return nil + } + return Int(value) + } + else { + guard case .int32(let value) = self else { + return nil + } + return Int(value) + } + } + + public var uintValue: UInt? { + if MemoryLayout.size == 8 { + guard case .uint64(let value) = self else { + return nil + } + return UInt(value) + } + else { + guard case .uint32(let value) = self else { + return nil + } + return UInt(value) + } + } + + public var int8Value: Int8? { + guard case .int8(let int) = self else { return nil } + return int } -} + public var uint8Value: UInt8? { + guard case .uint8(let uint) = self else { + return nil + } + return uint + } -// MARK: ExpressibleBy<>Literal support + public var int16Value: Int16? { + guard case .int16(let int) = self else { + return nil + } + return int + } -extension AnyValue: ExpressibleByNilLiteral, ExpressibleByBooleanLiteral, ExpressibleByStringLiteral, - ExpressibleByIntegerLiteral, ExpressibleByFloatLiteral, ExpressibleByArrayLiteral, - ExpressibleByDictionaryLiteral { + public var uint16Value: UInt16? { + guard case .uint16(let uint) = self else { + return nil + } + return uint + } - public init(nilLiteral: ()) { - self = .nil + public var int32Value: Int32? { + guard case .int32(let int) = self else { + return nil + } + return int } - public init(booleanLiteral value: BooleanLiteralType) { - self = .bool(value) + public var uint32Value: UInt32? { + guard case .uint32(let uint) = self else { + return nil + } + return uint } - public init(stringLiteral value: StringLiteralType) { - self = .string(value) + public var int64Value: Int64? { + guard case .int64(let int) = self else { + return nil + } + return int } - public init(integerLiteral value: IntegerLiteralType) { - self = .int64(Int64(value)) + public var uint64Value: UInt64? { + guard case .uint64(let uint) = self else { + return nil + } + return uint } - public init(floatLiteral value: FloatLiteralType) { - self = .double(value) + public func integerValue(_ type: I.Type) -> I? { + switch self { + case .int8(let value): return I(value) + case .int16(let value): return I(value) + case .int32(let value): return I(value) + case .int64(let value): return I(value) + case .uint8(let value): return I(value) + case .uint16(let value): return I(value) + case .uint32(let value): return I(value) + case .uint64(let value): return I(value) + default: + return nil + } } - public typealias ArrayLiteralElement = AnyValue + public var float16Value: Float16? { + guard case .float16(let float) = self else { + return nil + } + return float + } - public init(arrayLiteral elements: ArrayLiteralElement...) { - self = .array(elements) + public var floatValue: Float? { + guard case .float(let float) = self else { + return nil + } + return float } - public typealias Key = String - public typealias Value = AnyValue + public var doubleValue: Double? { + guard case .double(let double) = self else { + return nil + } + return double + } - public init(dictionaryLiteral elements: (Key, Value)...) { - self = .dictionary(Dictionary(uniqueKeysWithValues: elements)) + public var decimalValue: Decimal? { + guard case .decimal(let decimal) = self else { + return nil + } + return decimal } + public func floatingPointValue(_ type: F.Type) -> F? { + switch self { + case .int8(let value): return F(value) + case .int16(let value): return F(value) + case .int32(let value): return F(value) + case .int64(let value): return F(value) + case .uint8(let value): return F(value) + case .uint16(let value): return F(value) + case .uint32(let value): return F(value) + case .uint64(let value): return F(value) + case .float16(let value): return F(value) + case .float(let value): return F(value) + case .double(let value): return F(value) + case .decimal(let value): return F(value.description) + default: + return nil + } + } } -// MARK: Value (tree) conformance +// MARK: Conformances +extension AnyValue: Equatable {} +extension AnyValue: Hashable {} extension AnyValue: Value { public var isNull: Bool { @@ -219,35 +294,88 @@ extension AnyValue: Value { return true } - public var unwrapped: Any? { +} + +extension AnyValue: CustomStringConvertible { + + public var description: String { switch self { - case .nil: return nil - case .bool(let value): return value + case .nil: return "nil" + case .bool(let value): return value.description + case .int8(let value): return value.description + case .int16(let value): return value.description + case .int32(let value): return value.description + case .int64(let value): return value.description + case .uint8(let value): return value.description + case .uint16(let value): return value.description + case .uint32(let value): return value.description + case .uint64(let value): return value.description + case .integer(let value): return value.description + case .unsignedInteger(let value): return value.description + case .float16(let value): return value.description + case .float(let value): return value.description + case .double(let value): return value.description + case .decimal(let value): return value.description case .string(let value): return value - case .int8(let value): return value - case .int16(let value): return value - case .int32(let value): return value - case .int64(let value): return value - case .uint8(let value): return value - case .uint16(let value): return value - case .uint32(let value): return value - case .uint64(let value): return value - case .integer(let value): return value - case .unsignedInteger(let value): return value - case .float(let value): return value - case .double(let value): return value - case .decimal(let value): return value - case .data(let value): return value - case .url(let value): return value - case .uuid(let value): return value - case .date(let value): return value - case .array(let value): return Array(value.map(\.unwrapped)) - case .dictionary(let value): return Dictionary(uniqueKeysWithValues: value.map { ($0, $1.unwrapped) }) + case .date(let value): return ZonedDate(date: value, timeZone: .utc).iso8601EncodedString() + case .data(let value): return value.description + case .uuid(let value): return value.uuidString + case .url(let value): return value.absoluteString + case .array(let value): return value.description + case .dictionary(let value): return value.description + } + } + +} + + +// MARK: Wrapping + +extension AnyValue { + + /// Wraps the value into an equivalent `AnyValue` tree + public static func wrapped(_ value: Any?) throws -> AnyValue { + guard let value = value else { return .nil } + switch value { + case let val as AnyValue: return val + case let val as String: return .string(val) + case let val as Int: return .int(val) + case let val as UInt: return .uint(val) + case let val as Bool: return .bool(val) + case let val as Int8: return .int8(val) + case let val as UInt8: return .uint8(val) + case let val as Int16: return .int16(val) + case let val as UInt16: return .uint16(val) + case let val as Int32: return .int32(val) + case let val as UInt32: return .uint32(val) + case let val as Int64: return .int64(val) + case let val as UInt64: return .uint64(val) + case let val as Decimal: return .decimal(val) // Before other floats (Swift Decimal -> Double allowed) + case let val as Float16: return .float16(val) + case let val as Float: return .float(val) + case let val as Double: return .double(val) + case let val as BigInt: return .integer(val) + case let val as BigUInt: return .unsignedInteger(val) + case let val as Data: return .data(val) + case let val as URL: return .url(val) + case let val as UUID: return .uuid(val) + case let val as Date: return .date(val) + case let val as AnyArray: return .array(val) + case let val as [Any]: return .array(try val.map { try wrapped($0) }) + case let val as AnyDictionary: return .dictionary(val) + case let val as [String: Any]: + return .dictionary(AnyDictionary(uniqueKeysWithValues: try val.map { (try wrapped($0), try wrapped($1)) })) + case let val as [Int: Any]: + return .dictionary(AnyDictionary(uniqueKeysWithValues: try val.map { (try wrapped($0), try wrapped($1)) })) + case let val as OrderedDictionary: + return .dictionary(AnyDictionary(uniqueKeysWithValues: try val.map { (try wrapped($0), try wrapped($1)) })) + case let val as OrderedDictionary: + return .dictionary(AnyDictionary(uniqueKeysWithValues: try val.map { (try wrapped($0), try wrapped($1)) })) + default: throw Error.unsupportedValue(value) } } - /// Unwraps all the values in the tree, filtering nils present in `array` or `dictionary` values - public var compactUnwrapped: Any? { + public var unwrapped: Any? { switch self { case .nil: return nil case .bool(let value): return value @@ -262,6 +390,7 @@ extension AnyValue: Value { case .uint64(let value): return value case .integer(let value): return value case .unsignedInteger(let value): return value + case .float16(let value): return value case .float(let value): return value case .double(let value): return value case .decimal(let value): return value @@ -269,162 +398,137 @@ extension AnyValue: Value { case .url(let value): return value case .uuid(let value): return value case .date(let value): return value - case .array(let value): return value.compactMap(\.compactUnwrapped) - case .dictionary(let value): return value.compactMapValues { $0.compactUnwrapped } + case .array(let value): return Array(value.map(\.unwrapped)) + case .dictionary(let value): + return Dictionary(uniqueKeysWithValues: value.compactMap { + guard let value = $1.unwrapped else { + return nil + } + if let key = $0.stringValue { + return (AnyHashable(key), value) + } + else if let key = $0.integerValue(Int.self) { + return (AnyHashable(key), value) + } + else { + // It is an unsupported key type, but we are returning + // nil(s) rather than throwing errors + return nil + } + }) as [AnyHashable: Any] } } } -// MARK: Decodable support - -extension AnyValue: Decodable { - - public init(from decoder: Swift.Decoder) throws { - - // Try keyed container - if let container = try? decoder.container(keyedBy: AnyCodingKey.self) { +// MARK: Literals - var dictionary = [String: AnyValue]() - for key in container.allKeys { - dictionary[key.stringValue] = try container.decode(AnyValue.self, forKey: key) - } - - self = .dictionary(dictionary) - return - } +extension AnyValue: ExpressibleByNilLiteral, ExpressibleByBooleanLiteral, ExpressibleByStringLiteral, + ExpressibleByIntegerLiteral, ExpressibleByFloatLiteral, ExpressibleByArrayLiteral, + ExpressibleByDictionaryLiteral { - // Try unkeyed container - if var container = try? decoder.unkeyedContainer() { + public init(nilLiteral: ()) { + self = .nil + } - var array = [AnyValue]() - for _ in 0 ..< (container.count ?? 0) { - array.append(try container.decode(AnyValue.self)) - } + public init(booleanLiteral value: BooleanLiteralType) { + self = .bool(value) + } - self = .array(array) - return - } + public init(stringLiteral value: StringLiteralType) { + self = .string(value) + } - // Treat as single value container - guard let container = try? decoder.singleValueContainer() else { - fatalError("Invalid decoder") - } + public init(integerLiteral value: IntegerLiteralType) { + self = .int64(Int64(value)) + } - if let rawContainer = container as? TreeValueDecodingContainer { - self = try Self.wrapped(rawContainer.decodeUnwrappedValue()) - return - } + public init(floatLiteral value: FloatLiteralType) { + self = .double(value) + } - if container.decodeNil() { - self = .nil - return - } + public typealias ArrayLiteralElement = AnyValue - if let value = try? container.decode(Bool.self) { - self = .bool(value) - return - } + public init(arrayLiteral elements: ArrayLiteralElement...) { + self = .array(elements) + } - if let value = try? container.decode(String.self) { - self = .string(value) - return - } + public typealias Key = AnyValue + public typealias Value = AnyValue - if let value = try? container.decode(Int8.self) { - self = .int8(value) - return - } + public init(dictionaryLiteral elements: (Key, Value)...) { + self = .dictionary(AnyDictionary(uniqueKeysWithValues: elements.map { ($0, $1) })) + } - if let value = try? container.decode(Int16.self) { - self = .int16(value) - return - } +} - if let value = try? container.decode(Int32.self) { - self = .int32(value) - return - } - if let value = try? container.decode(Int64.self) { - self = .int64(value) - return - } +// MARK: Codable +extension AnyValue: Decodable { - if let value = try? container.decode(UInt8.self) { - self = .uint8(value) - return - } + public init(from decoder: Swift.Decoder) throws { - if let value = try? container.decode(UInt16.self) { - self = .uint16(value) - return - } + // Try as single value container + if let container = try? decoder.singleValueContainer() { - if let value = try? container.decode(UInt32.self) { - self = .uint32(value) - return - } + if container.decodeNil() { + self = .nil + return + } - if let value = try? container.decode(UInt64.self) { - self = .uint64(value) - return - } + if let value = try? container.decode(Bool.self) { + self = .bool(value) + return + } - if let value = try? container.decode(BigInt.self) { - self = .integer(value) - return - } + if let value = try? container.decode(Int.self) { + self = .int(value) + return + } - if let value = try? container.decode(BigUInt.self) { - self = .unsignedInteger(value) - return - } + if let value = try? container.decode(Int64.self) { + self = .int64(value) + return + } - if let value = try? container.decode(Float.self) { - self = .float(value) - return - } + if let value = try? container.decode(UInt64.self) { + self = .uint64(value) + return + } - if let value = try? container.decode(Double.self) { - self = .double(value) - return - } + if let value = try? container.decode(BigInt.self) { + self = .integer(value) + return + } - if let value = try? container.decode(Decimal.self) { - self = .decimal(value) - return - } + if let value = try? container.decode(Double.self) { + self = .double(value) + return + } - if let value = try? container.decode(Data.self) { - self = .data(value) - return - } + if let value = try? container.decode(String.self) { + self = .string(value) + return + } - if let value = try? container.decode(URL.self) { - self = .url(value) - return - } + if let value = try? container.decode([AnyValue].self) { + self = .array(value) + return + } - if let value = try? container.decode(UUID.self) { - self = .uuid(value) - return } - if let value = try? container.decode(Date.self) { - self = .date(value) - return - } + // Try keyed container + if let container = try? decoder.container(keyedBy: AnyCodingKey.self) { - if let value = try? container.decode([AnyValue].self) { - self = .array(value) - return - } + var dictionary = AnyDictionary() + for key in container.allKeys { + dictionary[.string(key.stringValue)] = try container.decode(AnyValue.self, forKey: key) + } - if let value = try? container.decode([String: AnyValue].self) { - self = .dictionary(value) + self = .dictionary(dictionary) return } @@ -433,8 +537,6 @@ extension AnyValue: Decodable { } -// MARK: Encodable support - extension AnyValue: Encodable { public func encode(to encoder: Swift.Encoder) throws { @@ -466,6 +568,8 @@ extension AnyValue: Encodable { try container.encode(value) case .unsignedInteger(let value): try container.encode(value) + case .float16(let value): + try container.encode(value) case .float(let value): try container.encode(value) case .double(let value): @@ -490,11 +594,11 @@ extension AnyValue: Encodable { } -/// Make encoders/decoders available in AnyValue namespace -/// +// Make encoders/decoders available in AnyValue namespace + public extension AnyValue { typealias Encoder = AnyValueEncoder - typealias Decoder = AnyValueEncoder + typealias Decoder = AnyValueDecoder } diff --git a/Sources/PotentCodables/AnyValue/AnyValueDecoder.swift b/Sources/PotentCodables/AnyValue/AnyValueDecoder.swift index 58eee93de..b6b6cb1dd 100644 --- a/Sources/PotentCodables/AnyValue/AnyValueDecoder.swift +++ b/Sources/PotentCodables/AnyValue/AnyValueDecoder.swift @@ -8,7 +8,9 @@ // Distributed under the MIT License, See LICENSE for details. // +import BigInt import Foundation +import OrderedCollections public class AnyValueDecoder: ValueDecoder { @@ -39,199 +41,221 @@ public struct AnyValueDecoderTransform: InternalDecoderTransform { public let userInfo: [CodingUserInfoKey: Any] } - public static var nilValue: AnyValue { return .nil } - public static func unbox( - _ value: AnyValue, - as type: Bool.Type, - decoder: InternalValueDecoder - ) throws -> Bool? { + public static func intercepts(_ type: Decodable.Type) -> Bool { + return type == Date.self || type == NSDate.self + || type == Data.self || type == NSData.self + || type == URL.self || type == NSURL.self + || type == UUID.self || type == NSUUID.self + || type == Float16.self + || type == Decimal.self || type == NSDecimalNumber.self + || type == BigInt.self + || type == BigUInt.self + || type is DictionaryAdapter.Type + } + + public static func unbox(_ value: AnyValue, interceptedType: Decodable.Type, decoder: IVD) throws -> Any? { + if interceptedType == Date.self || interceptedType == NSDate.self { + return try unbox(value, as: Date.self, decoder: decoder) + } + else if interceptedType == Data.self || interceptedType == NSData.self { + return try unbox(value, as: Data.self, decoder: decoder) + } + else if interceptedType == URL.self || interceptedType == NSURL.self { + return try unbox(value, as: URL.self, decoder: decoder) + } + else if interceptedType == UUID.self || interceptedType == NSUUID.self { + return try unbox(value, as: UUID.self, decoder: decoder) + } + else if interceptedType == Float16.self { + return try unbox(value, as: Float16.self, decoder: decoder) + } + else if interceptedType == Decimal.self || interceptedType == NSDecimalNumber.self { + return try unbox(value, as: Decimal.self, decoder: decoder) + } + else if interceptedType == BigInt.self { + return try unbox(value, as: BigInt.self, decoder: decoder) + } + else if interceptedType == BigUInt.self { + return try unbox(value, as: BigUInt.self, decoder: decoder) + } + else if let dictType = interceptedType as? DictionaryAdapter.Type { + return try unbox(value, as: dictType, decoder: decoder) + } + fatalError("type not valid for intercept") + } + + fileprivate static func unbox(_ value: AnyValue, as type: DictionaryAdapter.Type, decoder: IVD) throws -> Any? { + guard case .dictionary(let dict) = value else { return nil } + var result = type.empty() + for (key, value) in dict { + guard let key = try decoder.unbox(value: key, as: type.keyType) else { + throw DecodingError.typeMismatch( + type.keyType, + .init(codingPath: decoder.codingPath, debugDescription: "Expected to decode key of type'\(type.keyType)' but got nil instead") + ) + } + guard let value = try decoder.unbox(value: value, as: type.valueType) else { + throw DecodingError.typeMismatch( + type.valueType, + .init(codingPath: decoder.codingPath, debugDescription: "Expected to decode value of type'\(type.keyType)' but got nil instead") + ) + } + result = type.assign(value: value, forKey: key, to: result) + } + return result + } + + public static func unbox(_ value: AnyValue, as type: Bool.Type, decoder: IVD) throws -> Bool? { guard case .bool(let value) = value else { return nil } return value } - public static func unbox( - _ value: AnyValue, - as type: Int.Type, - decoder: InternalValueDecoder - ) throws -> Int? { + public static func unbox(_ value: AnyValue, as type: Int.Type, decoder: IVD) throws -> Int? { switch MemoryLayout.size { case 4: return try unbox(value, as: Int32.self, decoder: decoder).flatMap { Int(exactly: $0)! } case 8: return try unbox(value, as: Int64.self, decoder: decoder).flatMap { Int(exactly: $0)! } default: - fatalError() + fatalError("unknown memory layout") } } - public static func unbox( - _ value: AnyValue, - as type: UInt.Type, - decoder: InternalValueDecoder - ) throws -> UInt? { + public static func unbox(_ value: AnyValue, as type: UInt.Type, decoder: IVD) throws -> UInt? { switch MemoryLayout.size { case 4: return try unbox(value, as: UInt32.self, decoder: decoder).flatMap { UInt(exactly: $0)! } case 8: return try unbox(value, as: UInt64.self, decoder: decoder).flatMap { UInt(exactly: $0)! } default: - fatalError() + fatalError("unknown memory layout") } } - public static func unbox( - _ value: AnyValue, - as type: Int8.Type, - decoder: InternalValueDecoder - ) throws -> Int8? { + public static func unbox(_ value: AnyValue, as type: Int8.Type, decoder: IVD) throws -> Int8? { guard case .int8(let value) = value else { return nil } return value } - public static func unbox( - _ value: AnyValue, - as type: Int16.Type, - decoder: InternalValueDecoder - ) throws -> Int16? { + public static func unbox(_ value: AnyValue, as type: Int16.Type, decoder: IVD) throws -> Int16? { guard case .int16(let value) = value else { return nil } return value } - public static func unbox( - _ value: AnyValue, - as type: Int32.Type, - decoder: InternalValueDecoder - ) throws -> Int32? { + public static func unbox(_ value: AnyValue, as type: Int32.Type, decoder: IVD) throws -> Int32? { guard case .int32(let value) = value else { return nil } return value } - public static func unbox( - _ value: AnyValue, - as type: Int64.Type, - decoder: InternalValueDecoder - ) throws -> Int64? { + public static func unbox(_ value: AnyValue, as type: Int64.Type, decoder: IVD) throws -> Int64? { guard case .int64(let value) = value else { return nil } return value } - public static func unbox( - _ value: AnyValue, - as type: UInt8.Type, - decoder: InternalValueDecoder - ) throws -> UInt8? { + public static func unbox(_ value: AnyValue, as type: UInt8.Type, decoder: IVD) throws -> UInt8? { guard case .uint8(let value) = value else { return nil } return value } - public static func unbox( - _ value: AnyValue, - as type: UInt16.Type, - decoder: InternalValueDecoder - ) throws -> UInt16? { + public static func unbox(_ value: AnyValue, as type: UInt16.Type, decoder: IVD) throws -> UInt16? { guard case .uint16(let value) = value else { return nil } return value } - public static func unbox( - _ value: AnyValue, - as type: UInt32.Type, - decoder: InternalValueDecoder - ) throws -> UInt32? { + public static func unbox(_ value: AnyValue, as type: UInt32.Type, decoder: IVD) throws -> UInt32? { guard case .uint32(let value) = value else { return nil } return value } - public static func unbox( - _ value: AnyValue, - as type: UInt64.Type, - decoder: InternalValueDecoder - ) throws -> UInt64? { + public static func unbox(_ value: AnyValue, as type: UInt64.Type, decoder: IVD) throws -> UInt64? { guard case .uint64(let value) = value else { return nil } return value } - public static func unbox( - _ value: AnyValue, - as type: Float.Type, - decoder: InternalValueDecoder - ) throws -> Float? { + public static func unbox(_ value: AnyValue, as type: Float16.Type, decoder: IVD) throws -> Float16? { + guard case .float16(let value) = value else { return nil } + return value + } + + public static func unbox(_ value: AnyValue, as type: Float.Type, decoder: IVD) throws -> Float? { guard case .float(let value) = value else { return nil } return value } - public static func unbox( - _ value: AnyValue, - as type: Double.Type, - decoder: InternalValueDecoder - ) throws -> Double? { + public static func unbox(_ value: AnyValue, as type: Double.Type, decoder: IVD) throws -> Double? { guard case .double(let value) = value else { return nil } return value } - public static func unbox( - _ value: AnyValue, - as type: String.Type, - decoder: InternalValueDecoder - ) throws -> String? { + public static func unbox(_ value: AnyValue, as type: String.Type, decoder: IVD) throws -> String? { guard case .string(let value) = value else { return nil } return value } - public static func unbox( - _ value: AnyValue, - as type: UUID.Type, - decoder: InternalValueDecoder - ) throws -> UUID? { + public static func unbox(_ value: AnyValue, as type: UUID.Type, decoder: IVD) throws -> UUID? { guard case .uuid(let value) = value else { return nil } return value } - public static func unbox( - _ value: AnyValue, - as type: Date.Type, - decoder: InternalValueDecoder - ) throws -> Date? { + public static func unbox(_ value: AnyValue, as type: URL.Type, decoder: IVD) throws -> URL? { + guard case .url(let value) = value else { return nil } + return value + } + + public static func unbox(_ value: AnyValue, as type: Date.Type, decoder: IVD) throws -> Date? { guard case .date(let value) = value else { return nil } return value } - public static func unbox( - _ value: AnyValue, - as type: Data.Type, - decoder: InternalValueDecoder - ) throws -> Data? { + public static func unbox(_ value: AnyValue, as type: Data.Type, decoder: IVD) throws -> Data? { guard case .data(let value) = value else { return nil } return value } - public static func unbox( - _ value: AnyValue, - as type: Decimal.Type, - decoder: InternalValueDecoder - ) throws -> Decimal? { + public static func unbox(_ value: AnyValue, as type: Decimal.Type, decoder: IVD) throws -> Decimal? { guard case .decimal(let value) = value else { return nil } return value } - public static func valueToUnkeyedValues( - _ value: AnyValue, - decoder: InternalValueDecoder - ) throws -> [AnyValue]? { + public static func unbox(_ value: AnyValue, as type: BigInt.Type, decoder: IVD) throws -> BigInt? { + guard case .integer(let value) = value else { return nil } + return value + } + + public static func unbox(_ value: AnyValue, as type: BigUInt.Type, decoder: IVD) throws -> BigUInt? { + guard case .unsignedInteger(let value) = value else { return nil } + return value + } + + public static func valueToUnkeyedValues(_ value: AnyValue, decoder: IVD) throws -> UnkeyedValues? { guard case .array(let array) = value else { return nil } return array } - public static func valueToKeyedValues( - _ value: AnyValue, - decoder: InternalValueDecoder - ) throws -> [String: AnyValue]? { + public static func valueToKeyedValues(_ value: AnyValue, decoder: IVD) throws -> KeyedValues? { guard case .dictionary(let dictionary) = value else { return nil } - return dictionary + return try KeyedValues( + dictionary.map { key, value in + switch key { + case .float16, .float, .double, .decimal, .date, .data, .uuid, .url, .array, .dictionary: + throw DecodingError.dataCorrupted(.init( + codingPath: decoder.codingPath, + debugDescription: "Dictionary contains unsupported keys" + )) + default: return (key.description, value) + } + }, + uniquingKeysWith: { _, _ in + throw DecodingError.dataCorrupted(.init( + codingPath: decoder.codingPath, + debugDescription: "Dictionary contains duplicate keys" + )) + } + ) } } @@ -250,3 +274,38 @@ public struct AnyValueDecoderTransform: InternalDecoderTransform { } #endif + + + +private protocol DictionaryAdapter { + static var keyType: Decodable.Type { get } + static var valueType: Decodable.Type { get } + static func empty() -> Any + static func assign(value: Any, forKey: Any, to dict: Any) -> Any +} + +extension Dictionary: DictionaryAdapter where Key: Decodable, Value: Decodable { + static var keyType: Decodable.Type { Key.self } + static var valueType: Decodable.Type { Value.self } + static func empty() -> Any { return Dictionary() } + static func assign(value: Any, forKey key: Any, to dict: Any) -> Any { + var dict = dict as! Self // swiftlint:disable:this force_cast + let key = key as! Key // swiftlint:disable:this force_cast + let value = value as! Value // swiftlint:disable:this force_cast + dict[key] = value + return dict + } +} + +extension OrderedDictionary: DictionaryAdapter where Key: Decodable, Value: Decodable { + static var keyType: Decodable.Type { Key.self } + static var valueType: Decodable.Type { Value.self } + static func empty() -> Any { return OrderedDictionary() } + static func assign(value: Any, forKey key: Any, to dict: Any) -> Any { + var dict = dict as! Self // swiftlint:disable:this force_cast + let key = key as! Key // swiftlint:disable:this force_cast + let value = value as! Value // swiftlint:disable:this force_cast + dict[key] = value + return dict + } +} diff --git a/Sources/PotentCodables/AnyValue/AnyValueEncoder.swift b/Sources/PotentCodables/AnyValue/AnyValueEncoder.swift index 09140a161..3c12b761d 100644 --- a/Sources/PotentCodables/AnyValue/AnyValueEncoder.swift +++ b/Sources/PotentCodables/AnyValue/AnyValueEncoder.swift @@ -8,7 +8,9 @@ // Distributed under the MIT License, See LICENSE for details. // +import BigInt import Foundation +import OrderedCollections public class AnyValueEncoder: ValueEncoder { @@ -47,169 +49,181 @@ public struct AnyValueEncoderTransform: InternalEncoderTransform { return .array([]) } - public static func boxNil(encoder: InternalValueEncoder) throws -> AnyValue { + public static func intercepts(_ type: Encodable.Type) -> Bool { + return type == Date.self || type == NSDate.self + || type == Data.self || type == NSData.self + || type == URL.self || type == NSURL.self + || type == UUID.self || type == NSUUID.self + || type == Float16.self + || type == Decimal.self || type == NSDecimalNumber.self + || type == BigInt.self + || type == BigUInt.self + || type is DictionaryAdapter.Type + } + + public static func box(_ value: Any, interceptedType: Encodable.Type, encoder: IVE) throws -> AnyValue { + if let value = value as? Date { + return try box(value, encoder: encoder) + } + else if let value = value as? Data { + return try box(value, encoder: encoder) + } + else if let value = value as? URL { + return try box(value, encoder: encoder) + } + else if let value = value as? UUID { + return try box(value, encoder: encoder) + } + else if let value = value as? Float16 { + return try box(value, encoder: encoder) + } + else if let value = value as? Decimal { + return try box(value, encoder: encoder) + } + else if let value = value as? BigInt { + return try box(value, encoder: encoder) + } + else if let value = value as? BigUInt { + return try box(value, encoder: encoder) + } + else if let value = value as? DictionaryAdapter { + return try box(value, encoder: encoder) + } + fatalError("type not valid for intercept") + } + + fileprivate static func box(_ value: DictionaryAdapter, encoder: IVE) throws -> AnyValue { + var dict = AnyValue.AnyDictionary() + for (key, value) in value.entries { + guard let boxedKey = try encoder.box(value: key) else { + throw EncodingError.invalidValue( + key, + .init(codingPath: encoder.codingPath, debugDescription: "Expected to encode key '\(key)' but got nil instead") + ) + } + guard let boxedValue = try encoder.box(value: value) else { + throw EncodingError.invalidValue( + key, + .init(codingPath: encoder.codingPath, debugDescription: "Expected to encode value '\(value)' but got nil instead") + ) + } + dict[boxedKey] = boxedValue + } + return .dictionary(dict) + } + + public static func boxNil(encoder: IVE) throws -> AnyValue { return .nil } - public static func box( - _ value: Bool, - encoder: InternalValueEncoder - ) throws -> AnyValue { + public static func box(_ value: Bool, encoder: IVE) throws -> AnyValue { return .bool(value) } - public static func box( - _ value: Int, - encoder: InternalValueEncoder - ) throws -> AnyValue { + public static func box(_ value: Int, encoder: IVE) throws -> AnyValue { switch MemoryLayout.size { case 4: return try box(Int32(value), encoder: encoder) case 8: return try box(Int64(value), encoder: encoder) default: - fatalError() + fatalError("unknown memory layout") } } - public static func box( - _ value: Int8, - encoder: InternalValueEncoder - ) throws -> AnyValue { + public static func box(_ value: Int8, encoder: IVE) throws -> AnyValue { return .int8(value) } - public static func box( - _ value: Int16, - encoder: InternalValueEncoder - ) throws -> AnyValue { + public static func box(_ value: Int16, encoder: IVE) throws -> AnyValue { return .int16(value) } - public static func box( - _ value: Int32, - encoder: InternalValueEncoder - ) throws -> AnyValue { + public static func box(_ value: Int32, encoder: IVE) throws -> AnyValue { return .int32(value) } - public static func box( - _ value: Int64, - encoder: InternalValueEncoder - ) throws -> AnyValue { + public static func box(_ value: Int64, encoder: IVE) throws -> AnyValue { return .int64(value) } - public static func box( - _ value: UInt, - encoder: InternalValueEncoder - ) throws -> AnyValue { + public static func box(_ value: UInt, encoder: IVE) throws -> AnyValue { switch MemoryLayout.size { case 4: return try box(UInt32(value), encoder: encoder) case 8: return try box(UInt64(value), encoder: encoder) default: - fatalError() + fatalError("unknown memory layout") } } - public static func box( - _ value: UInt8, - encoder: InternalValueEncoder - ) throws -> AnyValue { + public static func box(_ value: UInt8, encoder: IVE) throws -> AnyValue { return .uint8(value) } - public static func box( - _ value: UInt16, - encoder: InternalValueEncoder - ) throws -> AnyValue { + public static func box(_ value: UInt16, encoder: IVE) throws -> AnyValue { return .uint16(value) } - public static func box( - _ value: UInt32, - encoder: InternalValueEncoder - ) throws -> AnyValue { + public static func box(_ value: UInt32, encoder: IVE) throws -> AnyValue { return .uint32(value) } - public static func box( - _ value: UInt64, - encoder: InternalValueEncoder - ) throws -> AnyValue { + public static func box(_ value: UInt64, encoder: IVE) throws -> AnyValue { return .uint64(value) } - public static func box( - _ value: String, - encoder: InternalValueEncoder - ) throws -> AnyValue { + public static func box(_ value: String, encoder: IVE) throws -> AnyValue { return .string(value) } - public static func box( - _ value: Float, - encoder: InternalValueEncoder - ) throws -> AnyValue { + public static func box(_ value: Float16, encoder: IVE) throws -> AnyValue { + return .float16(value) + } + + public static func box(_ value: Float, encoder: IVE) throws -> AnyValue { return .float(value) } - public static func box( - _ value: Double, - encoder: InternalValueEncoder - ) throws -> AnyValue { + public static func box(_ value: Double, encoder: IVE) throws -> AnyValue { return .double(value) } - public static func box( - _ value: Decimal, - encoder: InternalValueEncoder - ) throws -> AnyValue { + public static func box(_ value: Decimal, encoder: IVE) throws -> AnyValue { return .decimal(value) } - public static func box( - _ value: Data, - encoder: InternalValueEncoder - ) throws -> AnyValue { + public static func box(_ value: Data, encoder: IVE) throws -> AnyValue { return .data(value) } - public static func box( - _ value: URL, - encoder: InternalValueEncoder - ) throws -> AnyValue { + public static func box(_ value: URL, encoder: IVE) throws -> AnyValue { return .url(value) } - public static func box( - _ value: UUID, - encoder: InternalValueEncoder - ) throws -> AnyValue { + public static func box(_ value: UUID, encoder: IVE) throws -> AnyValue { return .uuid(value) } - public static func box( - _ value: Date, - encoder: InternalValueEncoder - ) throws -> AnyValue { + public static func box(_ value: Date, encoder: IVE) throws -> AnyValue { return .date(value) } - public static func unkeyedValuesToValue( - _ values: [AnyValue], - encoder: InternalValueEncoder - ) -> AnyValue { - return .array(values) + public static func box(_ value: BigInt, encoder: IVE) throws -> AnyValue { + return .integer(value) + } + + public static func box(_ value: BigUInt, encoder: IVE) throws -> AnyValue { + return .unsignedInteger(value) + } + + public static func unkeyedValuesToValue(_ values: UnkeyedValues, encoder: IVE) throws -> AnyValue { + return .array(AnyValue.AnyArray(values)) } - public static func keyedValuesToValue( - _ values: [String: AnyValue], - encoder: InternalValueEncoder - ) -> AnyValue { - return .dictionary(values) + public static func keyedValuesToValue(_ values: KeyedValues, encoder: IVE) throws -> AnyValue { + return .dictionary(AnyValue.AnyDictionary(uniqueKeysWithValues: values.map { (key: .string($0), value: $1) })) } } @@ -228,3 +242,15 @@ public struct AnyValueEncoderTransform: InternalEncoderTransform { } #endif + + + +private protocol DictionaryAdapter { + var entries: [(Encodable, Encodable)] { get } +} +extension Dictionary: DictionaryAdapter where Key: Encodable, Value: Encodable { + var entries: [(Encodable, Encodable)] { Array(self) } +} +extension OrderedDictionary: DictionaryAdapter where Key: Encodable, Value: Encodable { + var entries: [(Encodable, Encodable)] { Array(elements) } +} diff --git a/Sources/PotentCodables/CodableErrors.swift b/Sources/PotentCodables/Errors.swift similarity index 100% rename from Sources/PotentCodables/CodableErrors.swift rename to Sources/PotentCodables/Errors.swift diff --git a/Sources/PotentCodables/Float16.swift b/Sources/PotentCodables/Float16.swift new file mode 100644 index 000000000..e98182acb --- /dev/null +++ b/Sources/PotentCodables/Float16.swift @@ -0,0 +1,45 @@ +// +// Float16.swift +// PotentCodables +// +// Copyright © 2021 Outfox, inc. +// +// +// Distributed under the MIT License, See LICENSE for details. +// + +#if arch(x86_64) + +import Float16 + + +/// Float16 shim for intel architectures +public typealias Float16 = float16 + + +extension Float16: Codable { + + public init(from decoder: Decoder) throws { + let container = try decoder.singleValueContainer() + self.init(try container.decode(Float.self)) + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.singleValueContainer() + try container.encode(Float(self)) + } + +} + +extension Float16: LosslessStringConvertible { + + public init?(_ description: String) { + guard let float = Float(description) else { + return nil + } + self.init(float) + } + +} + +#endif diff --git a/Sources/PotentCodables/NestedDecoders.swift b/Sources/PotentCodables/KeyedNestedDecoder.swift similarity index 53% rename from Sources/PotentCodables/NestedDecoders.swift rename to Sources/PotentCodables/KeyedNestedDecoder.swift index e23b8081a..fdc59f864 100644 --- a/Sources/PotentCodables/NestedDecoders.swift +++ b/Sources/PotentCodables/KeyedNestedDecoder.swift @@ -1,5 +1,5 @@ // -// NestedDecoders.swift +// KeyedNestedDecoder.swift // PotentCodables // // Copyright © 2021 Outfox, inc. @@ -111,101 +111,3 @@ public class KeyedNestedDecoder: Decoder, SingleValueDecodingConta } } - - -public class UnkeyedNestedDecoder: Decoder, SingleValueDecodingContainer { - - var container: UnkeyedDecodingContainer - let decoder: Decoder - - public init(container: UnkeyedDecodingContainer, decoder: Decoder) { - self.container = container - self.decoder = decoder - } - - public var userInfo: [CodingUserInfoKey: Any] { decoder.userInfo } - - public func container(keyedBy type: Key.Type) throws -> KeyedDecodingContainer where Key: CodingKey { - return try container.nestedContainer(keyedBy: type) - } - - public func unkeyedContainer() throws -> UnkeyedDecodingContainer { - return try container.nestedUnkeyedContainer() - } - - public func singleValueContainer() throws -> SingleValueDecodingContainer { - return self - } - - // Single value container - - public var codingPath: [CodingKey] { container.codingPath + [AnyCodingKey(index: container.currentIndex)] } - - public func decodeNil() -> Bool { - guard let isNil = try? container.decodeNil() else { - return true - } - return isNil - } - - public func decode(_ type: Bool.Type) throws -> Bool { - return try container.decode(type) - } - - public func decode(_ type: String.Type) throws -> String { - return try container.decode(type) - } - - public func decode(_ type: Float.Type) throws -> Float { - return try container.decode(type) - } - - public func decode(_ type: Double.Type) throws -> Double { - return try container.decode(type) - } - - public func decode(_ type: Int.Type) throws -> Int { - return try container.decode(type) - } - - public func decode(_ type: Int8.Type) throws -> Int8 { - return try container.decode(type) - } - - public func decode(_ type: Int16.Type) throws -> Int16 { - return try container.decode(type) - } - - public func decode(_ type: Int32.Type) throws -> Int32 { - return try container.decode(type) - } - - public func decode(_ type: Int64.Type) throws -> Int64 { - return try container.decode(type) - } - - public func decode(_ type: UInt.Type) throws -> UInt { - return try container.decode(type) - } - - public func decode(_ type: UInt8.Type) throws -> UInt8 { - return try container.decode(type) - } - - public func decode(_ type: UInt16.Type) throws -> UInt16 { - return try container.decode(type) - } - - public func decode(_ type: UInt32.Type) throws -> UInt32 { - return try container.decode(type) - } - - public func decode(_ type: UInt64.Type) throws -> UInt64 { - return try container.decode(type) - } - - public func decode(_ type: T.Type) throws -> T where T: Decodable { - return try container.decode(type) - } - -} diff --git a/Sources/PotentCodables/NestedEncoders.swift b/Sources/PotentCodables/KeyedNestedEncoder.swift similarity index 54% rename from Sources/PotentCodables/NestedEncoders.swift rename to Sources/PotentCodables/KeyedNestedEncoder.swift index 9d6f4253a..5bdeb477e 100644 --- a/Sources/PotentCodables/NestedEncoders.swift +++ b/Sources/PotentCodables/KeyedNestedEncoder.swift @@ -1,5 +1,5 @@ // -// NestedEncoders.swift +// KeyedNestedEncoder.swift // PotentCodables // // Copyright © 2021 Outfox, inc. @@ -110,100 +110,3 @@ public class KeyedNestedEncoder: Encoder, SingleValueEncodingConta } } - - -public class UnkeyedNestedEncoder: Encoder, SingleValueEncodingContainer { - - var container: UnkeyedEncodingContainer - let encoder: Encoder - - public init(container: UnkeyedEncodingContainer, encoder: Encoder) { - self.container = container - self.encoder = encoder - } - - public var userInfo: [CodingUserInfoKey: Any] { encoder.userInfo } - - public func container(keyedBy type: Key.Type) -> KeyedEncodingContainer where Key: CodingKey { - return container.nestedContainer(keyedBy: type) - } - - public func unkeyedContainer() -> UnkeyedEncodingContainer { - return container.nestedUnkeyedContainer() - } - - public func singleValueContainer() -> SingleValueEncodingContainer { - return self - } - - // Single value container - - public var codingPath: [CodingKey] { - return container.codingPath + [AnyCodingKey(index: container.count)] - } - - public func encodeNil() throws { - try container.encodeNil() - } - - public func encode(_ value: String) throws { - try container.encode(value) - } - - public func encode(_ value: Double) throws { - try container.encode(value) - } - - public func encode(_ value: Float) throws { - try container.encode(value) - } - - public func encode(_ value: Int) throws { - try container.encode(value) - } - - public func encode(_ value: Int8) throws { - try container.encode(value) - } - - public func encode(_ value: Int16) throws { - try container.encode(value) - } - - public func encode(_ value: Int32) throws { - try container.encode(value) - } - - public func encode(_ value: Int64) throws { - try container.encode(value) - } - - public func encode(_ value: UInt) throws { - try container.encode(value) - } - - public func encode(_ value: UInt8) throws { - try container.encode(value) - } - - public func encode(_ value: UInt16) throws { - try container.encode(value) - } - - public func encode(_ value: UInt32) throws { - try container.encode(value) - } - - public func encode(_ value: UInt64) throws { - try container.encode(value) - } - - public func encode(_ value: T) throws where T: Encodable { - try container.encode(value) - } - - public func encode(_ value: Bool) throws { - try container.encode(value) - } - -} diff --git a/Sources/PotentCodables/Refs.swift b/Sources/PotentCodables/Refs.swift index f69d96cb1..8756efeae 100644 --- a/Sources/PotentCodables/Refs.swift +++ b/Sources/PotentCodables/Refs.swift @@ -66,21 +66,141 @@ public protocol TypeIndex { /// public struct DefaultTypeIndex: TypeIndex { - fileprivate static var allowedTypes: [String: Decodable.Type] = [:] + fileprivate static var allowedTypes: [String: Decodable.Type] = [ + typeId(of: Bool.self): Bool.self, + typeId(of: Int.self): Int.self, + typeId(of: Int8.self): Int8.self, + typeId(of: Int16.self): Int16.self, + typeId(of: Int32.self): Int32.self, + typeId(of: Int64.self): Int64.self, + typeId(of: UInt.self): UInt.self, + typeId(of: UInt8.self): UInt8.self, + typeId(of: UInt16.self): UInt16.self, + typeId(of: UInt32.self): UInt32.self, + typeId(of: UInt64.self): UInt64.self, + typeId(of: Float16.self): Float16.self, + typeId(of: Float.self): Float.self, + typeId(of: Double.self): Double.self, + typeId(of: Decimal.self): Decimal.self, + typeId(of: String.self): String.self, + typeId(of: URL.self): URL.self, + typeId(of: UUID.self): UUID.self, + typeId(of: Date.self): Date.self, + typeId(of: Bool?.self): Bool?.self, + typeId(of: Int?.self): Int?.self, + typeId(of: Int8?.self): Int8?.self, + typeId(of: Int16?.self): Int16?.self, + typeId(of: Int32?.self): Int32?.self, + typeId(of: Int64?.self): Int64?.self, + typeId(of: UInt?.self): UInt?.self, + typeId(of: UInt8?.self): UInt8?.self, + typeId(of: UInt16?.self): UInt16?.self, + typeId(of: UInt32?.self): UInt32?.self, + typeId(of: UInt64?.self): UInt64?.self, + typeId(of: Float16?.self): Float16?.self, + typeId(of: Float?.self): Float?.self, + typeId(of: Double?.self): Double?.self, + typeId(of: Decimal?.self): Decimal?.self, + typeId(of: String?.self): String?.self, + typeId(of: URL?.self): URL?.self, + typeId(of: UUID?.self): UUID?.self, + typeId(of: Date?.self): Date?.self, + typeId(of: [Bool].self): [Bool].self, + typeId(of: [Int].self): [Int].self, + typeId(of: [Int8].self): [Int8].self, + typeId(of: [Int16].self): [Int16].self, + typeId(of: [Int32].self): [Int32].self, + typeId(of: [Int64].self): [Int64].self, + typeId(of: [UInt].self): [UInt].self, + typeId(of: [UInt8].self): [UInt8].self, + typeId(of: [UInt16].self): [UInt16].self, + typeId(of: [UInt32].self): [UInt32].self, + typeId(of: [UInt64].self): [UInt64].self, + typeId(of: [Float16].self): [Float16].self, + typeId(of: [Float].self): [Float].self, + typeId(of: [Double].self): [Double].self, + typeId(of: [Decimal].self): [Decimal].self, + typeId(of: [String].self): [String].self, + typeId(of: [URL].self): [URL].self, + typeId(of: [UUID].self): [UUID].self, + typeId(of: [Date].self): [Date].self, + typeId(of: [Bool]?.self): [Bool]?.self, + typeId(of: [Int]?.self): [Int]?.self, + typeId(of: [Int8]?.self): [Int8]?.self, + typeId(of: [Int16]?.self): [Int16]?.self, + typeId(of: [Int32]?.self): [Int32]?.self, + typeId(of: [Int64]?.self): [Int64]?.self, + typeId(of: [UInt]?.self): [UInt]?.self, + typeId(of: [UInt8]?.self): [UInt8]?.self, + typeId(of: [UInt16]?.self): [UInt16]?.self, + typeId(of: [UInt32]?.self): [UInt32]?.self, + typeId(of: [UInt64]?.self): [UInt64]?.self, + typeId(of: [Float16]?.self): [Float16]?.self, + typeId(of: [Float]?.self): [Float]?.self, + typeId(of: [Double]?.self): [Double]?.self, + typeId(of: [Decimal]?.self): [Decimal]?.self, + typeId(of: [String]?.self): [String]?.self, + typeId(of: [URL]?.self): [URL]?.self, + typeId(of: [UUID]?.self): [UUID]?.self, + typeId(of: [Date]?.self): [Date]?.self, + typeId(of: [String: Bool].self): [String: Bool].self, + typeId(of: [String: Int].self): [String: Int].self, + typeId(of: [String: Int8].self): [String: Int8].self, + typeId(of: [String: Int16].self): [String: Int16].self, + typeId(of: [String: Int32].self): [String: Int32].self, + typeId(of: [String: Int64].self): [String: Int64].self, + typeId(of: [String: UInt].self): [String: UInt].self, + typeId(of: [String: UInt8].self): [String: UInt8].self, + typeId(of: [String: UInt16].self): [String: UInt16].self, + typeId(of: [String: UInt32].self): [String: UInt32].self, + typeId(of: [String: UInt64].self): [String: UInt64].self, + typeId(of: [String: Float16].self): [String: Float16].self, + typeId(of: [String: Float].self): [String: Float].self, + typeId(of: [String: Double].self): [String: Double].self, + typeId(of: [String: Decimal].self): [String: Decimal].self, + typeId(of: [String: String].self): [String: String].self, + typeId(of: [String: URL].self): [String: URL].self, + typeId(of: [String: UUID].self): [String: UUID].self, + typeId(of: [String: Date].self): [String: Date].self, + typeId(of: [String: Bool]?.self): [String: Bool]?.self, + typeId(of: [String: Int]?.self): [String: Int]?.self, + typeId(of: [String: Int8]?.self): [String: Int8]?.self, + typeId(of: [String: Int16]?.self): [String: Int16]?.self, + typeId(of: [String: Int32]?.self): [String: Int32]?.self, + typeId(of: [String: Int64]?.self): [String: Int64]?.self, + typeId(of: [String: UInt]?.self): [String: UInt]?.self, + typeId(of: [String: UInt8]?.self): [String: UInt8]?.self, + typeId(of: [String: UInt16]?.self): [String: UInt16]?.self, + typeId(of: [String: UInt32]?.self): [String: UInt32]?.self, + typeId(of: [String: UInt64]?.self): [String: UInt64]?.self, + typeId(of: [String: Float16]?.self): [String: Float16]?.self, + typeId(of: [String: Float]?.self): [String: Float]?.self, + typeId(of: [String: Double]?.self): [String: Double]?.self, + typeId(of: [String: Decimal]?.self): [String: Decimal]?.self, + typeId(of: [String: String]?.self): [String: String]?.self, + typeId(of: [String: URL]?.self): [String: URL]?.self, + typeId(of: [String: UUID]?.self): [String: UUID]?.self, + typeId(of: [String: Date]?.self): [String: Date]?.self, + ] // Set the allowed types to the given array after mapping them using `mapAllowedTypes(:)`. public static func setAllowedTypes(_ types: [Decodable.Type]) { Self.allowedTypes = mapAllowedTypes(types) } + // Set the allowed types to the given array after mapping them using `mapAllowedTypes(:)`. + public static func addAllowedTypes(_ types: [Decodable.Type]) { + Self.allowedTypes = Self.allowedTypes.merging(mapAllowedTypes(types)) { $1 } + } + // Maps the given array of types to their generated type id and returns the dictionary. public static func mapAllowedTypes(_ types: [Decodable.Type]) -> [String: Decodable.Type] { return Dictionary(uniqueKeysWithValues: types.map { (key: Self.typeId(of: $0), value: $0) }) } public static func findType(id: String) -> Decodable.Type? { allowedTypes[id] } - public static func typeId(of type: Any.Type) -> String { "\(type)".split(separator: ".").last - .map { String($0) } ?? "\(type)" + public static func typeId(of type: Any.Type) -> String { + "\(type)".split(separator: ".").last.map { String($0) } ?? "\(type)" } } diff --git a/Sources/PotentCodables/Tagged.swift b/Sources/PotentCodables/Tagged.swift deleted file mode 100644 index 169b3ef11..000000000 --- a/Sources/PotentCodables/Tagged.swift +++ /dev/null @@ -1,229 +0,0 @@ -// -// Tagged.swift -// PotentCodables -// -// Copyright © 2021 Outfox, inc. -// -// -// Distributed under the MIT License, See LICENSE for details. -// - -import Foundation - - -/// Keys for requesting tagged values. -/// -/// For formats that support tagged values (YAML, CBOR, ASN1, etc.) encoders & decoders _may_ -/// support accessing the values as a destructured into a keyed container with the `tag` and `value` -/// keys. Using `TaggedItemKeys` allows clients to unambiguously access the destructured -/// components to determine how they should treat the value. -/// -/// # Note: -/// Use of `TaggedItemKeys` do not require specific support by Encoders/Decoders. Although, -/// using it without explicit support will require the keyed containers to be present in data being -/// decoded and will produce them explicitly when encoding. Formats with explicit support will -/// use a more efficient encoding. -/// -public enum TaggedItemKeys: String, CodingKey { - case tag = "@tag" - case value = "@value" -} - - - -public extension KeyedDecodingContainerProtocol where Key == TaggedItemKeys { - - /// Decodes an expected tag value. - /// - /// Decodes a tag value and throws an error if the tag is not of the expected value. - /// - func decode(_ type: D.Type, expectingTag tag: Tag) throws -> D { - let foundTag = try decode(Tag.self, forKey: .tag) - guard foundTag == tag else { - let desc = "Expected \(tag), found \(foundTag)" - throw DecodingError.typeMismatch(Self.self, DecodingError.Context( - codingPath: codingPath, - debugDescription: desc - )) - } - return try decode(type, forKey: .value) - } - - /// Decodes an expected tag value. - /// - /// Decodes a tag value and throws an error if the tag is not of the expected value. - /// - func decode( - _ type: D.Type, - expectingTag tag: Tag - ) throws -> D where Tag.RawValue: Equatable & Decodable { - let foundTagValue = try decode(Tag.RawValue.self, forKey: .tag) - guard foundTagValue == tag.rawValue else { - let foundTagName = Tag(rawValue: foundTagValue).map { String(describing: $0) } ?? "\(tag)" - let desc = "Expected \(tag), found \(foundTagName)" - throw DecodingError.typeMismatch(Self.self, DecodingError.Context( - codingPath: codingPath, - debugDescription: desc - )) - } - return try decode(type, forKey: .value) - } - - /// Decodes an expected tag value. - /// - /// Decodes a tag value and throws an error if the tag is not of the expected value. - /// - func decodeIfPresent( - _ type: D.Type, - expectingTag tag: Tag, - nullTag: Tag - ) throws -> D? { - let foundTag = try decode(Tag.self, forKey: .tag) - if foundTag == nullTag { - return nil - } - return try decode(type, expectingTag: tag) - } - - /// Decodes an expected tag value. - /// - /// Decodes a tag value and throws an error if the tag is not of the expected value. - /// - func decodeIfPresent( - _ type: D.Type, - expectingTag tag: Tag, - nullTag: Tag - ) throws -> D? where Tag.RawValue: Equatable & Decodable { - let foundTag = try decode(Tag.self, forKey: .tag) - if foundTag == nullTag { - return nil - } - return try decode(type, expectingTag: tag) - } - - /// Retrieves a nested keyed container for an expected tag value. - /// - func nestedContainer( - keyedBy type: Key.Type, - expectingTag tag: Tag - ) throws -> KeyedDecodingContainer { - let foundTag = try decode(Tag.self, forKey: .tag) - guard foundTag == tag else { - let desc = "Expected \(tag), found \(foundTag)" - throw DecodingError.typeMismatch(Self.self, DecodingError.Context( - codingPath: codingPath, - debugDescription: desc - )) - } - return try nestedContainer(keyedBy: type, forKey: .value) - } - - /// Retrieves a nested keyed container for an expected tag value. - /// - func nestedContainer( - keyedBy type: Key.Type, - expectingTag tag: Tag - ) throws -> KeyedDecodingContainer where Tag.RawValue: Equatable & Decodable { - let foundTagValue = try decode(Tag.RawValue.self, forKey: .tag) - guard foundTagValue == tag.rawValue else { - let foundTagName = Tag(rawValue: foundTagValue).map { String(describing: $0) } ?? "\(tag)" - let desc = "Expected \(tag), found \(foundTagName)" - throw DecodingError.typeMismatch(Self.self, DecodingError.Context( - codingPath: codingPath, - debugDescription: desc - )) - } - return try nestedContainer(keyedBy: type, forKey: .value) - } - - /// Retrieves a nested unkeyed container for an expected tag value. - /// - func nestedUnkeyedContainer(expectingTag tag: Tag) throws -> UnkeyedDecodingContainer { - let foundTag = try decode(Tag.self, forKey: .tag) - guard foundTag == tag else { - let desc = "Expected \(tag), found \(foundTag)" - throw DecodingError.typeMismatch(Self.self, DecodingError.Context( - codingPath: codingPath, - debugDescription: desc - )) - } - return try nestedUnkeyedContainer(forKey: .value) - } - - /// Retrieves a nested unkeyed container for an expected tag value. - /// - func nestedUnkeyedContainer(expectingTag tag: Tag) throws - -> UnkeyedDecodingContainer where Tag.RawValue: Equatable & Decodable { - let foundTagValue = try decode(Tag.RawValue.self, forKey: .tag) - guard foundTagValue == tag.rawValue else { - let foundTagName = Tag(rawValue: foundTagValue).map { String(describing: $0) } ?? "\(tag)" - let desc = "Expected \(tag), found \(foundTagName)" - throw DecodingError.typeMismatch(Self.self, DecodingError.Context( - codingPath: codingPath, - debugDescription: desc - )) - } - return try nestedUnkeyedContainer(forKey: .value) - } - -} - - - -public extension KeyedEncodingContainerProtocol where Key == TaggedItemKeys { - - /// Encodes a value with an associated tag value. - /// - mutating func encode(_ value: E, withTag tag: Tag) throws { - try encode(tag, forKey: .tag) - try encode(value, forKey: .value) - } - - /// Encodes a value with an associated tag value. - /// - mutating func encode(_ value: E, withTag tag: Tag) throws - where Tag.RawValue: Encodable { - try encode(tag.rawValue, forKey: .tag) - try encode(value, forKey: .value) - } - - /// Retrieves a nested keyed container for an expected tag value. - /// - mutating func nestedContainer( - keyedBy type: NestedKey.Type, - withTag tag: Tag - ) throws -> KeyedEncodingContainer { - try encode(tag, forKey: .tag) - return nestedContainer(keyedBy: type, forKey: .value) - } - - /// Retrieves a nested keyed container for an expected tag value. - /// - mutating func nestedContainer< - NestedKey: CodingKey, - Tag: Equatable & Encodable & RawRepresentable - >( - keyedBy type: NestedKey.Type, - withTag tag: Tag - ) throws -> KeyedEncodingContainer where Tag.RawValue: Equatable & Encodable { - try encode(tag.rawValue, forKey: .tag) - return nestedContainer(keyedBy: type, forKey: .value) - } - - /// Retrieves a nested unkeyed container for an expected tag value. - /// - mutating func nestedUnkeyedContainer(withTag tag: Tag) throws - -> UnkeyedEncodingContainer { - try encode(tag, forKey: .tag) - return nestedUnkeyedContainer(forKey: .value) - } - - /// Retrieves a nested unkeyed container for an expected tag value. - /// - mutating func nestedUnkeyedContainer(withTag tag: Tag) throws - -> UnkeyedEncodingContainer where Tag.RawValue: Equatable & Encodable { - try encode(tag, forKey: .tag) - return nestedUnkeyedContainer(forKey: .value) - } - -} diff --git a/Sources/PotentCodables/TreeValueDecodingContainer.swift b/Sources/PotentCodables/TreeValueDecodingContainer.swift deleted file mode 100644 index 689d7c4b9..000000000 --- a/Sources/PotentCodables/TreeValueDecodingContainer.swift +++ /dev/null @@ -1,20 +0,0 @@ -// -// TreeValueDecodingContainer.swift -// PotentCodables -// -// Copyright © 2021 Outfox, inc. -// -// -// Distributed under the MIT License, See LICENSE for details. -// - -import Foundation - - -public protocol TreeValueDecodingContainer: SingleValueDecodingContainer { - - func decodeUnwrappedValue() -> Any? - - func decodeTreeValue() -> Any? - -} diff --git a/Sources/PotentCodables/Value.swift b/Sources/PotentCodables/Value.swift index cc80e1938..93ae81c56 100644 --- a/Sources/PotentCodables/Value.swift +++ b/Sources/PotentCodables/Value.swift @@ -19,6 +19,4 @@ public protocol Value: Hashable { var isNull: Bool { get } - var unwrapped: Any? { get } - } diff --git a/Sources/PotentCodables/ValueDecoder.swift b/Sources/PotentCodables/ValueDecoder.swift index 8e73db49c..38962d7ba 100644 --- a/Sources/PotentCodables/ValueDecoder.swift +++ b/Sources/PotentCodables/ValueDecoder.swift @@ -9,6 +9,7 @@ // import Foundation +import OrderedCollections /// Decoder options that will be passed through the decoding process @@ -52,35 +53,32 @@ public protocol InternalDecoderTransform { static var nilValue: Value { get } - static func unbox(_ value: Value, as type: Bool.Type, decoder: InternalValueDecoder) throws -> Bool? - static func unbox(_ value: Value, as type: Int.Type, decoder: InternalValueDecoder) throws -> Int? - static func unbox(_ value: Value, as type: Int8.Type, decoder: InternalValueDecoder) throws -> Int8? - static func unbox(_ value: Value, as type: Int16.Type, decoder: InternalValueDecoder) throws -> Int16? - static func unbox(_ value: Value, as type: Int32.Type, decoder: InternalValueDecoder) throws -> Int32? - static func unbox(_ value: Value, as type: Int64.Type, decoder: InternalValueDecoder) throws -> Int64? - static func unbox(_ value: Value, as type: UInt.Type, decoder: InternalValueDecoder) throws -> UInt? - static func unbox(_ value: Value, as type: UInt8.Type, decoder: InternalValueDecoder) throws -> UInt8? - static func unbox(_ value: Value, as type: UInt16.Type, decoder: InternalValueDecoder) throws -> UInt16? - static func unbox(_ value: Value, as type: UInt32.Type, decoder: InternalValueDecoder) throws -> UInt32? - static func unbox(_ value: Value, as type: UInt64.Type, decoder: InternalValueDecoder) throws -> UInt64? - static func unbox(_ value: Value, as type: Float.Type, decoder: InternalValueDecoder) throws -> Float? - static func unbox(_ value: Value, as type: Double.Type, decoder: InternalValueDecoder) throws -> Double? - static func unbox(_ value: Value, as type: String.Type, decoder: InternalValueDecoder) throws -> String? - static func unbox(_ value: Value, as type: UUID.Type, decoder: InternalValueDecoder) throws -> UUID? - static func unbox(_ value: Value, as type: Date.Type, decoder: InternalValueDecoder) throws -> Date? - static func unbox(_ value: Value, as type: Data.Type, decoder: InternalValueDecoder) throws -> Data? - static func unbox(_ value: Value, as type: Decimal.Type, decoder: InternalValueDecoder) throws - -> Decimal? + typealias IVD = InternalValueDecoder + typealias UnkeyedValues = [Value] + typealias KeyedValues = OrderedDictionary + + static func unbox(_ value: Value, as type: Bool.Type, decoder: IVD) throws -> Bool? + static func unbox(_ value: Value, as type: Int.Type, decoder: IVD) throws -> Int? + static func unbox(_ value: Value, as type: Int8.Type, decoder: IVD) throws -> Int8? + static func unbox(_ value: Value, as type: Int16.Type, decoder: IVD) throws -> Int16? + static func unbox(_ value: Value, as type: Int32.Type, decoder: IVD) throws -> Int32? + static func unbox(_ value: Value, as type: Int64.Type, decoder: IVD) throws -> Int64? + static func unbox(_ value: Value, as type: UInt.Type, decoder: IVD) throws -> UInt? + static func unbox(_ value: Value, as type: UInt8.Type, decoder: IVD) throws -> UInt8? + static func unbox(_ value: Value, as type: UInt16.Type, decoder: IVD) throws -> UInt16? + static func unbox(_ value: Value, as type: UInt32.Type, decoder: IVD) throws -> UInt32? + static func unbox(_ value: Value, as type: UInt64.Type, decoder: IVD) throws -> UInt64? + static func unbox(_ value: Value, as type: Float.Type, decoder: IVD) throws -> Float? + static func unbox(_ value: Value, as type: Double.Type, decoder: IVD) throws -> Double? + static func unbox(_ value: Value, as type: String.Type, decoder: IVD) throws -> String? static func intercepts(_ type: Decodable.Type) -> Bool - static func unbox(_ value: Value, interceptedType: Decodable.Type, decoder: InternalValueDecoder) throws - -> Any? + static func unbox(_ value: Value, interceptedType: Decodable.Type, decoder: IVD) throws -> Any? - static func unbox(_ value: Value, otherType: Decodable.Type, decoder: InternalValueDecoder) throws - -> Any? + static func unbox(_ value: Value, otherType: Decodable.Type, decoder: IVD) throws -> Any? - static func valueToUnkeyedValues(_ value: Value, decoder: InternalValueDecoder) throws -> [Value]? - static func valueToKeyedValues(_ value: Value, decoder: InternalValueDecoder) throws -> [String: Value]? + static func valueToUnkeyedValues(_ value: Value, decoder: IVD) throws -> UnkeyedValues? + static func valueToKeyedValues(_ value: Value, decoder: IVD) throws -> KeyedValues? } @@ -379,7 +377,7 @@ private struct ValueKeyedDecodingContainer: Keye private let decoder: InternalValueDecoder /// A reference to the container we're reading from. - private let container: [String: Value] + private let container: OrderedDictionary /// The path of coding keys taken to get to this point in decoding. public private(set) var codingPath: [CodingKey] @@ -387,13 +385,13 @@ private struct ValueKeyedDecodingContainer: Keye // MARK: - Initialization /// Initializes `self` by referencing the given decoder and container. - fileprivate init(referencing decoder: InternalValueDecoder, wrapping container: [String: Value]) throws { + fileprivate init(referencing decoder: InternalValueDecoder, wrapping container: OrderedDictionary) throws { self.decoder = decoder switch decoder.options.keyDecodingStrategy { case .convertFromSnakeCase: // Convert the snake case keys in the container to camel case. // If we hit a duplicate key after conversion, then we'll use the first one we saw. Effectively an undefined behavior with dictionaries. - self.container = try Dictionary( + self.container = try OrderedDictionary( container.map { (KeyDecodingStrategy.convertFromSnakeCase($0.key), $0.value) }, uniquingKeysWith: { _, _ in @@ -403,7 +401,7 @@ private struct ValueKeyedDecodingContainer: Keye )) }) case .custom(let converter): - self.container = try Dictionary( + self.container = try OrderedDictionary( container.map { key, value in ( converter(decoder.codingPath + [AnyCodingKey(stringValue: key, intValue: nil)]).stringValue, value) @@ -457,14 +455,14 @@ private struct ValueKeyedDecodingContainer: Keye let original = key.stringValue let converted = KeyEncodingStrategy.convertToSnakeCase(original) if converted == original { - return "\(key) (\"\(original)\")" + return #""\#(original)""# } else { - return "\(key) (\"\(original)\"), converted to \(converted)" + return #""\#(original)" (\#(converted))"# } default: // Otherwise, just report the converted string - return "\(key) (\"\(key.stringValue)\")" + return #""\#(key.stringValue)""# } } @@ -1159,7 +1157,7 @@ private struct ValueUnkeyedDecodingContainer: UnkeyedDecodingC } } -extension InternalValueDecoder: SingleValueDecodingContainer, TreeValueDecodingContainer { +extension InternalValueDecoder: SingleValueDecodingContainer { // MARK: SingleValueDecodingContainer Methods private func expectNonNull(_ type: T.Type) throws { @@ -1172,12 +1170,15 @@ extension InternalValueDecoder: SingleValueDecodingContainer, TreeValueDecodingC } } - public func decodeTreeValue() -> Any? { - return storage.topContainer - } - - public func decodeUnwrappedValue() -> Any? { - return storage.topContainer.unwrapped + private func unwrap(_ value: T?) throws -> T { + guard let value = value else { + throw DecodingError.valueNotFound( + T.self, + DecodingError + .Context(codingPath: codingPath, debugDescription: "Expected \(T.self) but found null value instead.") + ) + } + return value } public func decodeNil() -> Bool { @@ -1186,77 +1187,77 @@ extension InternalValueDecoder: SingleValueDecodingContainer, TreeValueDecodingC public func decode(_ type: Bool.Type) throws -> Bool { try expectNonNull(Bool.self) - return try unbox(storage.topContainer, as: Bool.self)! + return try unwrap(unbox(storage.topContainer, as: Bool.self)) } public func decode(_ type: Int.Type) throws -> Int { try expectNonNull(Int.self) - return try unbox(storage.topContainer, as: Int.self)! + return try unwrap(unbox(storage.topContainer, as: Int.self)) } public func decode(_ type: Int8.Type) throws -> Int8 { try expectNonNull(Int8.self) - return try unbox(storage.topContainer, as: Int8.self)! + return try unwrap(unbox(storage.topContainer, as: Int8.self)) } public func decode(_ type: Int16.Type) throws -> Int16 { try expectNonNull(Int16.self) - return try unbox(storage.topContainer, as: Int16.self)! + return try unwrap(unbox(storage.topContainer, as: Int16.self)) } public func decode(_ type: Int32.Type) throws -> Int32 { try expectNonNull(Int32.self) - return try unbox(storage.topContainer, as: Int32.self)! + return try unwrap(unbox(storage.topContainer, as: Int32.self)) } public func decode(_ type: Int64.Type) throws -> Int64 { try expectNonNull(Int64.self) - return try unbox(storage.topContainer, as: Int64.self)! + return try unwrap(unbox(storage.topContainer, as: Int64.self)) } public func decode(_ type: UInt.Type) throws -> UInt { try expectNonNull(UInt.self) - return try unbox(storage.topContainer, as: UInt.self)! + return try unwrap(unbox(storage.topContainer, as: UInt.self)) } public func decode(_ type: UInt8.Type) throws -> UInt8 { try expectNonNull(UInt8.self) - return try unbox(storage.topContainer, as: UInt8.self)! + return try unwrap(unbox(storage.topContainer, as: UInt8.self)) } public func decode(_ type: UInt16.Type) throws -> UInt16 { try expectNonNull(UInt16.self) - return try unbox(storage.topContainer, as: UInt16.self)! + return try unwrap(unbox(storage.topContainer, as: UInt16.self)) } public func decode(_ type: UInt32.Type) throws -> UInt32 { try expectNonNull(UInt32.self) - return try unbox(storage.topContainer, as: UInt32.self)! + return try unwrap(unbox(storage.topContainer, as: UInt32.self)) } public func decode(_ type: UInt64.Type) throws -> UInt64 { try expectNonNull(UInt64.self) - return try unbox(storage.topContainer, as: UInt64.self)! + return try unwrap(unbox(storage.topContainer, as: UInt64.self)) } public func decode(_ type: Float.Type) throws -> Float { try expectNonNull(Float.self) - return try unbox(storage.topContainer, as: Float.self)! + return try unwrap(unbox(storage.topContainer, as: Float.self)) } public func decode(_ type: Double.Type) throws -> Double { try expectNonNull(Double.self) - return try unbox(storage.topContainer, as: Double.self)! + return try unwrap(unbox(storage.topContainer, as: Double.self)) } public func decode(_ type: String.Type) throws -> String { try expectNonNull(String.self) - return try unbox(storage.topContainer, as: String.self)! + return try unwrap(unbox(storage.topContainer, as: String.self)) } public func decode(_ type: T.Type) throws -> T { try expectNonNull(type) - return try unbox(storage.topContainer, as: type)! + return try unwrap(unbox(storage.topContainer, as: type)) } } @@ -1319,22 +1320,6 @@ private extension InternalValueDecoder { return try Transform.unbox(value, as: type, decoder: self) } - func unbox(_ value: Value, as type: UUID.Type) throws -> UUID? { - return try Transform.unbox(value, as: type, decoder: self) - } - - func unbox(_ value: Value, as type: Date.Type) throws -> Date? { - return try Transform.unbox(value, as: type, decoder: self) - } - - func unbox(_ value: Value, as type: Data.Type) throws -> Data? { - return try Transform.unbox(value, as: type, decoder: self) - } - - func unbox(_ value: Value, as type: Decimal.Type) throws -> Decimal? { - return try Transform.unbox(value, as: type, decoder: self) - } - func unbox(_ value: Value, as type: ValueStringDictionaryDecodableMarker.Type) throws -> T? { guard !value.isNull else { return nil } @@ -1347,45 +1332,32 @@ private extension InternalValueDecoder { codingPath.append(AnyCodingKey(stringValue: key, intValue: nil)) defer { self.codingPath.removeLast() } - result[key] = try unbox_(value, as: elementType) + result[key] = try unbox(value: value, as: elementType) } return result as? T } func unbox(_ value: Value, as type: T.Type) throws -> T? { - return try unbox_(value, as: type) as? T - } - - func unbox_(_ value: Value, as type: Decodable.Type) throws -> Any? { - if Transform.intercepts(type) { - return try Transform.unbox(value, interceptedType: type, decoder: self) + guard let unboxed = try unbox(value: value, as: type) else { + return nil } - if type == Date.self || type == NSDate.self { - return try unbox(value, as: Date.self) + guard let result = unboxed as? T else { + throw DecodingError.typeMismatch(at: codingPath, expectation: type, reality: unboxed) } - else if type == Data.self || type == NSData.self { - return try unbox(value, as: Data.self) - } - else if type == UUID.self || type == CFUUID.self { - return try unbox(value, as: UUID.self) - } - else if type == URL.self || type == NSURL.self { - guard let urlString = try unbox(value, as: String.self) else { - return nil - } + return result + } - guard let url = URL(string: urlString) else { - throw DecodingError.dataCorrupted(DecodingError.Context( - codingPath: codingPath, - debugDescription: "Invalid URL string." - )) - } +} - return url +public extension InternalValueDecoder { + + func unbox(value: Value, as type: Decodable.Type) throws -> Any? { + if type == Value.self { + return value } - else if type == Decimal.self || type == NSDecimalNumber.self { - return try unbox(value, as: Decimal.self) + else if Transform.intercepts(type) { + return try Transform.unbox(value, interceptedType: type, decoder: self) } else if let stringKeyedDictType = type as? ValueStringDictionaryDecodableMarker.Type { return try unbox(value, as: stringKeyedDictType) @@ -1394,8 +1366,10 @@ private extension InternalValueDecoder { return try Transform.unbox(value, otherType: type, decoder: self) } } + } + /// A marker protocol used to determine whether a value is a `String`-keyed `Dictionary` /// containing `Decodable` values (in which case it should be exempt from key conversion strategies). /// @@ -1420,19 +1394,11 @@ public extension InternalDecoderTransform { return false } - static func unbox( - _ value: Value, - interceptedType: Decodable.Type, - decoder: InternalValueDecoder - ) throws -> Any? { - fatalError() + static func unbox(_ value: Value, interceptedType: Decodable.Type, decoder: IVD) throws -> Any? { + fatalError("abstract") } - static func unbox( - _ value: Value, - otherType: Decodable.Type, - decoder: InternalValueDecoder - ) throws -> Any? { + static func unbox(_ value: Value, otherType: Decodable.Type, decoder: IVD) throws -> Any? { return try decoder.subDecode(with: value) { decoder in try otherType.init(from: decoder) } } diff --git a/Sources/PotentCodables/ValueEncoder.swift b/Sources/PotentCodables/ValueEncoder.swift index fe851ec24..957f73d98 100644 --- a/Sources/PotentCodables/ValueEncoder.swift +++ b/Sources/PotentCodables/ValueEncoder.swift @@ -8,7 +8,9 @@ // Distributed under the MIT License, See LICENSE for details. // +import BigInt import Foundation +import OrderedCollections /// Encoder options that will be passed through the encoding process @@ -53,35 +55,32 @@ public protocol InternalEncoderTransform { static var emptyKeyedContainer: Value { get } static var emptyUnkeyedContainer: Value { get } - static func boxNil(encoder: InternalValueEncoder) throws -> Value - static func box(_ value: Bool, encoder: InternalValueEncoder) throws -> Value - static func box(_ value: Int, encoder: InternalValueEncoder) throws -> Value - static func box(_ value: Int8, encoder: InternalValueEncoder) throws -> Value - static func box(_ value: Int16, encoder: InternalValueEncoder) throws -> Value - static func box(_ value: Int32, encoder: InternalValueEncoder) throws -> Value - static func box(_ value: Int64, encoder: InternalValueEncoder) throws -> Value - static func box(_ value: UInt, encoder: InternalValueEncoder) throws -> Value - static func box(_ value: UInt8, encoder: InternalValueEncoder) throws -> Value - static func box(_ value: UInt16, encoder: InternalValueEncoder) throws -> Value - static func box(_ value: UInt32, encoder: InternalValueEncoder) throws -> Value - static func box(_ value: UInt64, encoder: InternalValueEncoder) throws -> Value - static func box(_ value: String, encoder: InternalValueEncoder) throws -> Value - static func box(_ value: Float, encoder: InternalValueEncoder) throws -> Value - static func box(_ value: Double, encoder: InternalValueEncoder) throws -> Value - static func box(_ value: Decimal, encoder: InternalValueEncoder) throws -> Value - static func box(_ value: Data, encoder: InternalValueEncoder) throws -> Value - static func box(_ value: URL, encoder: InternalValueEncoder) throws -> Value - static func box(_ value: UUID, encoder: InternalValueEncoder) throws -> Value - static func box(_ value: Date, encoder: InternalValueEncoder) throws -> Value + typealias IVE = InternalValueEncoder + typealias UnkeyedValues = [Value] + typealias KeyedValues = OrderedDictionary + + static func boxNil(encoder: IVE) throws -> Value + static func box(_ value: Bool, encoder: IVE) throws -> Value + static func box(_ value: Int, encoder: IVE) throws -> Value + static func box(_ value: Int8, encoder: IVE) throws -> Value + static func box(_ value: Int16, encoder: IVE) throws -> Value + static func box(_ value: Int32, encoder: IVE) throws -> Value + static func box(_ value: Int64, encoder: IVE) throws -> Value + static func box(_ value: UInt, encoder: IVE) throws -> Value + static func box(_ value: UInt8, encoder: IVE) throws -> Value + static func box(_ value: UInt16, encoder: IVE) throws -> Value + static func box(_ value: UInt32, encoder: IVE) throws -> Value + static func box(_ value: UInt64, encoder: IVE) throws -> Value + static func box(_ value: String, encoder: IVE) throws -> Value + static func box(_ value: Float, encoder: IVE) throws -> Value + static func box(_ value: Double, encoder: IVE) throws -> Value static func intercepts(_ type: Encodable.Type) -> Bool - static func box(_ value: Any, interceptedType: Encodable.Type, encoder: InternalValueEncoder) throws - -> Value + static func box(_ value: Any, interceptedType: Encodable.Type, encoder: IVE) throws -> Value + static func box(_ value: Any, otherType: Encodable.Type, encoder: IVE) throws -> Value? - static func box(_ value: Any, otherType: Encodable.Type, encoder: InternalValueEncoder) throws -> Value? - - static func unkeyedValuesToValue(_ values: [Value], encoder: InternalValueEncoder) throws -> Value - static func keyedValuesToValue(_ values: [String: Value], encoder: InternalValueEncoder) throws -> Value + static func unkeyedValuesToValue(_ values: UnkeyedValues, encoder: IVE) throws -> Value + static func keyedValuesToValue(_ values: KeyedValues, encoder: IVE) throws -> Value } @@ -97,7 +96,7 @@ open class ValueEncoder where Transform: InternalEncoderTransf open var userInfo: [CodingUserInfoKey: Any] = [:] /// The options set on the top-level encoder. - open var options: Transform.Options { fatalError() } + open var options: Transform.Options { fatalError("abstract") } // MARK: - Constructing a Value Encoder @@ -117,7 +116,7 @@ open class ValueEncoder where Transform: InternalEncoderTransf open func encodeTree(_ value: T) throws -> Value { let encoder = InternalValueEncoder(options: options) - guard let topLevel = try encoder.box_(value) else { + guard let topLevel = try encoder.box(value: value) else { throw EncodingError.invalidValue( value, EncodingError.Context(codingPath: [], debugDescription: "Top-level \(T.self) did not encode any values.") @@ -196,16 +195,6 @@ public class InternalValueEncoder: Encoder where Transform: In public var containerCount: Int { storage.containers.count } - public var containerTypes: [String] { - return storage.containers.map { - switch $0 { - case is KeyedContainer: return "Keyed" - case is UnkeyedContainer: return "Unkeyed" - default: return "Value" - } - } - } - /// Contextual user-provided information for use during encoding. public var userInfo: [CodingUserInfoKey: Any] { return options.userInfo @@ -293,11 +282,11 @@ public class InternalValueEncoder: Encoder where Transform: In return self } - public func subEncode(_ block: (Encoder) throws -> Void) throws -> Value? { + public func subEncode(_ block: (SubEncoder) throws -> Void) throws -> Value? { // The value should request a container from the InternalValueEncoder. let depth = storage.count do { - try block(self) + try block(SubEncoder(encoder: self)) } catch { // If the value pushed a container before throwing, pop it back off to restore state. @@ -336,8 +325,8 @@ public class InternalValueEncoder: Encoder where Transform: In } } - private func rollUp(keyed: [String: Any]) throws -> [String: Value] { - var result = [String: Value]() + private func rollUp(keyed: OrderedDictionary) throws -> OrderedDictionary { + var result = OrderedDictionary() for (key, value) in keyed { result[key] = try rollUp(value) } @@ -365,6 +354,31 @@ public class InternalValueEncoder: Encoder where Transform: In } +public struct SubEncoder where Transform: InternalEncoderTransform, Value == Transform.Value { + + public let encoder: InternalValueEncoder + + fileprivate init(encoder: InternalValueEncoder) { + self.encoder = encoder + } + + public func withCodingKey(_ key: CodingKey, _ block: () throws -> Void) rethrows { + self.encoder.codingPath.append(key) + defer { self.encoder.codingPath.removeLast() } + try block() + } + + public func keyedContainer() -> KeyedContainer { + return encoder.storage.pushKeyedContainer() + } + + public func unkeyedContainer() -> UnkeyedContainer { + return encoder.storage.pushUnkeyedContainer() + } + +} + + // MARK: - Encoding Storage and Containers private struct ValueEncodingStorage where Transform: InternalEncoderTransform, @@ -673,8 +687,7 @@ private struct ValueUnkeyedEncodingContainer: UnkeyedEncodingC } public mutating func nestedContainer( - keyedBy keyType: NestedKey - .Type + keyedBy keyType: NestedKey.Type ) -> KeyedEncodingContainer { let nestedEncoder = ValueReferencingEncoder(referencing: encoder, at: count, wrapping: container) @@ -713,7 +726,7 @@ extension InternalValueEncoder: SingleValueEncodingContainer { fileprivate func assertCanEncodeNewValue() { precondition( canEncodeNewValue, - "Attempt to encode value through single value container when previously value already encoded." + "Attempt to encode value through single value container when previous value already encoded." ) } @@ -802,7 +815,8 @@ extension InternalValueEncoder: SingleValueEncodingContainer { private extension InternalValueEncoder { - /// Returns the given value boxed in a container appropriate for pushing onto the container stack. + // Returns the given value boxed in a container appropriate for pushing onto the container stack. + func boxNil() throws -> Value { return try Transform.boxNil(encoder: self) } func box(_ value: Bool) throws -> Value { return try Transform.box(value, encoder: self) } func box(_ value: Int) throws -> Value { return try Transform.box(value, encoder: self) } @@ -815,25 +829,20 @@ private extension InternalValueEncoder { func box(_ value: UInt16) throws -> Value { return try Transform.box(value, encoder: self) } func box(_ value: UInt32) throws -> Value { return try Transform.box(value, encoder: self) } func box(_ value: UInt64) throws -> Value { return try Transform.box(value, encoder: self) } - func box(_ value: String) throws -> Value { return try Transform.box(value, encoder: self) } func box(_ value: Float) throws -> Value { return try Transform.box(value, encoder: self) } func box(_ value: Double) throws -> Value { return try Transform.box(value, encoder: self) } - func box(_ value: Decimal) throws -> Value { return try Transform.box(value, encoder: self) } - func box(_ value: Data) throws -> Value { return try Transform.box(value, encoder: self) } - func box(_ value: URL) throws -> Value { return try Transform.box(value, encoder: self) } - func box(_ value: UUID) throws -> Value { return try Transform.box(value, encoder: self) } - func box(_ value: Date) throws -> Value { return try Transform.box(value, encoder: self) } + func box(_ value: String) throws -> Value { return try Transform.box(value, encoder: self) } func box(_ dict: [String: Encodable]) throws -> Value? { - return try subEncode { _ in + return try subEncode { subEncoder in - let result = storage.pushKeyedContainer() + let result = subEncoder.keyedContainer() for (key, value) in dict { - codingPath.append(AnyCodingKey(stringValue: key, intValue: nil)) - defer { self.codingPath.removeLast() } - result[key] = try box(value) + try subEncoder.withCodingKey(AnyCodingKey(stringValue: key, intValue: nil)) { + result[key] = try box(value) + } } } @@ -841,44 +850,30 @@ private extension InternalValueEncoder { } func box(_ value: Encodable) throws -> Value { - return try box_(value) ?? Transform.boxNil(encoder: self) + return try box(value: value) ?? Transform.boxNil(encoder: self) } - // This method is called "box_" instead of "box" to disambiguate it from the - // overloads. Because the return type here is different from all of the "box" - // overloads (and is more general), any "box" calls in here would call back - // into "box" recursively instead of calling the appropriate overload, which - // is not what we want. - // - func box_(_ value: Encodable) throws -> Value? { +} + +public extension InternalValueEncoder { + + func box(value: Encodable) throws -> Value? { + if let value = value as? Value { + return value + } let type = Swift.type(of: value) if Transform.intercepts(type) { return try Transform.box(value, interceptedType: type, encoder: self) } - else if type == Date.self || type == NSDate.self { - // Respect Date encoding strategy - return try box(value as! Date) // swiftlint:disable:this force_cast - - } - else if type == Data.self || type == NSData.self { - // Respect Data encoding strategy - return try box(value as! Data) // swiftlint:disable:this force_cast - } - else if type == URL.self || type == NSURL.self { - // Encode URLs as single strings. - return try box((value as! URL).absoluteString) // swiftlint:disable:this force_cast - } - else if type == Decimal.self || type == NSDecimalNumber.self { - // Encode Decimals as doubles. - return try box(NSDecimalNumber(decimal: value as! Decimal).doubleValue) // swiftlint:disable:this force_cast - } else if value is ValueStringDictionaryEncodableMarker { return try box((value as Any) as! [String: Encodable]) // swiftlint:disable:this force_cast } return try Transform.box(value, otherType: type, encoder: self) } + } + // MARK: - ValueReferencingEncoder /// ValueReferencingEncoder is a special subclass of InternalValueEncoder which @@ -929,6 +924,9 @@ private class ValueReferencingEncoder: InternalValueEncoder: InternalValueEncoder Any { if depth < encoder.containerCount { return encoder.container(depth: depth) @@ -1006,10 +1000,14 @@ private class ValueReferencingEncoder: InternalValueEncoder - public init(backing: [String: Any] = [:]) { - self.backing = backing + public init() { + self.backing = [:] + } + + public init(backing: OrderedDictionary) { + self.backing = backing.mapValues { $0 } } public subscript(key: String) -> Any? { @@ -1041,15 +1039,6 @@ public class UnkeyedContainer { backing.insert(newElement, at: at) } - public subscript(index: Int) -> Any { - get { - return backing[index] - } - set { - backing[index] = newValue - } - } - } @@ -1068,21 +1057,13 @@ public extension InternalEncoderTransform { return false } - static func box( - _ value: Any, - interceptedType: Encodable.Type, - encoder: InternalValueEncoder - ) throws -> Value { - fatalError() + static func box(_ value: Any, interceptedType: Encodable.Type, encoder: IVE) throws -> Value { + fatalError("abstract") } - static func box( - _ value: Any, - otherType: Encodable.Type, - encoder: InternalValueEncoder - ) throws -> Value? { + static func box(_ value: Any, otherType: Encodable.Type, encoder: IVE) throws -> Value? { // swiftlint:disable:next force_cast - return try encoder.subEncode { encoder in try (value as! Encodable).encode(to: encoder) } + return try encoder.subEncode { subEncoder in try (value as! Encodable).encode(to: subEncoder.encoder) } } } diff --git a/Sources/PotentCodables/ValueTransformer.swift b/Sources/PotentCodables/ValueTransformer.swift index 0a8ccfc7a..d26654c09 100644 --- a/Sources/PotentCodables/ValueTransformer.swift +++ b/Sources/PotentCodables/ValueTransformer.swift @@ -454,6 +454,14 @@ public extension KeyedEncodingContainer { try encode(transformer.encode(value), forKey: key) } + mutating func encode( + _ value: Transformer.Target, + forKey key: Key, + using transformer: Transformer + ) throws where Transformer.Source == Int { + try encode(transformer.encode(value), forKey: key) + } + mutating func encode( _ value: Transformer.Target, forKey key: Key, @@ -486,6 +494,14 @@ public extension KeyedEncodingContainer { try encode(transformer.encode(value), forKey: key) } + mutating func encode( + _ value: Transformer.Target, + forKey key: Key, + using transformer: Transformer + ) throws where Transformer.Source == UInt { + try encode(transformer.encode(value), forKey: key) + } + mutating func encode( _ value: Transformer.Target, forKey key: Key, @@ -568,6 +584,19 @@ public extension KeyedEncodingContainer { try encode(value, forKey: key, using: transformer) } + mutating func encodeIfPresent( + _ value: Transformer.Target?, + forKey key: Key, + using transformer: Transformer + ) throws where Transformer.Source == Int { + guard let value = value else { + try encodeIfPresent(nil as Transformer.Source?, forKey: key) + return + } + + try encode(value, forKey: key, using: transformer) + } + mutating func encodeIfPresent( _ value: Transformer.Target?, forKey key: Key, @@ -620,6 +649,19 @@ public extension KeyedEncodingContainer { try encode(value, forKey: key, using: transformer) } + mutating func encodeIfPresent( + _ value: Transformer.Target?, + forKey key: Key, + using transformer: Transformer + ) throws where Transformer.Source == UInt { + guard let value = value else { + try encodeIfPresent(nil as Transformer.Source?, forKey: key) + return + } + + try encode(value, forKey: key, using: transformer) + } + mutating func encodeIfPresent( _ value: Transformer.Target?, forKey key: Key, @@ -723,94 +765,159 @@ public extension UnkeyedEncodingContainer { } mutating func encode( - _ value: Transformer.Target, + _ value: Transformer.Target?, using transformer: Transformer ) throws where Transformer.Source == Bool { - try encode(transformer.encode(value)) + if let value = try value.map({ try transformer.encode($0) }) { + try encode(value) + } + else { + try encodeNil() + } } mutating func encode( - _ value: Transformer.Target, + _ value: Transformer.Target?, using transformer: Transformer ) throws where Transformer.Source == String { - try encode(transformer.encode(value)) + if let value = try value.map({ try transformer.encode($0) }) { + try encode(value) + } + else { + try encodeNil() + } } mutating func encode( - _ value: Transformer.Target, + _ value: Transformer.Target?, using transformer: Transformer ) throws where Transformer.Source == Int8 { - try encode(transformer.encode(value)) + if let value = try value.map({ try transformer.encode($0) }) { + try encode(value) + } + else { + try encodeNil() + } } mutating func encode( - _ value: Transformer.Target, + _ value: Transformer.Target?, using transformer: Transformer ) throws where Transformer.Source == Int16 { - try encode(transformer.encode(value)) + if let value = try value.map({ try transformer.encode($0) }) { + try encode(value) + } + else { + try encodeNil() + } } mutating func encode( - _ value: Transformer.Target, + _ value: Transformer.Target?, using transformer: Transformer ) throws where Transformer.Source == Int32 { - try encode(transformer.encode(value)) + if let value = try value.map({ try transformer.encode($0) }) { + try encode(value) + } + else { + try encodeNil() + } } mutating func encode( - _ value: Transformer.Target, + _ value: Transformer.Target?, using transformer: Transformer ) throws where Transformer.Source == Int64 { - try encode(transformer.encode(value)) + if let value = try value.map({ try transformer.encode($0) }) { + try encode(value) + } + else { + try encodeNil() + } } mutating func encode( - _ value: Transformer.Target, + _ value: Transformer.Target?, using transformer: Transformer ) throws where Transformer.Source == UInt8 { - try encode(transformer.encode(value)) + if let value = try value.map({ try transformer.encode($0) }) { + try encode(value) + } + else { + try encodeNil() + } } mutating func encode( - _ value: Transformer.Target, + _ value: Transformer.Target?, using transformer: Transformer ) throws where Transformer.Source == UInt16 { - try encode(transformer.encode(value)) + if let value = try value.map({ try transformer.encode($0) }) { + try encode(value) + } + else { + try encodeNil() + } } mutating func encode( - _ value: Transformer.Target, + _ value: Transformer.Target?, using transformer: Transformer ) throws where Transformer.Source == UInt32 { - try encode(transformer.encode(value)) + if let value = try value.map({ try transformer.encode($0) }) { + try encode(value) + } + else { + try encodeNil() + } } mutating func encode( - _ value: Transformer.Target, + _ value: Transformer.Target?, using transformer: Transformer ) throws where Transformer.Source == UInt64 { - try encode(transformer.encode(value)) + if let value = try value.map({ try transformer.encode($0) }) { + try encode(value) + } + else { + try encodeNil() + } } mutating func encode( - _ value: Transformer.Target, + _ value: Transformer.Target?, using transformer: Transformer ) throws where Transformer.Source == Float { - try encode(transformer.encode(value)) + if let value = try value.map({ try transformer.encode($0) }) { + try encode(value) + } + else { + try encodeNil() + } } mutating func encode( - _ value: Transformer.Target, + _ value: Transformer.Target?, using transformer: Transformer ) throws where Transformer.Source == Double { - try encode(transformer.encode(value)) + if let value = try value.map({ try transformer.encode($0) }) { + try encode(value) + } + else { + try encodeNil() + } } mutating func encode( - _ value: Transformer.Target, + _ value: Transformer.Target?, using transformer: Transformer ) throws where Transformer.Source: Encodable { - try encode(transformer.encode(value)) + if let value = try value.map({ try transformer.encode($0) }) { + try encode(value) + } + else { + try encodeNil() + } } mutating func encode( @@ -827,6 +934,13 @@ public extension UnkeyedEncodingContainer { try encode(contentsOf: values.map { try transformer.encode($0) }) } + mutating func encode( + contentsOf values: [Transformer.Target], + using transformer: Transformer + ) throws where Transformer.Source == Int { + try encode(contentsOf: values.map { try transformer.encode($0) }) + } + mutating func encode( contentsOf values: [Transformer.Target], using transformer: Transformer @@ -855,6 +969,13 @@ public extension UnkeyedEncodingContainer { try encode(contentsOf: values.map { try transformer.encode($0) }) } + mutating func encode( + contentsOf values: [Transformer.Target], + using transformer: Transformer + ) throws where Transformer.Source == UInt { + try encode(contentsOf: values.map { try transformer.encode($0) }) + } + mutating func encode( contentsOf values: [Transformer.Target], using transformer: Transformer @@ -917,11 +1038,18 @@ public extension SingleValueEncodingContainer { mutating func encode( _ value: Transformer.Target, - transformer: Transformer + using transformer: Transformer ) throws where Transformer.Source == String { try encode(transformer.encode(value)) } + mutating func encode( + _ value: Transformer.Target, + using transformer: Transformer + ) throws where Transformer.Source == Int { + try encode(transformer.encode(value)) + } + mutating func encode( _ value: Transformer.Target, using transformer: Transformer @@ -950,6 +1078,13 @@ public extension SingleValueEncodingContainer { try encode(transformer.encode(value)) } + mutating func encode( + _ value: Transformer.Target, + using transformer: Transformer + ) throws where Transformer.Source == UInt { + try encode(transformer.encode(value)) + } + mutating func encode( _ value: Transformer.Target, using transformer: Transformer diff --git a/Sources/PotentCodables/ZonedDate.swift b/Sources/PotentCodables/ZonedDate.swift index 3ea341062..bf99f1292 100644 --- a/Sources/PotentCodables/ZonedDate.swift +++ b/Sources/PotentCodables/ZonedDate.swift @@ -36,12 +36,17 @@ public struct ZonedDate: Equatable, Hashable, Codable { self.timeZone = timeZone } -} + private static let iso8601Parser = ISO8601FlexibleDateFormatter() -extension ZonedDate: CustomStringConvertible { + public init?(iso8601Encoded string: String) { + guard let zonedDate = Self.iso8601Parser.date(from: string) else { + return nil + } + self.date = zonedDate.date + self.timeZone = zonedDate.timeZone + } - /// ISO8601 formatted date/time string. - public var description: String { + public func iso8601EncodedString() -> String { return Formatters.for(timeZone: timeZone).string(from: date) } @@ -49,10 +54,10 @@ extension ZonedDate: CustomStringConvertible { private enum Formatters { - private static var formatters: [TimeZone: DateFormatter] = [:] + private static var formatters: [TimeZone: ISO8601DateFormatter] = [:] private static let formattersLock = NSLock() - static func `for`(timeZone: TimeZone) -> DateFormatter { + static func `for`(timeZone: TimeZone) -> ISO8601DateFormatter { formattersLock.lock() defer { formattersLock.unlock() } @@ -60,11 +65,9 @@ private enum Formatters { return found } - let formatter = DateFormatter() - formatter.calendar = Calendar(identifier: .iso8601) - formatter.locale = Locale(identifier: "en_US_POSIX") + let formatter = ISO8601DateFormatter() + formatter.formatOptions = [.withInternetDateTime, .withFractionalSeconds, .withTimeZone] formatter.timeZone = timeZone - formatter.dateFormat = "yyyy-MM-dd HH:mm:ss.SSS XXXXX" formatters[timeZone] = formatter @@ -72,3 +75,79 @@ private enum Formatters { } } + +private struct ISO8601FlexibleDateFormatter { + + private let noSuffixes: ISO8601DateFormatter + private let zoneSuffix: ISO8601DateFormatter + private let fractionalSecondsSuffix: ISO8601DateFormatter + private let zoneAndFractionalSecondsSuffixes: ISO8601DateFormatter + + public init() { + noSuffixes = { + let formatter = ISO8601DateFormatter() + formatter.formatOptions = [.withInternetDateTime] + formatter.timeZone = .utc + return formatter + }() + + zoneSuffix = { + let formatter = ISO8601DateFormatter() + formatter.formatOptions = [.withInternetDateTime, .withTimeZone] + formatter.timeZone = .utc + return formatter + }() + + fractionalSecondsSuffix = { + let formatter = ISO8601DateFormatter() + formatter.formatOptions = [.withInternetDateTime, .withFractionalSeconds] + formatter.timeZone = .utc + return formatter + }() + + zoneAndFractionalSecondsSuffixes = { + let formatter = ISO8601DateFormatter() + formatter.formatOptions = [.withInternetDateTime, .withFractionalSeconds, .withTimeZone] + formatter.timeZone = .utc + return formatter + }() + } + + public func date(from string: String) -> ZonedDate? { + let parsedDate: Date? + let hasSeconds: Bool + let hasZone: Bool + let timeZone: TimeZone + if let timeStartIndex = string.firstIndex(of: "T") { + let time = string[timeStartIndex...] + let zoneStartIndex = time.firstIndex { char in char == "-" || char == "+" || char == "Z" } ?? time.endIndex + let timeWithoutZone = time[time.startIndex ..< zoneStartIndex] + hasSeconds = timeWithoutZone.contains(".") + hasZone = zoneStartIndex != time.endIndex + timeZone = TimeZone.timeZone(from: String(time[zoneStartIndex...])) ?? .current + } + else { + hasSeconds = false + hasZone = false + timeZone = .current + } + + if hasSeconds && hasZone { + parsedDate = zoneAndFractionalSecondsSuffixes.date(from: string) + } + else if hasSeconds { + parsedDate = fractionalSecondsSuffix.date(from: string) + } + else if hasZone { + parsedDate = zoneSuffix.date(from: string) + } + else { + parsedDate = noSuffixes.date(from: string) + } + if let parsedDate = parsedDate { + return ZonedDate(date: parsedDate, timeZone: timeZone) + } + return nil + } + +} diff --git a/Sources/PotentJSON/JSON.swift b/Sources/PotentJSON/JSON.swift index 59d47efa2..80c66f4b7 100644 --- a/Sources/PotentJSON/JSON.swift +++ b/Sources/PotentJSON/JSON.swift @@ -8,6 +8,7 @@ // Distributed under the MIT License, See LICENSE for details. // +import BigInt import Foundation import OrderedCollections import PotentCodables @@ -34,13 +35,14 @@ public enum JSON { enum Error: Swift.Error { case unsupportedType + case invalidNumber } public struct Number: Equatable, Hashable, Codable { - public let value: String - public let isInteger: Bool - public let isNegative: Bool + public var value: String + public var isInteger: Bool + public var isNegative: Bool public init(_ value: String, isInteger: Bool, isNegative: Bool) { self.value = value @@ -49,23 +51,27 @@ public enum JSON { } public init(_ value: String) { - self.value = value - isInteger = value.allSatisfy(\.isNumber) - isNegative = value.hasPrefix("-") + self.init(value, isInteger: value.allSatisfy { $0.isNumber || $0 == "-" }, isNegative: value.hasPrefix("-")) } - public init(_ value: Double) { - self.value = value.description + public init(_ value: T) { + self.value = String(describing: value) isInteger = false isNegative = value < 0 } - public init(_ value: Int) { - self.value = value.description + public init(_ value: T) { + self.value = String(value) isInteger = true isNegative = value < 0 } + public init(_ value: T) { + self.value = String(value) + isInteger = true + isNegative = false + } + public var integerValue: Int? { guard isInteger else { return nil } return Int(value) @@ -87,17 +93,40 @@ public enum JSON { public var numberValue: Any? { if isInteger { if isNegative { - if MemoryLayout.size == 4 { - return Int(value) ?? Int64(value) ?? Decimal(string: value) + guard let int = BigInt(value) else { + return nil + } + switch int.bitWidth { + case 0 ... (MemoryLayout.size * 8): + return Int(int) + case 0 ... (MemoryLayout.size * 8): + return Int64(int) + default: + return BigInt(value) } - return Int(value) ?? Decimal(string: value) } - if MemoryLayout.size == 4 { - return Int(value) ?? Int64(value) ?? UInt(value) ?? UInt64(value) ?? Decimal(string: value) + else { + guard let int = BigUInt(value) else { + return nil + } + switch int.bitWidth { + case 0 ... (MemoryLayout.size * 8) - 1: + return Int(int) + case 0 ... (MemoryLayout.size * 8): + return UInt(int) + case 0 ... (MemoryLayout.size * 8) - 1: + return Int64(int) + case 0 ... (MemoryLayout.size * 8): + return UInt64(int) + default: + return BigInt(value) + } } - return Int(value) ?? UInt(value) ?? Decimal(string: value) } - return Double(value) ?? Decimal(string: value) + guard let double = Double(value) else { + return nil + } + return double } } @@ -112,11 +141,25 @@ public enum JSON { case array(Array) case object(Object) - public var isNull: Bool { - if case .null = self { - return true + public subscript(dynamicMember member: String) -> JSON? { + if let object = objectValue { + return object[member] } - return false + return nil + } + + public subscript(index: Int) -> JSON? { + if let array = arrayValue, index < array.count { + return array[index] + } + return nil + } + + public subscript(key: String) -> JSON? { + if let object = objectValue { + return object[key] + } + return nil } public var stringValue: String? { @@ -164,25 +207,20 @@ public enum JSON { return value } - public subscript(dynamicMember member: String) -> JSON? { - if let object = objectValue { - return object[member] - } - return nil - } +} - public subscript(index: Int) -> JSON? { - if let array = arrayValue { - return index < array.count ? array[index] : nil - } - return nil - } - public subscript(key: String) -> JSON? { - if let object = objectValue { - return object[key] +// MARK: Conformances + +extension JSON: Equatable {} +extension JSON: Hashable {} +extension JSON: Value { + + public var isNull: Bool { + if case .null = self { + return true } - return nil + return false } } @@ -204,9 +242,10 @@ extension JSON: CustomStringConvertible { } -extension JSON: Equatable {} -extension JSON: Hashable {} -extension JSON: Value { + +// MARK: Wrapping + +extension JSON { public var unwrapped: Any? { switch self { @@ -222,9 +261,7 @@ extension JSON: Value { } -/** - * Literal support - **/ +// MARK: Literals extension JSON: ExpressibleByNilLiteral, ExpressibleByBooleanLiteral, ExpressibleByStringLiteral, ExpressibleByIntegerLiteral, ExpressibleByFloatLiteral, ExpressibleByArrayLiteral, @@ -265,6 +302,7 @@ extension JSON: ExpressibleByNilLiteral, ExpressibleByBooleanLiteral, Expressibl } + extension JSON.Number: ExpressibleByFloatLiteral, ExpressibleByIntegerLiteral, ExpressibleByStringLiteral { public init(stringLiteral value: String) { @@ -281,8 +319,9 @@ extension JSON.Number: ExpressibleByFloatLiteral, ExpressibleByIntegerLiteral, E } -/// Make encoders/decoders available in JSON namespace -/// + +// Make encoders/decoders available in AnyValue namespace + public extension JSON { typealias Encoder = JSONEncoder diff --git a/Sources/PotentJSON/JSONDecoder.swift b/Sources/PotentJSON/JSONDecoder.swift index 1f658a122..b6ae8af03 100644 --- a/Sources/PotentJSON/JSONDecoder.swift +++ b/Sources/PotentJSON/JSONDecoder.swift @@ -8,7 +8,9 @@ // Distributed under the MIT License, See LICENSE for details. // +import BigInt import Foundation +import OrderedCollections import PotentCodables @@ -92,7 +94,6 @@ public class JSONDecoder: ValueDecoder, DecodesFromS public struct JSONDecoderTransform: InternalDecoderTransform, InternalValueDeserializer, InternalValueParser { public typealias Value = JSON - public typealias Decoder = InternalValueDecoder public typealias State = Void public static let nilValue = JSON.null @@ -106,8 +107,51 @@ public struct JSONDecoderTransform: InternalDecoderTransform, InternalValueDeser public let userInfo: [CodingUserInfoKey: Any] } + public static func intercepts(_ type: Decodable.Type) -> Bool { + return type == Date.self || type == NSDate.self + || type == Data.self || type == NSData.self + || type == URL.self || type == NSURL.self + || type == UUID.self || type == NSUUID.self + || type == Float16.self + || type == Decimal.self || type == NSDecimalNumber.self + || type == BigInt.self + || type == BigUInt.self + || type == AnyValue.self + } + + public static func unbox(_ value: JSON, interceptedType: Decodable.Type, decoder: IVD) throws -> Any? { + if interceptedType == Date.self || interceptedType == NSDate.self { + return try unbox(value, as: Date.self, decoder: decoder) + } + else if interceptedType == Data.self || interceptedType == NSData.self { + return try unbox(value, as: Data.self, decoder: decoder) + } + else if interceptedType == URL.self || interceptedType == NSURL.self { + return try unbox(value, as: URL.self, decoder: decoder) + } + else if interceptedType == UUID.self || interceptedType == NSUUID.self { + return try unbox(value, as: UUID.self, decoder: decoder) + } + else if interceptedType == Float16.self { + return try unbox(value, as: Float16.self, decoder: decoder) + } + else if interceptedType == Decimal.self || interceptedType == NSDecimalNumber.self { + return try unbox(value, as: Decimal.self, decoder: decoder) + } + else if interceptedType == BigInt.self { + return try unbox(value, as: BigInt.self, decoder: decoder) + } + else if interceptedType == BigUInt.self { + return try unbox(value, as: BigUInt.self, decoder: decoder) + } + else if interceptedType == AnyValue.self { + return try unbox(value, as: AnyValue.self, decoder: decoder) + } + fatalError("type not valid for intercept") + } + /// Returns the given value unboxed from a container. - public static func unbox(_ value: JSON, as type: Bool.Type, decoder: Decoder) throws -> Bool? { + public static func unbox(_ value: JSON, as type: Bool.Type, decoder: IVD) throws -> Bool? { switch value { case .bool(let value): return value case .null: return nil @@ -137,7 +181,7 @@ public struct JSONDecoderTransform: InternalDecoderTransform, InternalValueDeser return result } - public static func unbox(_ value: JSON, as type: Int.Type, decoder: Decoder) throws -> Int? { + public static func unbox(_ value: JSON, as type: Int.Type, decoder: IVD) throws -> Int? { switch value { case .number(let number): return try coerce(number, at: decoder.codingPath) case .null: return nil @@ -146,7 +190,7 @@ public struct JSONDecoderTransform: InternalDecoderTransform, InternalValueDeser } } - public static func unbox(_ value: JSON, as type: Int8.Type, decoder: Decoder) throws -> Int8? { + public static func unbox(_ value: JSON, as type: Int8.Type, decoder: IVD) throws -> Int8? { switch value { case .number(let number): return try coerce(number, at: decoder.codingPath) case .null: return nil @@ -155,7 +199,7 @@ public struct JSONDecoderTransform: InternalDecoderTransform, InternalValueDeser } } - public static func unbox(_ value: JSON, as type: Int16.Type, decoder: Decoder) throws -> Int16? { + public static func unbox(_ value: JSON, as type: Int16.Type, decoder: IVD) throws -> Int16? { switch value { case .number(let number): return try coerce(number, at: decoder.codingPath) case .null: return nil @@ -164,7 +208,7 @@ public struct JSONDecoderTransform: InternalDecoderTransform, InternalValueDeser } } - public static func unbox(_ value: JSON, as type: Int32.Type, decoder: Decoder) throws -> Int32? { + public static func unbox(_ value: JSON, as type: Int32.Type, decoder: IVD) throws -> Int32? { switch value { case .number(let number): return try coerce(number, at: decoder.codingPath) case .null: return nil @@ -173,7 +217,7 @@ public struct JSONDecoderTransform: InternalDecoderTransform, InternalValueDeser } } - public static func unbox(_ value: JSON, as type: Int64.Type, decoder: Decoder) throws -> Int64? { + public static func unbox(_ value: JSON, as type: Int64.Type, decoder: IVD) throws -> Int64? { switch value { case .number(let number): return try coerce(number, at: decoder.codingPath) case .null: return nil @@ -182,7 +226,7 @@ public struct JSONDecoderTransform: InternalDecoderTransform, InternalValueDeser } } - public static func unbox(_ value: JSON, as type: UInt.Type, decoder: Decoder) throws -> UInt? { + public static func unbox(_ value: JSON, as type: UInt.Type, decoder: IVD) throws -> UInt? { switch value { case .number(let number): return try coerce(number, at: decoder.codingPath) case .null: return nil @@ -191,7 +235,7 @@ public struct JSONDecoderTransform: InternalDecoderTransform, InternalValueDeser } } - public static func unbox(_ value: JSON, as type: UInt8.Type, decoder: Decoder) throws -> UInt8? { + public static func unbox(_ value: JSON, as type: UInt8.Type, decoder: IVD) throws -> UInt8? { switch value { case .number(let number): return try coerce(number, at: decoder.codingPath) case .null: return nil @@ -200,7 +244,7 @@ public struct JSONDecoderTransform: InternalDecoderTransform, InternalValueDeser } } - public static func unbox(_ value: JSON, as type: UInt16.Type, decoder: Decoder) throws -> UInt16? { + public static func unbox(_ value: JSON, as type: UInt16.Type, decoder: IVD) throws -> UInt16? { switch value { case .number(let number): return try coerce(number, at: decoder.codingPath) case .null: return nil @@ -209,7 +253,7 @@ public struct JSONDecoderTransform: InternalDecoderTransform, InternalValueDeser } } - public static func unbox(_ value: JSON, as type: UInt32.Type, decoder: Decoder) throws -> UInt32? { + public static func unbox(_ value: JSON, as type: UInt32.Type, decoder: IVD) throws -> UInt32? { switch value { case .number(let number): return try coerce(number, at: decoder.codingPath) case .null: return nil @@ -218,7 +262,7 @@ public struct JSONDecoderTransform: InternalDecoderTransform, InternalValueDeser } } - public static func unbox(_ value: JSON, as type: UInt64.Type, decoder: Decoder) throws -> UInt64? { + public static func unbox(_ value: JSON, as type: UInt64.Type, decoder: IVD) throws -> UInt64? { switch value { case .number(let number): return try coerce(number, at: decoder.codingPath) case .null: return nil @@ -227,54 +271,125 @@ public struct JSONDecoderTransform: InternalDecoderTransform, InternalValueDeser } } - public static func unbox(_ value: JSON, as type: Float.Type, decoder: Decoder) throws -> Float? { + public static func unbox(_ value: JSON, as type: BigInt.Type, decoder: IVD) throws -> BigInt? { switch value { - case .number(let number): return try coerce(number, at: decoder.codingPath) + case .number(let number): + guard let int = BigInt(number.value) else { + throw DecodingError.dataCorruptedError(in: decoder, debugDescription: "Invalid integer literal") + } + return int case .null: return nil case let json: throw DecodingError.typeMismatch(at: decoder.codingPath, expectation: type, reality: json) } } - public static func unbox(_ value: JSON, as type: Double.Type, decoder: Decoder) throws -> Double? { + public static func unbox(_ value: JSON, as type: BigUInt.Type, decoder: IVD) throws -> BigUInt? { switch value { - case .number(let number): return try coerce(number, at: decoder.codingPath) + case .number(let number): + guard let int = BigUInt(number.value) else { + throw DecodingError.dataCorruptedError(in: decoder, debugDescription: "Invalid unsigned integer literal") + } + return int case .null: return nil case let json: throw DecodingError.typeMismatch(at: decoder.codingPath, expectation: type, reality: json) } } - public static func unbox(_ value: JSON, as type: Decimal.Type, decoder: Decoder) throws -> Decimal? { + public static func unbox(_ value: JSON, as type: Float16.Type, decoder: IVD) throws -> Float16? { + switch value { + case .number(let number): return try coerce(number, at: decoder.codingPath) + case .string(let string): + if let mapped = decoder.options.mapNonConformingFloatDecodingStrategyStrings(Float16.self, value: string) { + return mapped + } + case .null: return nil + default: + break + } + throw DecodingError.typeMismatch(at: decoder.codingPath, expectation: type, reality: value) + } + + public static func unbox(_ value: JSON, as type: Float.Type, decoder: IVD) throws -> Float? { + switch value { + case .number(let number): return try coerce(number, at: decoder.codingPath) + case .string(let string): + if let mapped = decoder.options.mapNonConformingFloatDecodingStrategyStrings(Float.self, value: string) { + return mapped + } + case .null: return nil + default: + break + } + throw DecodingError.typeMismatch(at: decoder.codingPath, expectation: type, reality: value) + } + + public static func unbox(_ value: JSON, as type: Double.Type, decoder: IVD) throws -> Double? { + switch value { + case .number(let number): return try coerce(number, at: decoder.codingPath) + case .string(let string): + if let mapped = decoder.options.mapNonConformingFloatDecodingStrategyStrings(Double.self, value: string) { + return mapped + } + case .null: return nil + default: + break + } + throw DecodingError.typeMismatch(at: decoder.codingPath, expectation: type, reality: value) + } + + public static func unbox(_ value: JSON, as type: Decimal.Type, decoder: IVD) throws -> Decimal? { switch value { case .number(let number): return Decimal(string: number.value) + case .string(let string): + if let (_, _, nanStr) = decoder.options.nonConformingFloatDecodingStrategyStrings, string == nanStr { + return Decimal.nan + } + case .null: return nil + default: + break + } + throw DecodingError.typeMismatch(at: decoder.codingPath, expectation: type, reality: value) + } + + public static func unbox(_ value: JSON, as type: String.Type, decoder: IVD) throws -> String? { + switch value { + case .string(let string): + return string case .null: return nil case let json: throw DecodingError.typeMismatch(at: decoder.codingPath, expectation: type, reality: json) } } - public static func unbox(_ value: JSON, as type: String.Type, decoder: Decoder) throws -> String? { + public static func unbox(_ value: JSON, as type: UUID.Type, decoder: IVD) throws -> UUID? { switch value { - case .null: return nil case .string(let string): - return string + guard let uuid = UUID(uuidString: string) else { + throw DecodingError.dataCorruptedError(in: decoder, debugDescription: "Expected valid UUID string") + } + return uuid + case .null: return nil case let json: throw DecodingError.typeMismatch(at: decoder.codingPath, expectation: type, reality: json) } } - public static func unbox(_ value: JSON, as type: UUID.Type, decoder: Decoder) throws -> UUID? { + public static func unbox(_ value: JSON, as type: URL.Type, decoder: IVD) throws -> URL? { switch value { case .string(let string): - return UUID(uuidString: string) + guard let url = URL(string: string) else { + throw DecodingError.dataCorruptedError(in: decoder, debugDescription: "Expected valid URL string") + } + return url case .null: return nil case let json: throw DecodingError.typeMismatch(at: decoder.codingPath, expectation: type, reality: json) } } - public static func unbox(_ value: JSON, as type: Date.Type, decoder: Decoder) throws -> Date? { + public static func unbox(_ value: JSON, as type: Date.Type, decoder: IVD) throws -> Date? { guard !value.isNull else { return nil } switch decoder.options.dateDecodingStrategy { @@ -291,7 +406,7 @@ public struct JSONDecoderTransform: InternalDecoderTransform, InternalValueDeser case .iso8601: let string = try unbox(value, as: String.self, decoder: decoder)! - guard let zonedDate = _iso8601Formatter.date(from: string) else { + guard let zonedDate = ZonedDate(iso8601Encoded: string) else { throw DecodingError.dataCorrupted(.init( codingPath: decoder.codingPath, debugDescription: "Expected date string to be ISO8601-formatted." @@ -314,7 +429,7 @@ public struct JSONDecoderTransform: InternalDecoderTransform, InternalValueDeser } } - public static func unbox(_ value: JSON, as type: Data.Type, decoder: Decoder) throws -> Data? { + public static func unbox(_ value: JSON, as type: Data.Type, decoder: IVD) throws -> Data? { guard !value.isNull else { return nil } switch decoder.options.dataDecodingStrategy { @@ -340,14 +455,46 @@ public struct JSONDecoderTransform: InternalDecoderTransform, InternalValueDeser } } - public static func valueToUnkeyedValues(_ value: JSON, decoder: Decoder) throws -> [JSON]? { + public static func unbox(_ value: JSON, as type: AnyValue.Type, decoder: IVD) throws -> AnyValue { + switch value { + case .null: return .nil + case .bool(let value): return .bool(value) + case .string(let value): return .string(value) + case .number(let value): + switch value.numberValue { + case .none: return .nil + case let int as Int: + return MemoryLayout.size == 4 ? .int32(Int32(int)) : .int64(Int64(int)) + case let uint as UInt: + return MemoryLayout.size == 4 ? .uint32(UInt32(uint)) : .uint64(UInt64(uint)) + case let int as Int64: + return .int64(Int64(int)) + case let uint as UInt64: + return .uint64(UInt64(uint)) + case let int as BigInt: + return .integer(int) + case let double as Double: + return .double(double) + default: + fatalError("numberValue returned unsupported value") + } + case .array(let value): + return .array(try value.map { try unbox($0, as: AnyValue.self, decoder: decoder) }) + case .object(let value): + return .dictionary(AnyValue.AnyDictionary(uniqueKeysWithValues: try value.map { key, value in + (.string(key), try unbox(value, as: AnyValue.self, decoder: decoder)) + })) + } + } + + public static func valueToUnkeyedValues(_ value: JSON, decoder: IVD) throws -> UnkeyedValues? { guard case .array(let array) = value else { return nil } return array } - public static func valueToKeyedValues(_ value: JSON, decoder: Decoder) throws -> [String: JSON]? { + public static func valueToKeyedValues(_ value: JSON, decoder: IVD) throws -> KeyedValues? { guard case .object(let dict) = value else { return nil } - return Dictionary(uniqueKeysWithValues: Array(dict.elements)) + return dict } public static func value(from data: Data, options: Options) throws -> JSON { @@ -361,9 +508,6 @@ public struct JSONDecoderTransform: InternalDecoderTransform, InternalValueDeser } -private let _iso8601Formatter = SuffixedDateFormatter.optionalFractionalSeconds(basePattern: "yyyy-MM-dd'T'HH:mm:ss") - - #if canImport(Combine) import Combine @@ -373,3 +517,33 @@ private let _iso8601Formatter = SuffixedDateFormatter.optionalFractionalSeconds( } #endif + + +extension JSONDecoderTransform.Options { + + var nonConformingFloatDecodingStrategyStrings: (posInf: String, negInf: String, nan: String)? { + if case .convertFromString(positiveInfinity: let posInfStr, negativeInfinity: let negInfStr, nan: let nanStr) + = nonConformingFloatDecodingStrategy { + return (posInfStr, negInfStr, nanStr) + } + return nil + } + + func mapNonConformingFloatDecodingStrategyStrings( + _ type: F.Type, value: String, posInf: F? = +.infinity, negInf: F? = -.infinity, nan: F? = .nan + ) -> F? { + if let (posInfStr, negInfStr, nanStr) = nonConformingFloatDecodingStrategyStrings { + if value == posInfStr { + return posInf + } + else if value == negInfStr { + return negInf + } + else if value == nanStr { + return nan + } + } + return nil + } + +} diff --git a/Sources/PotentJSON/JSONEncoder.swift b/Sources/PotentJSON/JSONEncoder.swift index 3aa3c1f29..5bb407546 100644 --- a/Sources/PotentJSON/JSONEncoder.swift +++ b/Sources/PotentJSON/JSONEncoder.swift @@ -8,6 +8,7 @@ // Distributed under the MIT License, See LICENSE for details. // +import BigInt import Foundation import PotentCodables @@ -36,6 +37,9 @@ public class JSONEncoder: ValueEncoder, EncodesToStr /// Produce JSON with dictionary keys sorted in lexicographic order. public static let sortedKeys = OutputFormatting(rawValue: 1 << 1) + + /// Produce JSON with slashes escaped. + public static let escapeSlashes = OutputFormatting(rawValue: 1 << 2) } /// The strategy to use for encoding `Date` values. @@ -123,7 +127,6 @@ public class JSONEncoder: ValueEncoder, EncodesToStr public struct JSONEncoderTransform: InternalEncoderTransform, InternalValueSerializer, InternalValueStringifier { public typealias Value = JSON - public typealias Encoder = InternalValueEncoder public typealias State = Void public static var emptyKeyedContainer = JSON.object([:]) @@ -138,23 +141,90 @@ public struct JSONEncoderTransform: InternalEncoderTransform, InternalValueSeria public let userInfo: [CodingUserInfoKey: Any] } - public static func boxNil(encoder: Encoder) throws -> JSON { return .null } - public static func box(_ value: Bool, encoder: Encoder) throws -> JSON { return .bool(value) } - public static func box(_ value: Int, encoder: Encoder) throws -> JSON { return .number(.init(value.description)) } - public static func box(_ value: Int8, encoder: Encoder) throws -> JSON { return .number(.init(value.description)) } - public static func box(_ value: Int16, encoder: Encoder) throws -> JSON { return .number(.init(value.description)) } - public static func box(_ value: Int32, encoder: Encoder) throws -> JSON { return .number(.init(value.description)) } - public static func box(_ value: Int64, encoder: Encoder) throws -> JSON { return .number(.init(value.description)) } - public static func box(_ value: UInt, encoder: Encoder) throws -> JSON { return .number(.init(value.description)) } - public static func box(_ value: UInt8, encoder: Encoder) throws -> JSON { return .number(.init(value.description)) } - public static func box(_ value: UInt16, encoder: Encoder) throws -> JSON { return .number(.init(value.description)) } - public static func box(_ value: UInt32, encoder: Encoder) throws -> JSON { return .number(.init(value.description)) } - public static func box(_ value: UInt64, encoder: Encoder) throws -> JSON { return .number(.init(value.description)) } - public static func box(_ value: String, encoder: Encoder) throws -> JSON { return .string(value) } - public static func box(_ value: URL, encoder: Encoder) throws -> JSON { return .string(value.absoluteString) } - public static func box(_ value: UUID, encoder: Encoder) throws -> JSON { return .string(value.uuidString) } - - public static func box(_ float: Float, encoder: Encoder) throws -> JSON { + public static func intercepts(_ type: Encodable.Type) -> Bool { + return type == Date.self || type == NSDate.self + || type == Data.self || type == NSData.self + || type == URL.self || type == NSURL.self + || type == UUID.self || type == NSUUID.self + || type == Float16.self + || type == Decimal.self || type == NSDecimalNumber.self + || type == BigInt.self + || type == BigUInt.self + || type == AnyValue.self + } + + public static func box(_ value: Any, interceptedType: Encodable.Type, encoder: IVE) throws -> JSON { + if let value = value as? Date { + return try box(value, encoder: encoder) + } + else if let value = value as? Data { + return try box(value, encoder: encoder) + } + else if let value = value as? URL { + return try box(value, encoder: encoder) + } + else if let value = value as? UUID { + return try box(value, encoder: encoder) + } + else if let value = value as? Float16 { + return try box(value, encoder: encoder) + } + else if let value = value as? Decimal { + return try box(value, encoder: encoder) + } + else if let value = value as? BigInt { + return try box(value, encoder: encoder) + } + else if let value = value as? BigUInt { + return try box(value, encoder: encoder) + } + else if let value = value as? AnyValue { + return try box(value, encoder: encoder) + } + fatalError("type not valid for intercept") + } + + public static func boxNil(encoder: IVE) throws -> JSON { return .null } + public static func box(_ value: Bool, encoder: IVE) throws -> JSON { return .bool(value) } + public static func box(_ value: Int, encoder: IVE) throws -> JSON { return .number(.init(value.description)) } + public static func box(_ value: Int8, encoder: IVE) throws -> JSON { return .number(.init(value.description)) } + public static func box(_ value: Int16, encoder: IVE) throws -> JSON { return .number(.init(value.description)) } + public static func box(_ value: Int32, encoder: IVE) throws -> JSON { return .number(.init(value.description)) } + public static func box(_ value: Int64, encoder: IVE) throws -> JSON { return .number(.init(value.description)) } + public static func box(_ value: UInt, encoder: IVE) throws -> JSON { return .number(.init(value.description)) } + public static func box(_ value: UInt8, encoder: IVE) throws -> JSON { return .number(.init(value.description)) } + public static func box(_ value: UInt16, encoder: IVE) throws -> JSON { return .number(.init(value.description)) } + public static func box(_ value: UInt32, encoder: IVE) throws -> JSON { return .number(.init(value.description)) } + public static func box(_ value: UInt64, encoder: IVE) throws -> JSON { return .number(.init(value.description)) } + public static func box(_ value: String, encoder: IVE) throws -> JSON { return .string(value) } + public static func box(_ value: URL, encoder: IVE) throws -> JSON { return .string(value.absoluteString) } + public static func box(_ value: UUID, encoder: IVE) throws -> JSON { return .string(value.uuidString) } + + public static func box(_ float: Float16, encoder: IVE) throws -> JSON { + guard !float.isInfinite, !float.isNaN else { + guard case .convertToString( + let posInfString, + let negInfString, + let nanString + ) = encoder.options.nonConformingFloatEncodingStrategy else { + throw EncodingError.invalidFloatingPointValue(float, at: encoder.codingPath) + } + + if float == Float16.infinity { + return .string(posInfString) + } + else if float == -Float16.infinity { + return .string(negInfString) + } + else { + return .string(nanString) + } + } + + return .number(.init(float)) + } + + public static func box(_ float: Float, encoder: IVE) throws -> JSON { guard !float.isInfinite, !float.isNaN else { guard case .convertToString( let posInfString, @@ -175,10 +245,10 @@ public struct JSONEncoderTransform: InternalEncoderTransform, InternalValueSeria } } - return .number(.init(float.description)) + return .number(.init(float)) } - public static func box(_ double: Double, encoder: Encoder) throws -> JSON { + public static func box(_ double: Double, encoder: IVE) throws -> JSON { guard !double.isInfinite, !double.isNaN else { guard case .convertToString( let posInfString, @@ -199,10 +269,10 @@ public struct JSONEncoderTransform: InternalEncoderTransform, InternalValueSeria } } - return .number(.init(double.description)) + return .number(.init(double)) } - public static func box(_ decimal: Decimal, encoder: Encoder) throws -> JSON { + public static func box(_ decimal: Decimal, encoder: IVE) throws -> JSON { guard !decimal.isInfinite, !decimal.isNaN else { guard case .convertToString( let posInfString, @@ -223,29 +293,34 @@ public struct JSONEncoderTransform: InternalEncoderTransform, InternalValueSeria } } - var decimal = decimal - let rep = NSDecimalString(&decimal, NSLocale.system) + return .number(.init(decimal.description)) + } + + public static func box(_ value: BigInt, encoder: IVE) throws -> JSON { + return .number(.init(value.description)) + } - return .number(.init(rep.description)) + public static func box(_ value: BigUInt, encoder: IVE) throws -> JSON { + return .number(.init(value.description)) } - public static func box(_ value: Data, encoder: Encoder) throws -> JSON { + public static func box(_ value: Data, encoder: IVE) throws -> JSON { switch encoder.options.dataEncodingStrategy { case .deferredToData: - return try encoder.subEncode { try value.encode(to: $0) } ?? emptyKeyedContainer + return try encoder.subEncode { try value.encode(to: $0.encoder) } ?? .null case .base64: return .string(value.base64EncodedString()) case .custom(let closure): - return try encoder.subEncode { try closure(value, $0) } ?? emptyKeyedContainer + return try encoder.subEncode { try closure(value, $0.encoder) } ?? .null } } - public static func box(_ value: Date, encoder: Encoder) throws -> JSON { + public static func box(_ value: Date, encoder: IVE) throws -> JSON { switch encoder.options.dateEncodingStrategy { case .deferredToDate: - return try encoder.subEncode { try value.encode(to: $0) } ?? emptyKeyedContainer + return try encoder.subEncode { try value.encode(to: $0.encoder) } ?? .null case .secondsSince1970: return .number(.init(value.timeIntervalSince1970.description)) @@ -254,26 +329,90 @@ public struct JSONEncoderTransform: InternalEncoderTransform, InternalValueSeria return .number(.init((1000.0 * value.timeIntervalSince1970).description)) case .iso8601: - return .string(_iso8601Formatter.string(from: value)) + return .string(ZonedDate(date: value, timeZone: .utc).iso8601EncodedString()) case .formatted(let formatter): return .string(formatter.string(from: value)) case .custom(let closure): - return try encoder.subEncode { try closure(value, $0) } ?? emptyKeyedContainer + return try encoder.subEncode { try closure(value, $0.encoder) } ?? .null } } - public static func unkeyedValuesToValue(_ values: [JSON], encoder: Encoder) -> JSON { + public static func box(_ value: AnyValue, encoder: IVE) throws -> JSON { + switch value { + case .nil: + return .null + case .bool(let value): + return .bool(value) + case .int8(let value): + return .number(JSON.Number(value)) + case .int16(let value): + return .number(JSON.Number(value)) + case .int32(let value): + return .number(JSON.Number(value)) + case .int64(let value): + return .number(JSON.Number(value)) + case .integer(let value): + return .number(JSON.Number(value)) + case .uint8(let value): + return .number(JSON.Number(value)) + case .uint16(let value): + return .number(JSON.Number(value)) + case .uint32(let value): + return .number(JSON.Number(value)) + case .uint64(let value): + return .number(JSON.Number(value)) + case .unsignedInteger(let value): + return .number(JSON.Number(value)) + case .float16(let value): + return .number(JSON.Number(value)) + case .float(let value): + return .number(JSON.Number(value)) + case .double(let value): + return .number(JSON.Number(value)) + case .decimal(let value): + return .number(JSON.Number(value.description, isInteger: false, isNegative: value < 0)) + case .string(let value): + return .string(value) + case .data(let value): + return .string(value.base64EncodedString()) + case .url(let value): + return .string(value.absoluteString) + case .date(let value): + return .string(ZonedDate(date: value, timeZone: .utc).iso8601EncodedString()) + case .uuid(let value): + return .string(value.uuidString) + case .array(let value): + return .array(try value.map { try box($0, encoder: encoder) }) + case .dictionary(let value): + return .object(JSON.Object(uniqueKeysWithValues: try value.map { key, value in + let boxedValue = try box(value, encoder: encoder) + if let stringKey = key.stringValue { + return (stringKey, boxedValue) + } + else if let intKey = key.integerValue(Int.self) { + return (String(intKey), boxedValue) + } + throw EncodingError.invalidValue(value, .init(codingPath: encoder.codingPath, + debugDescription: "Dictionary contains non-string values")) + })) + } + } + + public static func unkeyedValuesToValue(_ values: UnkeyedValues, encoder: IVE) -> JSON { return .array(values) } - public static func keyedValuesToValue(_ values: [String: JSON], encoder: Encoder) -> JSON { - return .object(JSON.Object(uniqueKeysWithValues: values)) + public static func keyedValuesToValue(_ values: KeyedValues, encoder: IVE) -> JSON { + return .object(values) } public static func data(from value: JSON, options: Options) throws -> Data { var writingOptions: JSONSerialization.WritingOptions = [] + if options.outputFormatting.contains(.escapeSlashes) { + writingOptions.insert(.escapeSlashes) + } if options.outputFormatting.contains(.prettyPrinted) { writingOptions.insert(.prettyPrinted) } @@ -285,6 +424,9 @@ public struct JSONEncoderTransform: InternalEncoderTransform, InternalValueSeria public static func string(from value: JSON, options: Options) throws -> String { var writingOptions: JSONSerialization.WritingOptions = [] + if options.outputFormatting.contains(.escapeSlashes) { + writingOptions.insert(.escapeSlashes) + } if options.outputFormatting.contains(.prettyPrinted) { writingOptions.insert(.prettyPrinted) } @@ -297,16 +439,6 @@ public struct JSONEncoderTransform: InternalEncoderTransform, InternalValueSeria } -private let _iso8601Formatter: DateFormatter = { - let formatter = DateFormatter() - formatter.calendar = Calendar(identifier: .iso8601) - formatter.locale = Locale(identifier: "en_US_POSIX") - formatter.timeZone = TimeZone(secondsFromGMT: 0) - formatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSSXXXXX" - return formatter -}() - - #if canImport(Combine) import Combine diff --git a/Sources/PotentJSON/JSONReader.swift b/Sources/PotentJSON/JSONReader.swift index 4ba6084f3..e313c5d94 100644 --- a/Sources/PotentJSON/JSONReader.swift +++ b/Sources/PotentJSON/JSONReader.swift @@ -30,9 +30,10 @@ struct JSONReader { case invalidEscapeSequence case invalidNumber case invalidArray - case expectedMapKey - case expectedMapSeparator - case expectedMapValue + case expectedObjectKey + case expectedObjectSeparator + case expectedObjectValue + case expectedArraySeparator } case unexpectedEndOfStream @@ -402,8 +403,7 @@ struct JSONReader { if let finalIndex = try consumeStructure(Structure.endObject, input: index) { return (output, finalIndex) } - - if let (key, value, nextIndex) = try parseObjectMember(index, options: opt) { + else if let (key, value, nextIndex) = try parseObjectMember(index, options: opt) { output[key] = value if let finalParser = try consumeStructure(Structure.endObject, input: nextIndex) { @@ -414,10 +414,10 @@ struct JSONReader { continue } else { - return nil + throw Error.invalidData(.expectedObjectSeparator, position: nextIndex) } } - return nil + throw Error.invalidData(.expectedObjectKey, position: index) } } @@ -426,13 +426,13 @@ struct JSONReader { options opt: JSONSerialization.ReadingOptions ) throws -> (String, JSON, Index)? { guard let (name, index) = try parseString(input) else { - throw Error.invalidData(.expectedMapKey, position: source.distanceFromStart(input)) + throw Error.invalidData(.expectedObjectKey, position: source.distanceFromStart(input)) } guard let separatorIndex = try consumeStructure(Structure.nameSeparator, input: index) else { - throw Error.invalidData(.expectedMapSeparator, position: source.distanceFromStart(index)) + throw Error.invalidData(.expectedObjectSeparator, position: source.distanceFromStart(index)) } guard let (value, finalIndex) = try parseValue(separatorIndex, options: opt) else { - throw Error.invalidData(.expectedMapValue, position: source.distanceFromStart(separatorIndex)) + throw Error.invalidData(.expectedObjectValue, position: source.distanceFromStart(separatorIndex)) } return (name, value, finalIndex) @@ -461,6 +461,9 @@ struct JSONReader { index = nextIndex continue } + else { + throw Error.invalidData(.expectedArraySeparator, position: nextIndex) + } } throw Error.invalidData(.invalidArray, position: source.distanceFromStart(index)) } diff --git a/Sources/PotentJSON/JSONSerialization.swift b/Sources/PotentJSON/JSONSerialization.swift index 2174c521a..b6eddb348 100644 --- a/Sources/PotentJSON/JSONSerialization.swift +++ b/Sources/PotentJSON/JSONSerialization.swift @@ -11,7 +11,7 @@ import Foundation -public struct JSONSerialization { +public enum JSONSerialization { public enum Error: Swift.Error { case fragmentDisallowed @@ -71,6 +71,7 @@ public struct JSONSerialization { public static let sortedKeys = WritingOptions(rawValue: 1 << 0) public static let prettyPrinted = WritingOptions(rawValue: 1 << 1) + public static let escapeSlashes = WritingOptions(rawValue: 1 << 2) } public static func data(from json: JSON, options: WritingOptions = []) throws -> Data { @@ -79,7 +80,9 @@ public struct JSONSerialization { public static func string(from json: JSON, options: WritingOptions = []) throws -> String { var output = String() - var writer = JSONWriter(pretty: options.contains(.prettyPrinted), sortedKeys: options.contains(.sortedKeys)) { + var writer = JSONWriter(escapeSlashes: options.contains(.escapeSlashes), + pretty: options.contains(.prettyPrinted), + sortedKeys: options.contains(.sortedKeys)) { output.append($0) } @@ -88,6 +91,4 @@ public struct JSONSerialization { return output } - private init() {} - } diff --git a/Sources/PotentJSON/JSONWriter.swift b/Sources/PotentJSON/JSONWriter.swift index da1baa484..7fa68ba59 100644 --- a/Sources/PotentJSON/JSONWriter.swift +++ b/Sources/PotentJSON/JSONWriter.swift @@ -27,11 +27,13 @@ struct JSONWriter { } private var indent = 0 + private let esccapeSlashes: Bool private let pretty: Bool private let sortedKeys: Bool private let writer: (String) -> Void - init(pretty: Bool = false, sortedKeys: Bool = false, writer: @escaping (String) -> Void) { + init(escapeSlashes: Bool = false, pretty: Bool = false, sortedKeys: Bool = false, writer: @escaping (String) -> Void) { + self.esccapeSlashes = escapeSlashes self.pretty = pretty self.sortedKeys = sortedKeys self.writer = writer @@ -60,9 +62,9 @@ struct JSONWriter { switch scalar { case "\"": writer("\\\"") // U+0022 quotation mark - case "\\": + case "\\" where esccapeSlashes: writer("\\\\") // U+005C reverse solidus - case "/": + case "/" where esccapeSlashes: writer("\\/") // U+002F solidus case "\u{8}": writer("\\b") // U+0008 backspace diff --git a/Sources/PotentYAML/YAML.swift b/Sources/PotentYAML/YAML.swift index 0a35dc1ba..70eb8e6ba 100644 --- a/Sources/PotentYAML/YAML.swift +++ b/Sources/PotentYAML/YAML.swift @@ -8,10 +8,11 @@ // Distributed under the MIT License, See LICENSE for details. // -import CoreServices +import BigInt import Foundation import PotentCodables + /// General YAML value. /// /// # Object Access @@ -48,28 +49,32 @@ public enum YAML { self.rawValue = rawValue } - public static let null = Tag("!!null") - public static let bool = Tag("!!bool") - public static let int = Tag("!!int") - public static let float = Tag("!!float") - public static let str = Tag("!!str") + public static let null = Tag("tag:yaml.org,2002:null") + public static let bool = Tag("tag:yaml.org,2002:bool") + public static let int = Tag("tag:yaml.org,2002:int") + public static let float = Tag("tag:yaml.org,2002:float") + public static let str = Tag("tag:yaml.org,2002:str") - public static let seq = Tag("!!seq") - public static let map = Tag("!!map") + public static let seq = Tag("tag:yaml.org,2002:seq") + public static let map = Tag("tag:yaml.org,2002:map") } public typealias Anchor = String public enum Error: Swift.Error { + case unableToCreateParser case unexpectedEOF case unexpectedEvent + case invalidToken + case invalidTaggedBool + case parserError(message: String, line: Int, column: Int) } public struct Number: Equatable, Hashable, Codable { - public let value: String - public let isInteger: Bool - public let isNegative: Bool + public var value: String + public var isInteger: Bool + public var isNegative: Bool public init(_ value: String, isInteger: Bool, isNegative: Bool) { self.value = value @@ -78,23 +83,35 @@ public enum YAML { } public init(_ value: String) { - self.value = value - isInteger = value.allSatisfy(\.isNumber) - isNegative = value.hasPrefix("-") + self.init(value, isInteger: value.allSatisfy { $0.isNumber || $0 == "-" }, isNegative: value.hasPrefix("-")) } - public init(_ value: Double) { - self.value = value.description + public init(_ value: T) { + self.value = String(describing: value) isInteger = false isNegative = value < 0 } - public init(_ value: Int) { - self.value = value.description + public init(_ value: T) { + self.value = String(value) isInteger = true isNegative = value < 0 } + public init(_ value: T) { + self.value = String(value) + isInteger = true + isNegative = false + } + + public var isNaN: Bool { + return value == ".nan" + } + + public var isInfinity: Bool { + return value.hasSuffix(".inf") + } + public var integerValue: Int? { guard isInteger else { return nil } return Int(value) @@ -116,17 +133,40 @@ public enum YAML { public var numberValue: Any? { if isInteger { if isNegative { - if MemoryLayout.size == 4 { - return Int(value) ?? Int64(value) ?? Decimal(string: value) + guard let int = BigInt(value) else { + return nil + } + switch int.bitWidth { + case 0 ... (MemoryLayout.size * 8): + return Int(int) + case 0 ... (MemoryLayout.size * 8): + return Int64(int) + default: + return BigInt(value) } - return Int(value) ?? Decimal(string: value) } - if MemoryLayout.size == 4 { - return Int(value) ?? Int64(value) ?? UInt(value) ?? UInt64(value) ?? Decimal(string: value) + else { + guard let int = BigUInt(value) else { + return nil + } + switch int.bitWidth { + case 0 ... (MemoryLayout.size * 8) - 1: + return Int(int) + case 0 ... (MemoryLayout.size * 8): + return UInt(int) + case 0 ... (MemoryLayout.size * 8) - 1: + return Int64(int) + case 0 ... (MemoryLayout.size * 8): + return UInt64(int) + default: + return BigInt(value) + } } - return Int(value) ?? UInt(value) ?? Decimal(string: value) } - return Double(value) ?? Decimal(string: value) + guard let double = Double(value) else { + return nil + } + return double } public static func == (lhs: Number, rhs: Number) -> Bool { @@ -134,11 +174,21 @@ public enum YAML { } } - public typealias Array = [YAML] + public typealias Sequence = [YAML] public struct MappingEntry: Equatable, Hashable { var key: YAML var value: YAML + + public init(key: YAML, value: YAML) { + self.key = key + self.value = value + } + + public init(key: String, value: YAML) { + self.key = .string(key, style: .any, tag: nil, anchor: nil) + self.value = value + } } public typealias Mapping = [MappingEntry] @@ -158,20 +208,45 @@ public enum YAML { case block } - case null(anchor: Anchor?) - case string(String, style: StringStyle, tag: Tag?, anchor: Anchor?) - case integer(Number, anchor: Anchor?) - case float(Number, anchor: Anchor?) - case bool(Bool, anchor: Anchor?) - case sequence([YAML], style: CollectionStyle, tag: Tag?, anchor: Anchor?) - case mapping(Mapping, style: CollectionStyle, tag: Tag?, anchor: Anchor?) + case null(anchor: Anchor? = nil) + case string(String, style: StringStyle = .any, tag: Tag? = nil, anchor: Anchor? = nil) + case integer(Number, anchor: Anchor? = nil) + case float(Number, anchor: Anchor? = nil) + case bool(Bool, anchor: Anchor? = nil) + case sequence([YAML], style: CollectionStyle = .any, tag: Tag? = nil, anchor: Anchor? = nil) + case mapping(Mapping, style: CollectionStyle = .any, tag: Tag? = nil, anchor: Anchor? = nil) case alias(String) - public var isNull: Bool { - if case .null = self { - return true + public init(_ string: String) { + self = .string(string) + } + + public subscript(dynamicMember member: String) -> YAML? { + if let mapping = mappingValue { + return mapping.first { $0.key.stringValue == member }?.value } - return false + return nil + } + + public subscript(index: Int) -> YAML? { + if let sequence = sequenceValue, index < sequence.count { + return sequence[index] + } + return nil + } + + public subscript(key: String) -> YAML? { + if let mapping = mappingValue { + return mapping.first { $0.key.stringValue == key }?.value + } + return nil + } + + public subscript(key: YAML) -> YAML? { + if let mapping = mappingValue { + return mapping.first { $0.key == key }?.value + } + return nil } public var stringValue: String? { @@ -179,48 +254,32 @@ public enum YAML { return value } - public var integerValue: Int? { + public var numberValue: Any? { switch self { - case .integer(let value, _): - return value.integerValue - case .float(let value, _): - return value.integerValue - default: - return nil + case .integer(let value, _): return value.numberValue + case .float(let value, _): return value.numberValue + default: return nil } } + public var integerValue: Int? { + guard case .integer(let value, _) = self else { return nil } + return value.integerValue + } + public var unsignedIntegerValue: UInt? { - switch self { - case .integer(let value, _): - return value.unsignedIntegerValue - case .float(let value, _): - return value.unsignedIntegerValue - default: - return nil - } + guard case .integer(let value, _) = self else { return nil } + return value.unsignedIntegerValue } public var floatValue: Float? { - switch self { - case .integer(let value, _): - return value.floatValue - case .float(let value, _): - return value.floatValue - default: - return nil - } + guard case .float(let value, _) = self else { return nil } + return value.floatValue } public var doubleValue: Double? { - switch self { - case .integer(let value, _): - return value.doubleValue - case .float(let value, _): - return value.doubleValue - default: - return nil - } + guard case .float(let value, _) = self else { return nil } + return value.doubleValue } public var boolValue: Bool? { @@ -228,7 +287,7 @@ public enum YAML { return value } - public var sequenceValue: Array? { + public var sequenceValue: Sequence? { guard case .sequence(let value, _, _, _) = self else { return nil } return value } @@ -238,25 +297,46 @@ public enum YAML { return value } - public subscript(dynamicMember member: String) -> YAML? { - if let mapping = mappingValue { - return mapping.first { $0.key.stringValue == member }?.value - } - return nil - } +} - public subscript(index: Int) -> YAML? { - if let sequence = sequenceValue { - return index < sequence.count ? sequence[index] : nil + +// MARK: Conformances + +extension YAML: Equatable { + + public static func == (lhs: YAML, rhs: YAML) -> Bool { + switch (lhs, rhs) { + case (.null(_), .null(_)): + return true + case (.string(let lstring, _, _, _), .string(let rstring, _, _, _)): + return lstring == rstring + case (.integer(let lnumber, _), .integer(let rnumber, _)): + return lnumber == rnumber + case (.float(let lnumber, _), .float(let rnumber, _)): + return lnumber == rnumber + case (.bool(let lbool, _), .bool(let rbool, _)): + return lbool == rbool + case (.sequence(let lsequence, _, _, _), .sequence(let rsequence, _, _, _)): + return lsequence == rsequence + case (.mapping(let lentries, _, _, _), .mapping(let rentries, _, _, _)): + return lentries == rentries + case (.alias(let lstring), .alias(let rstring)): + return lstring == rstring + default: + return false } - return nil } - public subscript(key: String) -> YAML? { - if let mapping = mappingValue { - return mapping.first { $0.key.stringValue == key }?.value +} + +extension YAML: Hashable {} +extension YAML: Value { + + public var isNull: Bool { + if case .null = self { + return true } - return nil + return false } } @@ -264,62 +344,27 @@ public enum YAML { extension YAML: CustomStringConvertible { public var description: String { + var output = "" do { - - var output = "" - try YAMLWriter.write([self]) { output += $0 ?? "" } - - return output } catch { return "Invalid YAML: \(error)" } - + return output } } -extension YAML: Equatable { - public static func == (lhs: YAML, rhs: YAML) -> Bool { - switch lhs { - case .null(let lanchor): - guard case .null(let ranchor) = rhs else { return false } - return lanchor == ranchor - case .string(let lstring, _, let ltag, let lanchor): - guard case .string(let rstring, _, let rtag, let ranchor) = rhs else { return false } - return lstring == rstring && ltag == rtag && lanchor == ranchor - case .integer(let lnumber, let lanchor): - guard case .integer(let rnumber, let ranchor) = rhs else { return false } - return lnumber == rnumber && lanchor == ranchor - case .float(let lnumber, let lanchor): - guard case .float(let rnumber, let ranchor) = rhs else { return false } - return lnumber == rnumber && lanchor == ranchor - case .bool(let lbool, let lanchor): - guard case .bool(let rbool, let ranchor) = rhs else { return false } - return lbool == rbool && lanchor == ranchor - case .sequence(let lsequence, _, let ltag, let lanchor): - guard case .sequence(let rsequence, _, let rtag, let ranchor) = rhs else { return false } - return lsequence == rsequence && ltag == rtag && lanchor == ranchor - case .mapping(let lentries, _, let ltag, let lanchor): - guard case .mapping(let rentries, _, let rtag, let ranchor) = rhs else { return false } - return lentries == rentries && ltag == rtag && lanchor == ranchor - case .alias(let lstring): - guard case .alias(let rstring) = rhs else { return false } - return lstring == rstring - } - } +// MARK: Wrapping -} - -extension YAML: Hashable {} -extension YAML: Value { +extension YAML { public var unwrapped: Any? { switch self { - case .null: return nil + case .null, .alias: return nil case .bool(let value, _): return value case .string(let value, _, _, _): return value case .integer(let value, _): return value.numberValue @@ -328,72 +373,68 @@ extension YAML: Value { case .mapping(let value, _, _, _): return Dictionary(uniqueKeysWithValues: value.map { entry in (entry.key.stringValue!, entry.value.unwrapped) }) - case .alias: fatalError("Aliases not supported during unwrapping") } } } -extension YAML: ExpressibleByNilLiteral { + +// MARK: Literals + +extension YAML: ExpressibleByNilLiteral, ExpressibleByBooleanLiteral, ExpressibleByStringLiteral, + ExpressibleByIntegerLiteral, ExpressibleByFloatLiteral, ExpressibleByArrayLiteral, + ExpressibleByDictionaryLiteral { public init(nilLiteral: ()) { self = .null(anchor: nil) } -} - -extension YAML: ExpressibleByBooleanLiteral { - public init(booleanLiteral value: BooleanLiteralType) { self = .bool(value, anchor: nil) } -} - -extension YAML: ExpressibleByIntegerLiteral { - public init(integerLiteral value: IntegerLiteralType) { self = .integer(Number(value), anchor: nil) } -} - -extension YAML: ExpressibleByFloatLiteral { - public init(floatLiteral value: FloatLiteralType) { self = .float(Number(value), anchor: nil) } -} - -extension YAML: ExpressibleByStringLiteral { - public init(stringLiteral value: StringLiteralType) { self = .string(value, style: .any, tag: nil, anchor: nil) } -} - -extension YAML: ExpressibleByArrayLiteral { - public init(arrayLiteral elements: YAML...) { self = .sequence(elements, style: .any, tag: nil, anchor: nil) } + public init(dictionaryLiteral elements: (YAML, YAML)...) { + self = .mapping(elements.map { MappingEntry(key: $0, value: $1) }, style: .any, tag: nil, anchor: nil) + } + } -extension YAML: ExpressibleByDictionaryLiteral { +extension YAML.Number: ExpressibleByFloatLiteral, ExpressibleByIntegerLiteral, ExpressibleByStringLiteral { - public init(dictionaryLiteral elements: (YAML, YAML)...) { - self = .mapping(elements.map { MappingEntry(key: $0, value: $1) }, style: .any, tag: nil, anchor: nil) + public init(stringLiteral value: String) { + self.init(value) + } + + public init(floatLiteral value: Double) { + self.init(value) + } + + public init(integerLiteral value: Int) { + self.init(value) } } -/// Make encoders/decoders available in JSON namespace -/// +// Make encoders/decoders available in AnyValue namespace + public extension YAML { typealias Encoder = YAMLEncoder diff --git a/Sources/PotentYAML/YAMLDecoder.swift b/Sources/PotentYAML/YAMLDecoder.swift index a8adee92e..5c9deb8d5 100644 --- a/Sources/PotentYAML/YAMLDecoder.swift +++ b/Sources/PotentYAML/YAMLDecoder.swift @@ -8,6 +8,7 @@ // Distributed under the MIT License, See LICENSE for details. // +import BigInt import Foundation import PotentCodables @@ -53,13 +54,14 @@ public class YAMLDecoder: ValueDecoder, DecodesFromS case custom((_ decoder: Decoder) throws -> Data) } - /// The strategy to use for non-YAML-conforming floating-point values (IEEE 754 infinity and NaN). + /// The strategy to use for YAML-conforming floating-point values (IEEE 754 infinity and NaN). public enum NonConformingFloatDecodingStrategy { + + /// Decode values following the YAML 1.2 sepcification. + case `default` + /// Throw upon encountering non-conforming values. This is the default strategy. case `throw` - - /// Decode the values from the given representation strings. - case convertFromString(positiveInfinity: String, negativeInfinity: String, nan: String) } /// The strategy to use in decoding dates. Defaults to `.deferredToDate`. @@ -68,15 +70,11 @@ public class YAMLDecoder: ValueDecoder, DecodesFromS /// The strategy to use in decoding binary data. Defaults to `.base64`. open var dataDecodingStrategy: DataDecodingStrategy = .base64 - /// The strategy to use in decoding non-conforming numbers. Defaults to `.throw`. - open var nonConformingFloatDecodingStrategy: NonConformingFloatDecodingStrategy = .throw - /// The options set on the top-level decoder. override public var options: YAMLDecoderTransform.Options { return YAMLDecoderTransform.Options( dateDecodingStrategy: dateDecodingStrategy, dataDecodingStrategy: dataDecodingStrategy, - nonConformingFloatDecodingStrategy: nonConformingFloatDecodingStrategy, keyDecodingStrategy: keyDecodingStrategy, userInfo: userInfo ) @@ -92,7 +90,6 @@ public class YAMLDecoder: ValueDecoder, DecodesFromS public struct YAMLDecoderTransform: InternalDecoderTransform, InternalValueDeserializer, InternalValueParser { public typealias Value = YAML - public typealias Decoder = InternalValueDecoder public typealias State = Void public static let nilValue = YAML.null(anchor: nil) @@ -101,13 +98,55 @@ public struct YAMLDecoderTransform: InternalDecoderTransform, InternalValueDeser public struct Options: InternalDecoderOptions { public let dateDecodingStrategy: YAMLDecoder.DateDecodingStrategy public let dataDecodingStrategy: YAMLDecoder.DataDecodingStrategy - public let nonConformingFloatDecodingStrategy: YAMLDecoder.NonConformingFloatDecodingStrategy public let keyDecodingStrategy: KeyDecodingStrategy public let userInfo: [CodingUserInfoKey: Any] } + public static func intercepts(_ type: Decodable.Type) -> Bool { + return type == Date.self || type == NSDate.self + || type == Data.self || type == NSData.self + || type == URL.self || type == NSURL.self + || type == UUID.self || type == NSUUID.self + || type == Float16.self + || type == Decimal.self || type == NSDecimalNumber.self + || type == BigInt.self + || type == BigUInt.self + || type == AnyValue.self + } + + public static func unbox(_ value: YAML, interceptedType: Decodable.Type, decoder: IVD) throws -> Any? { + if interceptedType == Date.self || interceptedType == NSDate.self { + return try unbox(value, as: Date.self, decoder: decoder) + } + else if interceptedType == Data.self || interceptedType == NSData.self { + return try unbox(value, as: Data.self, decoder: decoder) + } + else if interceptedType == URL.self || interceptedType == NSURL.self { + return try unbox(value, as: URL.self, decoder: decoder) + } + else if interceptedType == UUID.self || interceptedType == NSUUID.self { + return try unbox(value, as: UUID.self, decoder: decoder) + } + else if interceptedType == Float16.self { + return try unbox(value, as: Float16.self, decoder: decoder) + } + else if interceptedType == Decimal.self || interceptedType == NSDecimalNumber.self { + return try unbox(value, as: Decimal.self, decoder: decoder) + } + else if interceptedType == BigInt.self { + return try unbox(value, as: BigInt.self, decoder: decoder) + } + else if interceptedType == BigUInt.self { + return try unbox(value, as: BigUInt.self, decoder: decoder) + } + else if interceptedType == AnyValue.self { + return try unbox(value, as: AnyValue.self, decoder: decoder) + } + fatalError("type not valid for intercept") + } + /// Returns the given value unboxed from a container. - public static func unbox(_ value: YAML, as type: Bool.Type, decoder: Decoder) throws -> Bool? { + public static func unbox(_ value: YAML, as type: Bool.Type, decoder: IVD) throws -> Bool? { switch value { case .bool(let value, _): return value case .null: return nil @@ -137,157 +176,246 @@ public struct YAMLDecoderTransform: InternalDecoderTransform, InternalValueDeser return result } - public static func unbox(_ value: YAML, as type: Int.Type, decoder: Decoder) throws -> Int? { + public static func unbox(_ value: YAML, as type: Int.Type, decoder: IVD) throws -> Int? { switch value { case .integer(let number, _): return try coerce(number, at: decoder.codingPath) - case .float(let number, _): return try coerce(number, at: decoder.codingPath) case .null: return nil case let yaml: throw DecodingError.typeMismatch(at: decoder.codingPath, expectation: type, reality: yaml) } } - public static func unbox(_ value: YAML, as type: Int8.Type, decoder: Decoder) throws -> Int8? { + public static func unbox(_ value: YAML, as type: Int8.Type, decoder: IVD) throws -> Int8? { switch value { case .integer(let number, _): return try coerce(number, at: decoder.codingPath) - case .float(let number, _): return try coerce(number, at: decoder.codingPath) case .null: return nil case let yaml: throw DecodingError.typeMismatch(at: decoder.codingPath, expectation: type, reality: yaml) } } - public static func unbox(_ value: YAML, as type: Int16.Type, decoder: Decoder) throws -> Int16? { + public static func unbox(_ value: YAML, as type: Int16.Type, decoder: IVD) throws -> Int16? { switch value { case .integer(let number, _): return try coerce(number, at: decoder.codingPath) - case .float(let number, _): return try coerce(number, at: decoder.codingPath) case .null: return nil case let yaml: throw DecodingError.typeMismatch(at: decoder.codingPath, expectation: type, reality: yaml) } } - public static func unbox(_ value: YAML, as type: Int32.Type, decoder: Decoder) throws -> Int32? { + public static func unbox(_ value: YAML, as type: Int32.Type, decoder: IVD) throws -> Int32? { switch value { case .integer(let number, _): return try coerce(number, at: decoder.codingPath) - case .float(let number, _): return try coerce(number, at: decoder.codingPath) case .null: return nil case let yaml: throw DecodingError.typeMismatch(at: decoder.codingPath, expectation: type, reality: yaml) } } - public static func unbox(_ value: YAML, as type: Int64.Type, decoder: Decoder) throws -> Int64? { + public static func unbox(_ value: YAML, as type: Int64.Type, decoder: IVD) throws -> Int64? { switch value { case .integer(let number, _): return try coerce(number, at: decoder.codingPath) - case .float(let number, _): return try coerce(number, at: decoder.codingPath) case .null: return nil case let yaml: throw DecodingError.typeMismatch(at: decoder.codingPath, expectation: type, reality: yaml) } } - public static func unbox(_ value: YAML, as type: UInt.Type, decoder: Decoder) throws -> UInt? { + public static func unbox(_ value: YAML, as type: UInt.Type, decoder: IVD) throws -> UInt? { switch value { case .integer(let number, _): return try coerce(number, at: decoder.codingPath) - case .float(let number, _): return try coerce(number, at: decoder.codingPath) case .null: return nil case let yaml: throw DecodingError.typeMismatch(at: decoder.codingPath, expectation: type, reality: yaml) } } - public static func unbox(_ value: YAML, as type: UInt8.Type, decoder: Decoder) throws -> UInt8? { + public static func unbox(_ value: YAML, as type: UInt8.Type, decoder: IVD) throws -> UInt8? { switch value { case .integer(let number, _): return try coerce(number, at: decoder.codingPath) - case .float(let number, _): return try coerce(number, at: decoder.codingPath) case .null: return nil case let yaml: throw DecodingError.typeMismatch(at: decoder.codingPath, expectation: type, reality: yaml) } } - public static func unbox(_ value: YAML, as type: UInt16.Type, decoder: Decoder) throws -> UInt16? { + public static func unbox(_ value: YAML, as type: UInt16.Type, decoder: IVD) throws -> UInt16? { switch value { case .integer(let number, _): return try coerce(number, at: decoder.codingPath) - case .float(let number, _): return try coerce(number, at: decoder.codingPath) case .null: return nil case let yaml: throw DecodingError.typeMismatch(at: decoder.codingPath, expectation: type, reality: yaml) } } - public static func unbox(_ value: YAML, as type: UInt32.Type, decoder: Decoder) throws -> UInt32? { + public static func unbox(_ value: YAML, as type: UInt32.Type, decoder: IVD) throws -> UInt32? { switch value { case .integer(let number, _): return try coerce(number, at: decoder.codingPath) - case .float(let number, _): return try coerce(number, at: decoder.codingPath) case .null: return nil case let yaml: throw DecodingError.typeMismatch(at: decoder.codingPath, expectation: type, reality: yaml) } } - public static func unbox(_ value: YAML, as type: UInt64.Type, decoder: Decoder) throws -> UInt64? { + public static func unbox(_ value: YAML, as type: UInt64.Type, decoder: IVD) throws -> UInt64? { switch value { case .integer(let number, _): return try coerce(number, at: decoder.codingPath) - case .float(let number, _): return try coerce(number, at: decoder.codingPath) case .null: return nil case let yaml: throw DecodingError.typeMismatch(at: decoder.codingPath, expectation: type, reality: yaml) } } - public static func unbox(_ value: YAML, as type: Float.Type, decoder: Decoder) throws -> Float? { + public static func unbox(_ value: YAML, as type: BigInt.Type, decoder: IVD) throws -> BigInt? { switch value { - case .integer(let number, _): return try coerce(number, at: decoder.codingPath) - case .float(let number, _): return try coerce(number, at: decoder.codingPath) + case .integer(let number, _): return BigInt(number.value) + case .null: return nil + case let yaml: + throw DecodingError.typeMismatch(at: decoder.codingPath, expectation: type, reality: yaml) + } + } + + public static func unbox(_ value: YAML, as type: BigUInt.Type, decoder: IVD) throws -> BigUInt? { + switch value { + case .integer(let number, _): + if number.isNegative { + throw overflow(type, value: value, at: decoder.codingPath) + } + return BigUInt(number.value) case .null: return nil case let yaml: throw DecodingError.typeMismatch(at: decoder.codingPath, expectation: type, reality: yaml) } } - public static func unbox(_ value: YAML, as type: Double.Type, decoder: Decoder) throws -> Double? { + public static func unbox(_ value: YAML, as type: Float16.Type, decoder: IVD) throws -> Float16? { + switch value { + case .integer(let number, _): return try coerce(number, at: decoder.codingPath) + case .float(let number, _): + if number.isNaN { + return Float16.nan + } + else if number.isInfinity { + return number.isNegative ? -Float16.infinity : +Float16.infinity + } + else { + return try coerce(number, at: decoder.codingPath) + } + case .null: return nil + default: + break + } + throw DecodingError.typeMismatch(at: decoder.codingPath, expectation: type, reality: value) + } + + public static func unbox(_ value: YAML, as type: Float.Type, decoder: IVD) throws -> Float? { switch value { case .integer(let number, _): return try coerce(number, at: decoder.codingPath) - case .float(let number, _): return try coerce(number, at: decoder.codingPath) + case .float(let number, _): + if number.isNaN { + return Float.nan + } + else if number.isInfinity { + return number.isNegative ? -Float.infinity : +Float.infinity + } + else { + return try coerce(number, at: decoder.codingPath) + } case .null: return nil case let yaml: throw DecodingError.typeMismatch(at: decoder.codingPath, expectation: type, reality: yaml) } } - public static func unbox(_ value: YAML, as type: Decimal.Type, decoder: Decoder) throws -> Decimal? { + public static func unbox(_ value: YAML, as type: Double.Type, decoder: IVD) throws -> Double? { switch value { - case .integer(let number, _): return Decimal(string: number.value) - case .float(let number, _): return Decimal(string: number.value) + case .integer(let number, _): return try coerce(number, at: decoder.codingPath) + case .float(let number, _): + if number.isNaN { + return Double.nan + } + else if number.isInfinity { + return number.isNegative ? -Double.infinity : +Double.infinity + } + else { + return try coerce(number, at: decoder.codingPath) + } case .null: return nil case let yaml: throw DecodingError.typeMismatch(at: decoder.codingPath, expectation: type, reality: yaml) } } - public static func unbox(_ value: YAML, as type: String.Type, decoder: Decoder) throws -> String? { + public static func unbox(_ value: YAML, as type: Decimal.Type, decoder: IVD) throws -> Decimal? { switch value { + case .integer(let number, _): + guard let decimal = Decimal(string: number.value) else { + throw DecodingError.typeMismatch( + Decimal.self, + .init(codingPath: decoder.codingPath, + debugDescription: "Decimal unable to parse number")) + } + return decimal + case .float(let number, _): + if number.isNaN { + return Decimal.nan + } + else if number.isInfinity { + throw DecodingError.typeMismatch( + Decimal.self, + .init(codingPath: decoder.codingPath, + debugDescription: "Decimal does not support infinity")) + } + guard let decimal = Decimal(string: number.value) else { + throw DecodingError.typeMismatch( + Decimal.self, + .init(codingPath: decoder.codingPath, + debugDescription: "Decimal unable to parse number")) + } + return decimal case .null: return nil + case let yaml: + throw DecodingError.typeMismatch(at: decoder.codingPath, expectation: type, reality: yaml) + } + } + + public static func unbox(_ value: YAML, as type: String.Type, decoder: IVD) throws -> String? { + switch value { case .string(let string, _, _, _): return string + case .null: return nil + case let yaml: + throw DecodingError.typeMismatch(at: decoder.codingPath, expectation: type, reality: yaml) + } + } + + public static func unbox(_ value: YAML, as type: UUID.Type, decoder: IVD) throws -> UUID? { + switch value { + case .string(let string, _, _, _): + guard let uuid = UUID(uuidString: string) else { + throw DecodingError.dataCorruptedError(in: decoder, debugDescription: "Expected valid URL string") + } + return uuid + case .null: return nil case let yaml: throw DecodingError.typeMismatch(at: decoder.codingPath, expectation: type, reality: yaml) } } - public static func unbox(_ value: YAML, as type: UUID.Type, decoder: Decoder) throws -> UUID? { + public static func unbox(_ value: YAML, as type: URL.Type, decoder: IVD) throws -> URL? { switch value { case .string(let string, _, _, _): - return UUID(uuidString: string) + guard let url = URL(string: string) else { + throw DecodingError.dataCorruptedError(in: decoder, debugDescription: "Expected valid URL string") + } + return url case .null: return nil case let yaml: throw DecodingError.typeMismatch(at: decoder.codingPath, expectation: type, reality: yaml) } } - public static func unbox(_ value: YAML, as type: Date.Type, decoder: Decoder) throws -> Date? { + public static func unbox(_ value: YAML, as type: Date.Type, decoder: IVD) throws -> Date? { guard !value.isNull else { return nil } switch decoder.options.dateDecodingStrategy { @@ -304,13 +432,13 @@ public struct YAMLDecoderTransform: InternalDecoderTransform, InternalValueDeser case .iso8601: let string = try unbox(value, as: String.self, decoder: decoder)! - guard let zonedDate = _iso8601Formatter.date(from: string) else { + guard let date = ZonedDate(iso8601Encoded: string)?.utcDate else { throw DecodingError.dataCorrupted(.init( codingPath: decoder.codingPath, debugDescription: "Expected date string to be ISO8601-formatted." )) } - return zonedDate.utcDate + return date case .formatted(let formatter): let string = try unbox(value, as: String.self, decoder: decoder)! @@ -327,7 +455,7 @@ public struct YAMLDecoderTransform: InternalDecoderTransform, InternalValueDeser } } - public static func unbox(_ value: YAML, as type: Data.Type, decoder: Decoder) throws -> Data? { + public static func unbox(_ value: YAML, as type: Data.Type, decoder: IVD) throws -> Data? { guard !value.isNull else { return nil } switch decoder.options.dataDecodingStrategy { @@ -353,26 +481,70 @@ public struct YAMLDecoderTransform: InternalDecoderTransform, InternalValueDeser } } - public static func valueToUnkeyedValues(_ value: YAML, decoder: Decoder) throws -> [YAML]? { + public static func unbox(_ value: YAML, as type: AnyValue.Type, decoder: IVD) throws -> AnyValue { + switch value { + case .null: return .nil + case .bool(let value, _): return .bool(value) + case .string(let value, _, _, _): return .string(value) + case .integer(let value, anchor: _), .float(let value, anchor: _): + switch value.numberValue { + case .none: return .nil + case let int as Int: + return MemoryLayout.size == 4 ? .int32(Int32(int)) : .int64(Int64(int)) + case let uint as UInt: + return MemoryLayout.size == 4 ? .uint32(UInt32(uint)) : .uint64(UInt64(uint)) + case let int as Int64: + return .int64(Int64(int)) + case let uint as UInt64: + return .uint64(UInt64(uint)) + case let int as BigInt: + return .integer(int) + case let double as Double: + return .double(double) + default: + fatalError("numberValue returned unsupported value") + } + case .sequence(let value, _, _, _): + return .array(try value.map { try unbox($0, as: AnyValue.self, decoder: decoder) }) + case .mapping(let value, _, _, _): + return .dictionary(AnyValue.AnyDictionary(uniqueKeysWithValues: try value.map { entry in + (try unbox(entry.key, as: AnyValue.self, decoder: decoder), + try unbox(entry.value, as: AnyValue.self, decoder: decoder)) + })) + default: + break + } + throw DecodingError.typeMismatch(at: decoder.codingPath, expectation: type, reality: value) + } + + public static func valueToUnkeyedValues(_ value: YAML, decoder: IVD) throws -> UnkeyedValues? { guard case .sequence(let sequence, _, _, _) = value else { return nil } return sequence } - public static func valueToKeyedValues(_ value: YAML, decoder: Decoder) throws -> [String: YAML]? { + public static func valueToKeyedValues(_ value: YAML, decoder: IVD) throws -> KeyedValues? { guard case .mapping(let mapping, _, _, _) = value else { return nil } - return Dictionary(uniqueKeysWithValues: try mapping.map { - guard let stringKey = $0.key.stringValue else { - throw DecodingError.typeMismatch( - type(of: $0.key.unwrapped), - DecodingError.Context( - codingPath: decoder.codingPath, - debugDescription: "Key is not a string", - underlyingError: nil + return try KeyedValues( + mapping.map { entry in + switch entry.key { + case .string(let stringKey, _, _, _): + return (stringKey, entry.value) + case .integer(let intKey, _): + return (intKey.value, entry.value) + default: + throw DecodingError.dataCorruptedError( + in: decoder, + debugDescription: "Mapping contains unsupported keys" ) - ) + } + }, + uniquingKeysWith: { _, _ in + throw DecodingError.dataCorrupted(.init( + codingPath: decoder.codingPath, + debugDescription: "Mapping contains duplicate keys" + )) } - return (stringKey, $0.value) - }) + ) } public static func value(from data: Data, options: Options) throws -> YAML { @@ -380,21 +552,12 @@ public struct YAMLDecoderTransform: InternalDecoderTransform, InternalValueDeser } public static func value(from string: String, options: Options) throws -> YAML { - guard let data = string.data(using: .utf8) else { - throw DecodingError - .dataCorrupted( - DecodingError - .Context(codingPath: [], debugDescription: "String cannot be encoded as UTF-8", underlyingError: nil) - ) - } - return try YAMLSerialization.yaml(from: data) + return try YAMLSerialization.yaml(from: string) } } -private let _iso8601Formatter = SuffixedDateFormatter.optionalFractionalSeconds(basePattern: "yyyy-MM-dd'T'HH:mm:ss") - #if canImport(Combine) diff --git a/Sources/PotentYAML/YAMLEncoder.swift b/Sources/PotentYAML/YAMLEncoder.swift index e0e4e362b..71375973a 100644 --- a/Sources/PotentYAML/YAMLEncoder.swift +++ b/Sources/PotentYAML/YAMLEncoder.swift @@ -8,6 +8,7 @@ // Distributed under the MIT License, See LICENSE for details. // +import BigInt import Foundation import PotentCodables @@ -74,16 +75,6 @@ public class YAMLEncoder: ValueEncoder, EncodesToStr case custom((Data, Encoder) throws -> Void) } - /// The strategy to use for non-YAML-conforming floating-point values (IEEE 754 infinity and NaN). - public enum NonConformingFloatEncodingStrategy { - - /// Throw upon encountering non-conforming values. This is the default strategy. - case `throw` - - /// Encode the values using the given representation strings. - case convertToString(positiveInfinity: String, negativeInfinity: String, nan: String) - } - /// The output format to produce. Defaults to `[]`. public var outputFormatting: OutputFormatting = [] @@ -93,15 +84,11 @@ public class YAMLEncoder: ValueEncoder, EncodesToStr /// The strategy to use in encoding binary data. Defaults to `.base64`. public var dataEncodingStrategy: YAMLEncoder.DataEncodingStrategy = .base64 - /// The strategy to use in encoding non-conforming numbers. Defaults to `.throw`. - public var nonConformingFloatEncodingStrategy: YAMLEncoder.NonConformingFloatEncodingStrategy = .throw - /// The options set on the top-level encoder. override public var options: YAMLEncoderTransform.Options { return YAMLEncoderTransform.Options( dateEncodingStrategy: dateEncodingStrategy, dataEncodingStrategy: dataEncodingStrategy, - nonConformingFloatEncodingStrategy: nonConformingFloatEncodingStrategy, outputFormatting: outputFormatting, keyEncodingStrategy: keyEncodingStrategy, userInfo: userInfo @@ -120,193 +107,236 @@ public class YAMLEncoder: ValueEncoder, EncodesToStr public struct YAMLEncoderTransform: InternalEncoderTransform, InternalValueSerializer, InternalValueStringifier { public typealias Value = YAML - public typealias Encoder = InternalValueEncoder public typealias State = Void - public static var emptyKeyedContainer = YAML.mapping([], style: .any, tag: nil, anchor: nil) - public static var emptyUnkeyedContainer = YAML.sequence([], style: .any, tag: nil, anchor: nil) + public static var emptyKeyedContainer = YAML.mapping([]) + public static var emptyUnkeyedContainer = YAML.sequence([]) public struct Options: InternalEncoderOptions { public let dateEncodingStrategy: YAMLEncoder.DateEncodingStrategy public let dataEncodingStrategy: YAMLEncoder.DataEncodingStrategy - public let nonConformingFloatEncodingStrategy: YAMLEncoder.NonConformingFloatEncodingStrategy public let outputFormatting: YAMLEncoder.OutputFormatting public let keyEncodingStrategy: KeyEncodingStrategy public let userInfo: [CodingUserInfoKey: Any] } - public static func boxNil(encoder: Encoder) throws -> YAML { return .null(anchor: nil) } - public static func box(_ value: Bool, encoder: Encoder) throws -> YAML { return .bool(value, anchor: nil) } - public static func box( - _ value: Int, - encoder: Encoder - ) throws -> YAML { return .integer(.init(value.description), anchor: nil) } - public static func box( - _ value: Int8, - encoder: Encoder - ) throws -> YAML { return .integer(.init(value.description), anchor: nil) } - public static func box( - _ value: Int16, - encoder: Encoder - ) throws -> YAML { return .integer(.init(value.description), anchor: nil) } - public static func box( - _ value: Int32, - encoder: Encoder - ) throws -> YAML { return .integer(.init(value.description), anchor: nil) } - public static func box( - _ value: Int64, - encoder: Encoder - ) throws -> YAML { return .integer(.init(value.description), anchor: nil) } - public static func box( - _ value: UInt, - encoder: Encoder - ) throws -> YAML { return .integer(.init(value.description), anchor: nil) } - public static func box( - _ value: UInt8, - encoder: Encoder - ) throws -> YAML { return .integer(.init(value.description), anchor: nil) } - public static func box( - _ value: UInt16, - encoder: Encoder - ) throws -> YAML { return .integer(.init(value.description), anchor: nil) } - public static func box( - _ value: UInt32, - encoder: Encoder - ) throws -> YAML { return .integer(.init(value.description), anchor: nil) } - public static func box( - _ value: UInt64, - encoder: Encoder - ) throws -> YAML { return .integer(.init(value.description), anchor: nil) } - public static func box( - _ value: String, - encoder: Encoder - ) throws -> YAML { return .string(value, style: .any, tag: nil, anchor: nil) } - public static func box( - _ value: URL, - encoder: Encoder - ) throws -> YAML { return .string(value.absoluteString, style: .any, tag: nil, anchor: nil) } - public static func box( - _ value: UUID, - encoder: Encoder - ) throws -> YAML { return .string(value.uuidString, style: .any, tag: nil, anchor: nil) } - - public static func box(_ float: Float, encoder: Encoder) throws -> YAML { + public static func intercepts(_ type: Encodable.Type) -> Bool { + return type == Date.self || type == NSDate.self + || type == Data.self || type == NSData.self + || type == URL.self || type == NSURL.self + || type == UUID.self || type == NSUUID.self + || type == Float16.self + || type == Decimal.self || type == NSDecimalNumber.self + || type == BigInt.self + || type == BigUInt.self + || type == AnyValue.self + } + + public static func box(_ value: Any, interceptedType: Encodable.Type, encoder: IVE) throws -> YAML { + if let value = value as? Date { + return try box(value, encoder: encoder) + } + else if let value = value as? Data { + return try box(value, encoder: encoder) + } + else if let value = value as? URL { + return try box(value, encoder: encoder) + } + else if let value = value as? UUID { + return try box(value, encoder: encoder) + } + else if let value = value as? Float16 { + return try box(value, encoder: encoder) + } + else if let value = value as? Decimal { + return try box(value, encoder: encoder) + } + else if let value = value as? BigInt { + return try box(value, encoder: encoder) + } + else if let value = value as? BigUInt { + return try box(value, encoder: encoder) + } + else if let value = value as? AnyValue { + return try box(value, encoder: encoder) + } + fatalError("type not valid for intercept") + } + + public static func boxNil(encoder: IVE) throws -> YAML { return .null(anchor: nil) } + public static func box(_ value: Bool, encoder: IVE) throws -> YAML { return .bool(value) } + public static func box(_ value: Int, encoder: IVE) throws -> YAML { return .integer(.init(value)) } + public static func box(_ value: Int8, encoder: IVE) throws -> YAML { return .integer(.init(value)) } + public static func box(_ value: Int16, encoder: IVE) throws -> YAML { return .integer(.init(value)) } + public static func box(_ value: Int32, encoder: IVE) throws -> YAML { return .integer(.init(value)) } + public static func box(_ value: Int64, encoder: IVE) throws -> YAML { return .integer(.init(value)) } + public static func box(_ value: UInt, encoder: IVE) throws -> YAML { return .integer(.init(value)) } + public static func box(_ value: UInt8, encoder: IVE) throws -> YAML { return .integer(.init(value)) } + public static func box(_ value: UInt16, encoder: IVE) throws -> YAML { return .integer(.init(value)) } + public static func box(_ value: UInt32, encoder: IVE) throws -> YAML { return .integer(.init(value)) } + public static func box(_ value: UInt64, encoder: IVE) throws -> YAML { return .integer(.init(value)) } + public static func box(_ value: BigInt, encoder: IVE) throws -> YAML { return .integer(.init(value)) } + public static func box(_ value: BigUInt, encoder: IVE) throws -> YAML { return .integer(.init(value)) } + public static func box(_ value: String, encoder: IVE) throws -> YAML { return .string(value) } + public static func box(_ value: URL, encoder: IVE) throws -> YAML { return .string(value.absoluteString) } + public static func box(_ value: UUID, encoder: IVE) throws -> YAML { return .string(value.uuidString) } + + public static func box(_ float: Float16, encoder: IVE) throws -> YAML { guard !float.isInfinite, !float.isNaN else { - guard case .convertToString( - let posInfString, - let negInfString, - let nanString - ) = encoder.options.nonConformingFloatEncodingStrategy else { - throw EncodingError.invalidFloatingPointValue(float, at: encoder.codingPath) + if float == Float16.infinity { + return .float(YAML.Number("+.inf", isInteger: false, isNegative: false)) + } + else if float == -Float16.infinity { + return .float(YAML.Number("-.inf", isInteger: false, isNegative: true)) } + else { + return .float(YAML.Number(".nan", isInteger: false, isNegative: false)) + } + } + + return .float(.init(float)) + } + public static func box(_ float: Float, encoder: IVE) throws -> YAML { + guard !float.isInfinite, !float.isNaN else { if float == Float.infinity { - return .string(posInfString, style: .any, tag: nil, anchor: nil) + return .float(YAML.Number("+.inf", isInteger: false, isNegative: false)) } else if float == -Float.infinity { - return .string(negInfString, style: .any, tag: nil, anchor: nil) + return .float(YAML.Number("-.inf", isInteger: false, isNegative: true)) } else { - return .string(nanString, style: .any, tag: nil, anchor: nil) + return .float(YAML.Number(".nan", isInteger: false, isNegative: false)) } } - return .float(.init(float.description), anchor: nil) + return .float(.init(float)) } - public static func box(_ double: Double, encoder: Encoder) throws -> YAML { + public static func box(_ double: Double, encoder: IVE) throws -> YAML { guard !double.isInfinite, !double.isNaN else { - guard case .convertToString( - let posInfString, - let negInfString, - let nanString - ) = encoder.options.nonConformingFloatEncodingStrategy else { - throw EncodingError.invalidFloatingPointValue(double, at: encoder.codingPath) - } - if double == Double.infinity { - return .string(posInfString, style: .any, tag: nil, anchor: nil) + return .float(YAML.Number("+.inf", isInteger: false, isNegative: false)) } else if double == -Double.infinity { - return .string(negInfString, style: .any, tag: nil, anchor: nil) + return .float(YAML.Number("-.inf", isInteger: false, isNegative: true)) } else { - return .string(nanString, style: .any, tag: nil, anchor: nil) + return .float(YAML.Number(".nan", isInteger: false, isNegative: false)) } } - return .float(.init(double.description), anchor: nil) + return .float(.init(double)) } - public static func box(_ decimal: Decimal, encoder: Encoder) throws -> YAML { + public static func box(_ decimal: Decimal, encoder: IVE) throws -> YAML { guard !decimal.isInfinite, !decimal.isNaN else { - guard case .convertToString( - let posInfString, - let negInfString, - let nanString - ) = encoder.options.nonConformingFloatEncodingStrategy else { - throw EncodingError.invalidFloatingPointValue(decimal, at: encoder.codingPath) - } - - if decimal.isInfinite, decimal.sign == .plus { - return .string(posInfString, style: .any, tag: nil, anchor: nil) - } - else if decimal.isInfinite, decimal.sign == .minus { - return .string(negInfString, style: .any, tag: nil, anchor: nil) + if decimal.isNaN { + return .float(YAML.Number(".nan", isInteger: false, isNegative: false)) } else { - return .string(nanString, style: .any, tag: nil, anchor: nil) + throw EncodingError.invalidFloatingPointValue(decimal, at: encoder.codingPath) } } - var decimal = decimal - let rep = NSDecimalString(&decimal, NSLocale.system) - - return .float(.init(rep.description), anchor: nil) + return .float(YAML.Number(decimal.description, isInteger: false, isNegative: false)) } - public static func box(_ value: Data, encoder: Encoder) throws -> YAML { + public static func box(_ value: Data, encoder: IVE) throws -> YAML { switch encoder.options.dataEncodingStrategy { case .deferredToData: - return try encoder.subEncode { try value.encode(to: $0) } ?? emptyKeyedContainer + return try encoder.subEncode { try value.encode(to: $0.encoder) } ?? emptyKeyedContainer case .base64: - return .string(value.base64EncodedString(), style: .any, tag: nil, anchor: nil) + return .string(value.base64EncodedString()) case .custom(let closure): - return try encoder.subEncode { try closure(value, $0) } ?? emptyKeyedContainer + return try encoder.subEncode { try closure(value, $0.encoder) } ?? emptyKeyedContainer } } - public static func box(_ value: Date, encoder: Encoder) throws -> YAML { + public static func box(_ value: Date, encoder: IVE) throws -> YAML { switch encoder.options.dateEncodingStrategy { case .deferredToDate: - return try encoder.subEncode { try value.encode(to: $0) } ?? emptyKeyedContainer + return try encoder.subEncode { try value.encode(to: $0.encoder) } ?? emptyKeyedContainer case .secondsSince1970: - return .float(.init(value.timeIntervalSince1970.description), anchor: nil) + return .float(.init(value.timeIntervalSince1970.description)) case .millisecondsSince1970: - return .integer(.init((1000.0 * value.timeIntervalSince1970).description), anchor: nil) + return .integer(.init((1000.0 * value.timeIntervalSince1970).description)) case .iso8601: - return .string(_iso8601Formatter.string(from: value), style: .any, tag: nil, anchor: nil) + return .string(ZonedDate(date: value, timeZone: .utc).iso8601EncodedString()) case .formatted(let formatter): - return .string(formatter.string(from: value), style: .any, tag: nil, anchor: nil) + return .string(formatter.string(from: value)) case .custom(let closure): - return try encoder.subEncode { try closure(value, $0) } ?? emptyKeyedContainer + return try encoder.subEncode { try closure(value, $0.encoder) } ?? emptyKeyedContainer } } - public static func unkeyedValuesToValue(_ values: [YAML], encoder: Encoder) -> YAML { - return .sequence(values, style: .any, tag: nil, anchor: nil) + public static func box(_ value: AnyValue, encoder: IVE) throws -> YAML { + switch value { + case .nil: + return .null(anchor: nil) + case .bool(let value): + return try box(value, encoder: encoder) + case .int8(let value): + return try box(value, encoder: encoder) + case .int16(let value): + return try box(value, encoder: encoder) + case .int32(let value): + return try box(value, encoder: encoder) + case .int64(let value): + return try box(value, encoder: encoder) + case .integer(let value): + return try box(value, encoder: encoder) + case .uint8(let value): + return try box(value, encoder: encoder) + case .uint16(let value): + return try box(value, encoder: encoder) + case .uint32(let value): + return try box(value, encoder: encoder) + case .uint64(let value): + return try box(value, encoder: encoder) + case .unsignedInteger(let value): + return try box(value, encoder: encoder) + case .float16(let value): + return try box(value, encoder: encoder) + case .float(let value): + return try box(value, encoder: encoder) + case .double(let value): + return try box(value, encoder: encoder) + case .decimal(let value): + return try box(value, encoder: encoder) + case .string(let value): + return try box(value, encoder: encoder) + case .data(let value): + return try box(value, encoder: encoder) + case .url(let value): + return try box(value, encoder: encoder) + case .date(let value): + return try box(value, encoder: encoder) + case .uuid(let value): + return try box(value, encoder: encoder) + case .array(let value): + return .sequence(try value.map { try box($0, encoder: encoder) }) + case .dictionary(let value): + return .mapping(try value.map { + YAML.MappingEntry(key: try box($0, encoder: encoder), value: try box($1, encoder: encoder)) + }) + } } - public static func keyedValuesToValue(_ values: [String: YAML], encoder: Encoder) -> YAML { + public static func unkeyedValuesToValue(_ values: UnkeyedValues, encoder: IVE) -> YAML { + return .sequence(values) + } + + public static func keyedValuesToValue(_ values: KeyedValues, encoder: IVE) -> YAML { return .mapping( - values.map { .init(key: .string($0.key, style: .any, tag: nil, anchor: nil), value: $0.value) }, + values.map { YAML.MappingEntry(key: YAML($0), value: $1) }, style: .any, tag: nil, anchor: nil @@ -332,16 +362,6 @@ public struct YAMLEncoderTransform: InternalEncoderTransform, InternalValueSeria } -private let _iso8601Formatter: DateFormatter = { - let formatter = DateFormatter() - formatter.calendar = Calendar(identifier: .iso8601) - formatter.locale = Locale(identifier: "en_US_POSIX") - formatter.timeZone = TimeZone(secondsFromGMT: 0) - formatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSSXXXXX" - return formatter -}() - - #if canImport(Combine) import Combine diff --git a/Sources/PotentYAML/YAMLReader.swift b/Sources/PotentYAML/YAMLReader.swift index d381f3980..4410a4a0d 100644 --- a/Sources/PotentYAML/YAMLReader.swift +++ b/Sources/PotentYAML/YAMLReader.swift @@ -13,13 +13,35 @@ import Foundation public enum YAMLReader { - public static func read(data: Data) throws -> YAML.Array { - - var parseCfg = fy_parse_cfg(search_path: nil, flags: fy_parse_cfg_flags(rawValue: 0), userdata: nil, diag: nil) - - guard let parser = fy_parser_create(&parseCfg).map({ Parser(rawParser: $0) }) else { - fatalError("Unable to create parser") + public static func read(data: Data) throws -> YAML.Sequence { + + var diagCfg = + fy_diag_cfg( + fp: nil, + output_fn: nil, + user: nil, + level: FYET_ERROR, + module_mask: UInt32.max, + colorize: false, + show_source: false, + show_position: true, + show_type: false, + show_module: false, + source_width: Int32.max, + position_width: 4, + type_width: 0, + module_width: 0 + ) + let diag = fy_diag_create(&diagCfg) + fy_diag_set_collect_errors(diag, true) + defer { fy_diag_destroy(diag) } + + var parseCfg = fy_parse_cfg(search_path: nil, flags: FYPCF_QUIET, userdata: nil, diag: diag) + + guard let parser = fy_parser_create(&parseCfg).map({ Parser(rawParser: $0, rawDiag: diag) }) else { + throw YAML.Error.unableToCreateParser } + defer { parser.destroy() } return try data.withUnsafeBytes { (ptr: UnsafeRawBufferPointer) in @@ -35,9 +57,9 @@ public enum YAMLReader { } - static func stream(parser: Parser) throws -> YAML.Array { + static func stream(parser: Parser) throws -> YAML.Sequence { - var documents: YAML.Array = [] + var documents: YAML.Sequence = [] while true { @@ -45,7 +67,7 @@ public enum YAMLReader { defer { parser.free(event: event) } guard event.type != FYET_STREAM_END else { - return documents + break } switch event.type { @@ -53,10 +75,11 @@ public enum YAMLReader { documents.append(try document(parser: parser)) default: - throw YAML.Error.unexpectedEvent + throw parser.error(fallback: .unexpectedEvent) } } + return documents } @@ -83,7 +106,7 @@ public enum YAMLReader { defer { parser.free(event: event) } guard event.type != FYET_MAPPING_END else { - return result + break } let key = try value(event: event, parser: parser) @@ -96,12 +119,13 @@ public enum YAMLReader { result.append(YAML.Mapping.Element(key: key, value: val)) } + return result } - static func sequence(parser: Parser) throws -> YAML.Array { + static func sequence(parser: Parser) throws -> YAML.Sequence { - var result: YAML.Array = [] + var result: YAML.Sequence = [] while true { @@ -109,13 +133,15 @@ public enum YAMLReader { defer { parser.free(event: event) } guard event.type != FYET_SEQUENCE_END else { - return result + break } let val = try value(event: event, parser: parser) result.append(val) } + + return result } static let nullRegex = RegEx(pattern: #"^(null|Null|NULL|~)$"#)! @@ -126,62 +152,87 @@ public enum YAMLReader { static let infinityRegex = RegEx(pattern: #"^([-+]?(\.inf|\.Inf|\.INF))$"#)! static let nanRegex = RegEx(pattern: #"^(\.nan|\.NaN|\.NAN)$"#)! - static func scalar(value: String, style: fy_scalar_style, tag: YAML.Tag?, anchor: String?) -> YAML { + static func scalar(value: String, style: fy_scalar_style, tag: YAML.Tag?, anchor: String?) throws -> YAML { let stringStyle = YAML.StringStyle(rawValue: style.rawValue)! switch tag { case .none: + return try untaggedScalar(value: value, stringStyle: stringStyle, style: style, tag: tag, anchor: anchor) - if style == FYSS_PLAIN { + case .some(YAML.Tag.null): + return .null(anchor: anchor) - if nullRegex.matches(string: value) { - return .null(anchor: anchor) - } + case .some(YAML.Tag.bool): + return try boolTaggedScalar(value: value, anchor: anchor) - if trueRegex.matches(string: value) { - return .bool(true, anchor: anchor) - } + case .some(YAML.Tag.int): + return .integer(YAML.Number(value), anchor: anchor) - if falseRegex.matches(string: value) { - return .bool(false, anchor: anchor) - } + case .some(YAML.Tag.float): + return .float(YAML.Number(value), anchor: anchor) - if integerRegex.matches(string: value) { - return .integer(YAML.Number(value), anchor: anchor) - } + case .some(let tag): + return .string(value, style: stringStyle, tag: tag, anchor: anchor) + } - if floatRegex.matches(string: value) { - return .float(YAML.Number(value), anchor: anchor) - } + } - if infinityRegex.matches(string: value) { - return .float(YAML.Number(value), anchor: anchor) - } + static func untaggedScalar( + value: String, + stringStyle: YAML.StringStyle, + style: fy_scalar_style, + tag: YAML.Tag?, + anchor: String? + ) throws -> YAML { - if nanRegex.matches(string: value) { - return .float(YAML.Number(value), anchor: anchor) - } + if style == FYSS_PLAIN { + if nullRegex.matches(string: value) { + return .null(anchor: anchor) } - return .string(value, style: stringStyle, tag: nil, anchor: nil) + if trueRegex.matches(string: value) { + return .bool(true, anchor: anchor) + } - case .some(YAML.Tag.null): - return .null(anchor: anchor) + if falseRegex.matches(string: value) { + return .bool(false, anchor: anchor) + } - case .some(YAML.Tag.bool): - return .bool(Bool(value.lowercased())!, anchor: anchor) + if integerRegex.matches(string: value) { + return .integer(YAML.Number(value), anchor: anchor) + } - case .some(YAML.Tag.int): - return .integer(YAML.Number(value), anchor: anchor) + if floatRegex.matches(string: value) { + return .float(YAML.Number(value), anchor: anchor) + } - case .some(YAML.Tag.float): - return .float(YAML.Number(value), anchor: anchor) + if infinityRegex.matches(string: value) { + return .float(YAML.Number(value.lowercased()), anchor: anchor) + } - case .some(let tag): - return .string(value, style: stringStyle, tag: tag, anchor: anchor) + if nanRegex.matches(string: value) { + return .float(YAML.Number(value.lowercased()), anchor: anchor) + } + + } + + return .string(value, style: stringStyle, tag: nil, anchor: nil) + } + + static func boolTaggedScalar(value: String, anchor: String?) throws -> YAML { + + if let bool = Bool(value.lowercased()) { + return .bool(bool, anchor: anchor) + } + else if value == "1" { + return .bool(true, anchor: anchor) + } + else if value == "0" { + return .bool(false, anchor: anchor) } + throw YAML.Error.invalidTaggedBool } static func value(event: Parser.Event, parser: Parser) throws -> YAML { @@ -198,13 +249,13 @@ public enum YAMLReader { guard let (scalarValue, scalarStyle) = event.scalar else { return .null(anchor: event.anchor) } - return scalar(value: scalarValue, style: scalarStyle, tag: YAML.Tag(event.tag), anchor: event.anchor) + return try scalar(value: scalarValue, style: scalarStyle, tag: YAML.Tag(event.tag), anchor: event.anchor) case FYET_ALIAS: - return YAML.alias(event.tag ?? "") + return YAML.alias(event.anchor ?? "") default: - throw YAML.Error.unexpectedEvent + throw parser.error(fallback: .unexpectedEvent) } } @@ -218,26 +269,17 @@ public enum YAMLReader { var anchor: String? { guard let token = rawEvent.pointee.scalar.anchor else { return nil } - guard let anchor = String(token: token) else { - fatalError() - } - return anchor + return String(token: token) } var tag: String? { guard let token = rawEvent.pointee.scalar.tag else { return nil } - guard let anchor = String(token: token) else { - fatalError() - } - return anchor + return String(token: token) } var scalar: (String, fy_scalar_style)? { guard let token = rawEvent.pointee.scalar.value else { return nil } - guard let anchor = String(token: token) else { - fatalError() - } - return (anchor, fy_token_scalar_style(token)) + return String(token: token).map { ($0, fy_token_scalar_style(token)) } } var style: fy_node_style { @@ -247,6 +289,7 @@ public enum YAMLReader { } let rawParser: OpaquePointer + let rawDiag: OpaquePointer? func nextIfPresent() -> Event? { return fy_parser_parse(rawParser).map { Event(rawEvent: $0) } @@ -254,7 +297,7 @@ public enum YAMLReader { func next() throws -> Event { guard let event = nextIfPresent() else { - throw YAML.Error.unexpectedEOF + throw error(fallback: .unexpectedEOF) } return event } @@ -264,12 +307,25 @@ public enum YAMLReader { let event = try next() if event.type != eventType { - throw YAML.Error.unexpectedEvent + throw error(fallback: .unexpectedEvent) } return event } + func error(fallback: YAML.Error) -> YAML.Error { + guard let diag = rawDiag else { return fallback } + + var prev: UnsafeMutableRawPointer? + if let error = fy_diag_errors_iterate(diag, &prev) { + return YAML.Error.parserError(message: String(cString: error.pointee.msg), + line: Int(error.pointee.line), + column: Int(error.pointee.column)) + } + + return fallback + } + func destroy() { fy_parser_destroy(rawParser) } @@ -287,7 +343,7 @@ extension String { init?(token: OpaquePointer) { var tokenLen = 0 guard let tokenData = fy_token_get_text(token, &tokenLen) else { - fatalError() + return nil } self.init(data: Data(bytes: UnsafeRawPointer(tokenData), count: tokenLen), encoding: .utf8) } diff --git a/Sources/PotentYAML/YAMLSerialization.swift b/Sources/PotentYAML/YAMLSerialization.swift index 623ee7fc5..49998776a 100644 --- a/Sources/PotentYAML/YAMLSerialization.swift +++ b/Sources/PotentYAML/YAMLSerialization.swift @@ -13,7 +13,7 @@ import Foundation /// Convenience API for serializing and deserialization YAML items. /// -public struct YAMLSerialization { +public enum YAMLSerialization { /// Errors throws during serialization and deserialization /// @@ -22,10 +22,10 @@ public struct YAMLSerialization { /// possible public enum Error: Swift.Error {} - /// Deserialize YAML encoded `Data` object. + /// Deserialize YAML encoded `Data`. /// /// - Parameters: - /// - from: The `Data` value containing YAML encoded bytes + /// - from: The `Data` value containing YAML encoded data /// - Throws: /// - `YAMLSerialization.Error`: if any corrupted data is encountered /// - 'Swift.Error`: if any stream I/O error is encountered @@ -37,6 +37,23 @@ public struct YAMLSerialization { return .sequence(yamls, style: .any, tag: nil, anchor: nil) } + /// Deserialize YAML encoded `String`. + /// + /// - Parameters: + /// - from: The `String` value containing YAML data. + /// - Throws: + /// - `YAMLSerialization.Error`: if any corrupted data is encountered + /// - 'Swift.Error`: if any stream I/O error is encountered + public static func yaml(from string: String) throws -> YAML { + guard let data = string.data(using: .utf8) else { + throw DecodingError + .dataCorrupted( + DecodingError + .Context(codingPath: [], debugDescription: "String cannot be encoded as UTF-8", underlyingError: nil) + ) + } + return try yaml(from: data) + } public struct WritingOptions: OptionSet { public let rawValue: UInt @@ -64,6 +81,4 @@ public struct YAMLSerialization { return output } - private init() {} - } diff --git a/Sources/PotentYAML/YAMLWriter.swift b/Sources/PotentYAML/YAMLWriter.swift index c02199a67..b69987581 100644 --- a/Sources/PotentYAML/YAMLWriter.swift +++ b/Sources/PotentYAML/YAMLWriter.swift @@ -20,7 +20,7 @@ struct YAMLWriter { typealias Writer = (String?) -> Void - public static func write(_ documents: YAML.Array, sortedKeys: Bool = false, writer: @escaping Writer) throws { + public static func write(_ documents: YAML.Sequence, sortedKeys: Bool = false, writer: @escaping Writer) throws { func output( emitter: OpaquePointer?, @@ -130,7 +130,7 @@ struct YAMLWriter { emit(emitter: emitter, type: FYET_MAPPING_END) case .alias(let alias): - emit(emitter: emitter, type: FYET_ALIAS, args: alias) + emit(emitter: emitter, alias: alias) } } @@ -151,6 +151,15 @@ struct YAMLWriter { } } + private static func emit( + emitter: OpaquePointer, + alias: String + ) { + alias.withCString { aliasPtr in + emit(emitter: emitter, type: FYET_ALIAS, args: aliasPtr) + } + } + private static func emit(emitter: OpaquePointer, type: fy_event_type, args: CVarArg...) { withVaList(args) { valist in let event = fy_emit_event_vcreate(emitter, type, valist) diff --git a/Tests/.swiftlint.yml b/Tests/.swiftlint.yml index 92ec714de..d5ed36eca 100644 --- a/Tests/.swiftlint.yml +++ b/Tests/.swiftlint.yml @@ -3,7 +3,7 @@ disabled_rules: - function_body_length - type_body_length - identifier_name +- trailing_comma file_length: - - 1500 - - 2000 + - 3000 diff --git a/Tests/ASN1AnyStringTests.swift b/Tests/ASN1AnyStringTests.swift new file mode 100644 index 000000000..f50f61e2e --- /dev/null +++ b/Tests/ASN1AnyStringTests.swift @@ -0,0 +1,88 @@ +// +// ASN1AnyStringTests.swift +// PotentCodables +// +// Copyright © 2021 Outfox, inc. +// +// +// Distributed under the MIT License, See LICENSE for details. +// + +import Foundation +import PotentASN1 +import XCTest + + +class ASN1AnyStringTests: XCTestCase { + + func testInit() { + + let val0 = AnyString("test", kind: .utf8) + XCTAssertEqual(val0.storage, "test") + XCTAssertEqual(val0.kind, .utf8) + + let val1: AnyString = "test" + XCTAssertEqual(val1.storage, "test") + XCTAssertNil(val1.kind) + + let val2 = AnyString(String("test")) + XCTAssertEqual(val2.storage, "test") + XCTAssertNil(val2.kind) + + let val3 = "test".withCString { AnyString(cString: $0) } + XCTAssertEqual(val3.storage, "test") + XCTAssertNil(val3.kind) + } + + func testCodable() { + + XCTAssertEqual(try JSONEncoder().encode(AnyString("test")), #""test""#.data(using: .utf8)) + XCTAssertEqual(try JSONDecoder().decode(AnyString.self, from: #""test""#.data(using: .utf8)!), AnyString("test")) + } + + func testUppercased() { + XCTAssertEqual(AnyString("test").uppercased(), "TEST") + } + + func testLowercased() { + XCTAssertEqual(AnyString("TEST").lowercased(), "test") + } + + func testUTF8() { + XCTAssertEqual(Array(AnyString("test").utf8), Array("test".utf8)) + } + + func testUTF16() { + XCTAssertEqual(Array(AnyString("test").utf16), Array("test".utf16)) + } + + func testUnicodeScalars() { + XCTAssertEqual(Array(AnyString("test").unicodeScalars), Array("test".unicodeScalars)) + } + + func testCString() { + + XCTAssertEqual(AnyString("test").withCString { String(cString: $0) }, "test") + } + + func testIndices() { + + let str = AnyString("test") + XCTAssertEqual(str[str.startIndex], "t") + XCTAssertEqual(str[str.startIndex.. ASN1 { + return .tagged(0x35, Data()) + } + + } + + XCTAssertEqual( + try ASN1Decoder(schema: .integer()) + .decode(TaggedInt.self, from: Data([ASN1.Tag.integer.rawValue, 0x01, 0x01])), + TaggedInt(tag: 0x35, value: BigInt(1)) + ) + } + +} diff --git a/Tests/ASN1EncoderTests.swift b/Tests/ASN1EncoderTests.swift new file mode 100644 index 000000000..7cb499f6a --- /dev/null +++ b/Tests/ASN1EncoderTests.swift @@ -0,0 +1,372 @@ +// +// ASN1EncoderTests.swift +// PotentCodables +// +// Copyright © 2021 Outfox, inc. +// +// +// Distributed under the MIT License, See LICENSE for details. +// + +import BigInt +import Foundation +import PotentCodables +import PotentASN1 +import XCTest + + +class ASN1EncoderTests: XCTestCase { + + func testEncodeNull() throws { + + XCTAssertEqual( + try ASN1Encoder(schema: .null).encode(nil as String?), + Data([ASN1.Tag.null.rawValue, 0]) + ) + } + + func testEncodeBool() throws { + + XCTAssertEqual( + try ASN1Encoder(schema: .boolean()).encode(true), + Data([ASN1.Tag.boolean.rawValue, 0x01, 0xff]) + ) + XCTAssertEqual( + try ASN1Encoder(schema: .boolean()).encode(false), + Data([ASN1.Tag.boolean.rawValue, 0x01, 0x00]) + ) + } + + func testEncodeInt() throws { + + XCTAssertEqual( + try ASN1Encoder(schema: .integer()).encode(1), + Data([ASN1.Tag.integer.rawValue, 0x01, 0x01]) + ) + XCTAssertEqual( + try ASN1Encoder(schema: .integer()).encode(-1), + Data([ASN1.Tag.integer.rawValue, 0x01, 0xff]) + ) + } + + func testEncodeInt8() throws { + + XCTAssertEqual( + try ASN1Encoder(schema: .integer()).encode(Int8(1)), + Data([ASN1.Tag.integer.rawValue, 0x01, 0x01]) + ) + XCTAssertEqual( + try ASN1Encoder(schema: .integer()).encode(Int8(-1)), + Data([ASN1.Tag.integer.rawValue, 0x01, 0xff]) + ) + XCTAssertEqual( + try ASN1Encoder(schema: .integer()).encode(Int8.max), + Data([ASN1.Tag.integer.rawValue, 0x01, 0x7f]) + ) + XCTAssertEqual( + try ASN1Encoder(schema: .integer()).encode(Int8.min), + Data([ASN1.Tag.integer.rawValue, 0x01, 0x80]) + ) + } + + func testEncodeInt16() throws { + + XCTAssertEqual( + try ASN1Encoder(schema: .integer()).encode(Int16(1)), + Data([ASN1.Tag.integer.rawValue, 0x01, 0x01]) + ) + XCTAssertEqual( + try ASN1Encoder(schema: .integer()).encode(Int16(-1)), + Data([ASN1.Tag.integer.rawValue, 0x01, 0xff]) + ) + XCTAssertEqual( + try ASN1Encoder(schema: .integer()).encode(Int16.max), + Data([ASN1.Tag.integer.rawValue, 0x02, 0x7f, 0xff]) + ) + XCTAssertEqual( + try ASN1Encoder(schema: .integer()).encode(Int16.min), + Data([ASN1.Tag.integer.rawValue, 0x02, 0x80, 0x00]) + ) + } + + func testEncodeInt32() throws { + + XCTAssertEqual( + try ASN1Encoder(schema: .integer()).encode(Int32(1)), + Data([ASN1.Tag.integer.rawValue, 0x01, 0x01]) + ) + XCTAssertEqual( + try ASN1Encoder(schema: .integer()).encode(Int32(-1)), + Data([ASN1.Tag.integer.rawValue, 0x01, 0xff]) + ) + XCTAssertEqual( + try ASN1Encoder(schema: .integer()).encode(Int32.max), + Data([ASN1.Tag.integer.rawValue, 0x04, 0x7f, 0xff, 0xff, 0xff]) + ) + XCTAssertEqual( + try ASN1Encoder(schema: .integer()).encode(Int32.min), + Data([ASN1.Tag.integer.rawValue, 0x04, 0x80, 0x00, 0x00, 0x00]) + ) + } + + func testEncodeInt64() throws { + + XCTAssertEqual( + try ASN1Encoder(schema: .integer()).encode(Int64(1)), + Data([ASN1.Tag.integer.rawValue, 0x01, 0x01]) + ) + XCTAssertEqual( + try ASN1Encoder(schema: .integer()).encode(Int64(-1)), + Data([ASN1.Tag.integer.rawValue, 0x01, 0xff]) + ) + XCTAssertEqual( + try ASN1Encoder(schema: .integer()).encode(Int64.max), + Data([ASN1.Tag.integer.rawValue, 0x08, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff]) + ) + XCTAssertEqual( + try ASN1Encoder(schema: .integer()).encode(Int64.min), + Data([ASN1.Tag.integer.rawValue, 0x08, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]) + ) + } + + func testEncodeUInt() throws { + + XCTAssertEqual( + try ASN1Encoder(schema: .integer()).encode(UInt(1)), + Data([ASN1.Tag.integer.rawValue, 0x01, 0x01]) + ) + } + + func testEncodeUInt8() throws { + + XCTAssertEqual( + try ASN1Encoder(schema: .integer()).encode(UInt8(1)), + Data([ASN1.Tag.integer.rawValue, 0x01, 0x01]) + ) + XCTAssertEqual( + try ASN1Encoder(schema: .integer()).encode(UInt8.max), + Data([ASN1.Tag.integer.rawValue, 0x02, 0x00, 0xff]) + ) + } + + func testEncodeUInt16() throws { + + XCTAssertEqual( + try ASN1Encoder(schema: .integer()).encode(UInt16(1)), + Data([ASN1.Tag.integer.rawValue, 0x01, 0x01]) + ) + XCTAssertEqual( + try ASN1Encoder(schema: .integer()).encode(UInt16.max), + Data([ASN1.Tag.integer.rawValue, 0x03, 0x00, 0xff, 0xff]) + ) + } + + func testEncodeUInt32() throws { + + XCTAssertEqual( + try ASN1Encoder(schema: .integer()).encode(UInt32(1)), + Data([ASN1.Tag.integer.rawValue, 0x01, 0x01]) + ) + XCTAssertEqual( + try ASN1Encoder(schema: .integer()).encode(UInt32.max), + Data([ASN1.Tag.integer.rawValue, 0x05, 0x00, 0xff, 0xff, 0xff, 0xff]) + ) + } + + func testEncodeUInt64() throws { + + XCTAssertEqual( + try ASN1Encoder(schema: .integer()).encode(UInt64(1)), + Data([ASN1.Tag.integer.rawValue, 0x01, 0x01]) + ) + XCTAssertEqual( + try ASN1Encoder(schema: .integer()).encode(UInt64.max), + Data([ASN1.Tag.integer.rawValue, 0x09, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff]) + ) + } + + func testEncodeBigInt() throws { + + XCTAssertEqual( + try ASN1Encoder(schema: .integer()).encode(ASN1.Integer(1)), + Data([ASN1.Tag.integer.rawValue, 0x01, 0x01]) + ) + XCTAssertEqual( + try ASN1Encoder(schema: .integer()).encode(ASN1.Integer(-1)), + Data([ASN1.Tag.integer.rawValue, 0x01, 0xff]) + ) + XCTAssertEqual( + try ASN1Encoder(schema: .integer()).encode(ASN1.Integer(Int8.max)), + Data([ASN1.Tag.integer.rawValue, 0x01, 0x7f]) + ) + XCTAssertEqual( + try ASN1Encoder(schema: .integer()).encode(ASN1.Integer(Int8.min)), + Data([ASN1.Tag.integer.rawValue, 0x01, 0x80]) + ) + XCTAssertEqual( + try ASN1Encoder(schema: .integer()).encode(ASN1.Integer(Int64.max)), + Data([ASN1.Tag.integer.rawValue, 0x08, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff]) + ) + XCTAssertEqual( + try ASN1Encoder(schema: .integer()).encode(ASN1.Integer(Int64.min)), + Data([ASN1.Tag.integer.rawValue, 0x08, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]) + ) + } + + func testEncodeBigUInt() throws { + + XCTAssertEqual( + try ASN1Encoder(schema: .integer()).encode(BigUInt(1)), + Data([ASN1.Tag.integer.rawValue, 0x01, 0x01]) + ) + XCTAssertEqual( + try ASN1Encoder(schema: .integer()).encode(BigUInt(UInt8.max)), + Data([ASN1.Tag.integer.rawValue, 0x02, 0x00, 0xff]) + ) + XCTAssertEqual( + try ASN1Encoder(schema: .integer()).encode(BigUInt(UInt64.max)), + Data([ASN1.Tag.integer.rawValue, 0x09, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff]) + ) + } + + func testEncodeFloat() throws { + + XCTAssertEqual( + try ASN1Encoder(schema: .real).encode(Float(1.5)).hexEncodedString(), + Data([ASN1.Tag.real.rawValue, 0x04, 0x00, 0x31, 0x2E, 0x35]).hexEncodedString() + ) + } + + func testEncodeDouble() throws { + + XCTAssertEqual( + try ASN1Encoder(schema: .real).encode(Double(1.23)).hexEncodedString(), + Data([ASN1.Tag.real.rawValue, 0x05, 0x00, 0x31, 0x2E, 0x32, 0x33]).hexEncodedString() + ) + } + + func testEncodeDecimal() throws { + + XCTAssertEqual( + try ASN1Encoder(schema: .real).encode(Decimal(sign: .plus, exponent: -2, significand: 123)), + Data([ASN1.Tag.real.rawValue, 0x05, 0x00, 0x31, 0x2E, 0x32, 0x33]) + ) + } + + func testEncodeStrings() throws { + + XCTAssertEqual( + try ASN1Encoder(schema: .string(kind: .utf8)).encode("test"), + Data([ASN1.Tag.utf8String.rawValue, 0x4, 0x74, 0x65, 0x73, 0x74]) + ) + } + + func testEncodeUUID() throws { + + XCTAssertEqual( + try ASN1Encoder(schema: .string(kind: .utf8)).encode(UUID(uuidString: "353568D8-B232-4A00-BD76-338B8B3C7FB7")), + Data([ASN1.Tag.utf8String.rawValue, 0x24, + 0x33, 0x35, 0x33, 0x35, 0x36, 0x38, 0x44, 0x38, 0x2D, 0x42, 0x32, 0x33, + 0x32, 0x2D, 0x34, 0x41, 0x30, 0x30, 0x2D, 0x42, 0x44, 0x37, 0x36, 0x2D, + 0x33, 0x33, 0x38, 0x42, 0x38, 0x42, 0x33, 0x43, 0x37, 0x46, 0x42, 0x37]) + ) + } + + func testEncodeURL() throws { + + XCTAssertEqual( + try ASN1Encoder(schema: .string(kind: .utf8)).encode(URL(string: "https://example.com/some/thing")), + Data([ASN1.Tag.utf8String.rawValue, 0x1E, + 0x68, 0x74, 0x74, 0x70, 0x73, 0x3A, 0x2F, 0x2F, 0x65, 0x78, 0x61, 0x6D, + 0x70, 0x6C, 0x65, 0x2E, 0x63, 0x6F, 0x6D, 0x2F, 0x73, 0x6F, 0x6D, 0x65, + 0x2F, 0x74, 0x68, 0x69, 0x6E, 0x67]) + ) + } + + func testEncodeData() { + XCTAssertEqual( + try ASN1Encoder(schema: .octetString()).encode(Data(hexEncoded: "4CFD3C5A6D1B4B528476DA6EEF38C253")), + Data([ASN1.Tag.octetString.rawValue, 0x10, + 0x4C, 0xFD, 0x3C, 0x5A, 0x6D, 0x1B, 0x4B, 0x52, + 0x84, 0x76, 0xDA, 0x6E, 0xEF, 0x38, 0xC2, 0x53]) + ) + } + + func testEncodeGeneralizedDate() { + XCTAssertEqual( + try ASN1Encoder(schema: .time(kind: .generalized)) + .encode(ZonedDate(iso8601Encoded: "2022-12-11T10:09:08Z")!.utcDate), + Data([ASN1.Tag.generalizedTime.rawValue, 0x13, + 0x32, 0x30, 0x32, 0x32, 0x31, 0x32, 0x31, 0x31, + 0x31, 0x30, 0x30, 0x39, 0x30, 0x38, 0x2E, 0x30, 0x30, 0x30, 0x5A]) + ) + } + + func testEncodeUTCDate() { + XCTAssertEqual( + try ASN1Encoder(schema: .time(kind: .utc)) + .encode(ZonedDate(iso8601Encoded: "2022-12-11T10:09:08Z")!.utcDate), + Data([ASN1.Tag.utcTime.rawValue, 0x0d, + 0x32, 0x32, 0x31, 0x32, 0x31, 0x31, 0x31, 0x30, 0x30, 0x39, 0x30, 0x38, 0x5A]) + ) + } + + func testEncodeAnyString() { + XCTAssertEqual( + try ASN1Encoder(schema: .string(kind: .utf8)).encode(AnyString("Hello World", kind: .utf8)), + Data([ASN1.Tag.utf8String.rawValue, 0x0b, 0x48, 0x65, 0x6C, 0x6C, 0x6F, 0x20, 0x57, 0x6F, 0x72, 0x6C, 0x64]) + ) + } + + func testEncodeAnyTime() { + XCTAssertEqual( + try ASN1Encoder(schema: .time(kind: .utc)) + .encode(AnyTime(ZonedDate(iso8601Encoded: "2022-12-11T10:09:08Z")!, kind: .utc)), + Data([ASN1.Tag.utcTime.rawValue, 0x0d, + 0x32, 0x32, 0x31, 0x32, 0x31, 0x31, 0x31, 0x30, 0x30, 0x39, 0x30, 0x38, 0x5A]) + ) + } + + func testEncodeBitString() { + XCTAssertEqual( + try ASN1Encoder(schema: .bitString()).encode(BitString(octets: [0xfe, 0xff, 0xff, 0x7f])), + Data([ASN1.Tag.bitString.rawValue, 0x05, 0x01, 0x7f, 0xff, 0xff, 0xfe]) + ) + } + + func testEncodeObjectIdentifier() { + XCTAssertEqual( + try ASN1Encoder(schema: .objectIdentifier()).encode(ObjectIdentifier([1, 2, 3, 4])), + Data([ASN1.Tag.objectIdentifier.rawValue, 0x03, 0x2A, 0x03, 0x04]) + ) + } + + func testEncodeTagged() { + + struct TaggedInt: Equatable, Codable, Tagged { + var tag: ASN1.AnyTag = 0x35 + var int: BigInt + + init?(tag: ASN1.AnyTag, value: Any?) { + guard let int = value as? BigInt else { + return nil + } + self.int = int + } + + var value: Any? { int } + + func encode(schema: Schema) throws -> ASN1 { + return .tagged(0x35, try ASN1Encoder(schema: .integer()).encode(int)) + } + + } + + XCTAssertEqual( + try ASN1Encoder(schema: .implicit(0x35, .integer())).encode(TaggedInt(tag: 0x35, value: BigInt(1))), + Data([ASN1.Tag.tag(from: 0x35, in: .contextSpecific, constructed: false), + 0x03, ASN1.Tag.integer.rawValue, 0x01, 0x01]) + ) + } + +} diff --git a/Tests/ASN1SchemaDecodeTests.swift b/Tests/ASN1SchemaDecodeTests.swift new file mode 100644 index 000000000..bec98839c --- /dev/null +++ b/Tests/ASN1SchemaDecodeTests.swift @@ -0,0 +1,1146 @@ +// +// ASN1SchemaDecodeTests.swift +// PotentCodables +// +// Copyright © 2021 Outfox, inc. +// +// +// Distributed under the MIT License, See LICENSE for details. +// + +import Foundation +import PotentCodables +import PotentASN1 +import XCTest + + +class ASN1SchemaDecodeTests: XCTestCase { + + func testDecodeStructureWithDynamic() throws { + + struct TestValue: Codable, SchemaSpecified { + var a: Int + var b: ASN1? + + static var asn1Schema: Schema = .sequence([ + "a": .type(.integer()), + "b": .dynamic(unknownTypeSchema: .nothing, [ + .integer(1): .integer(), + .integer(2): .boolean(), + ]), + ]) + } + + let testValue = try ASN1Decoder.decode(TestValue.self, from: Data(hexEncoded: "300602010102017b")) + + XCTAssertEqual(testValue.a, 1) + XCTAssertEqual(testValue.b, .integer(123)) + + let testValue2 = try ASN1Decoder.decode(TestValue.self, from: Data(hexEncoded: "30060201020101ff")) + + XCTAssertEqual(testValue2.a, 2) + XCTAssertEqual(testValue2.b, .boolean(true)) + + let testValue3 = try ASN1Decoder.decode(TestValue.self, from: Data(hexEncoded: "3003020103")) + + XCTAssertEqual(testValue3.a, 3) + XCTAssertNil(testValue3.b) + } + + func testDecodeStructure() throws { + + struct TestValue: Codable, SchemaSpecified { + var a: Bool + var b: Int + + static var asn1Schema: Schema = .sequence([ + "a": .boolean(), + "b": .integer(), + ]) + + } + + let testValue = try ASN1Decoder.decode(TestValue.self, from: Data(hexEncoded: "30060101ff02017b")) + + XCTAssertEqual(testValue.a, true) + XCTAssertEqual(testValue.b, 123) + } + + func testDecodeStructureWhenFieldMissingWithDefault() throws { + + struct TestValue: Codable, SchemaSpecified { + var a: Bool + var b: Int + + static var asn1Schema: Schema = .sequence([ + "a": .boolean(default: false), + "b": .integer(), + ]) + + } + + let testValue = try ASN1Decoder.decode(TestValue.self, from: Data(hexEncoded: "300302017b")) + + XCTAssertEqual(testValue.a, false) + XCTAssertEqual(testValue.b, 123) + } + + func testDecodeStructureWhenFieldMissingWithDefaultReversed() throws { + + struct TestValue: Codable, SchemaSpecified { + var a: Bool + var b: Int + + static var asn1Schema: Schema = .sequence([ + "a": .boolean(), + "b": .integer(default: ASN1.Integer(1)), + ]) + + } + + let testValue = try ASN1Decoder.decode(TestValue.self, from: Data(hexEncoded: "30030101ff")) + + XCTAssertEqual(testValue.a, true) + XCTAssertEqual(testValue.b, 1) + } + + func testDecodeStructureFailsWhenFieldMissingWithNoDefault() throws { + + struct TestValue: Codable, SchemaSpecified { + var a: Bool + var b: Int + + static var asn1Schema: Schema = .sequence([ + "a": .boolean(), + "b": .integer(), + ]) + + } + + XCTAssertThrowsError(try ASN1Decoder.decode(TestValue.self, from: Data(hexEncoded: "300302017b"))) { error in + AssertDecodingKeyNotFound(error) + } + } + + func testDecodeStructureOf() throws { + + struct TestValue: Codable, SchemaSpecified { + var a: [Int] + + static var asn1Schema: Schema = .sequence([ + "a": .sequenceOf(.integer()), + ]) + + } + + let testValue = try ASN1Decoder.decode(TestValue.self, from: Data(hexEncoded: "300b3009020101020102020103")) + + XCTAssertEqual(testValue.a, [1, 2, 3]) + } + + func testDecodeStructureOfWithMaxAllowed() throws { + + struct TestValue: Codable, SchemaSpecified { + var a: [Int] + + static var asn1Schema: Schema = .sequence([ + "a": .sequenceOf(.integer(), size: .max(3)), + ]) + + } + + let testValue = try ASN1Decoder.decode(TestValue.self, from: Data(hexEncoded: "300b3009020101020102020103")) + + XCTAssertEqual(testValue.a, [1, 2, 3]) + } + + func testDecodeStructureOfWithMaxNotAllowed() throws { + + struct TestValue: Codable, SchemaSpecified { + var a: [Int] + + static var asn1Schema: Schema = .sequence([ + "a": .sequenceOf(.integer(), size: .max(2)), + ]) + + } + + XCTAssertThrowsError( + try ASN1Decoder.decode(TestValue.self, from: Data(hexEncoded: "300b3009020101020102020103")) + ) { error in + AssertValueOutOfRange(error) + } + } + + func testDecodeStructureOfWithMinAllowed() throws { + + struct TestValue: Codable, SchemaSpecified { + var a: [Int] + + static var asn1Schema: Schema = .sequence([ + "a": .sequenceOf(.integer(), size: .min(2)), + ]) + + } + + let testValue = try ASN1Decoder.decode(TestValue.self, from: Data(hexEncoded: "300b3009020101020102020103")) + + XCTAssertEqual(testValue.a, [1, 2, 3]) + } + + func testDecodeStructureOfWithMinNotAllowed() throws { + + struct TestValue: Codable, SchemaSpecified { + var a: [Int] + + static var asn1Schema: Schema = .sequence([ + "a": .sequenceOf(.integer(), size: .min(4)), + ]) + + } + + XCTAssertThrowsError( + try ASN1Decoder.decode(TestValue.self, from: Data(hexEncoded: "300b3009020101020102020103")) + ) { error in + AssertValueOutOfRange(error) + } + } + + func testDecodeStructureOfWithRangeAllowed() throws { + + struct TestValue: Codable, SchemaSpecified { + var a: [Int] + + static var asn1Schema: Schema = .sequence([ + "a": .sequenceOf(.integer(), size: .range(1, 3)), + ]) + + } + + let testValue = try ASN1Decoder.decode(TestValue.self, from: Data(hexEncoded: "300b3009020101020102020103")) + + XCTAssertEqual(testValue.a, [1, 2, 3]) + } + + func testDecodeStructureOfWithRangeNotAllowed() throws { + + struct TestValue: Codable, SchemaSpecified { + var a: [Int] + + static var asn1Schema: Schema = .sequence([ + "a": .sequenceOf(.integer(), size: .range(4, 5)), + ]) + + } + + XCTAssertThrowsError( + try ASN1Decoder.decode(TestValue.self, from: Data(hexEncoded: "300b3009020101020102020103")) + ) { error in + AssertValueOutOfRange(error) + } + } + + func testDecodeStructureOfWithExactAllowed() throws { + + struct TestValue: Codable, SchemaSpecified { + var a: [Int] + + static var asn1Schema: Schema = .sequence([ + "a": .sequenceOf(.integer(), size: .is(3)), + ]) + + } + + let testValue = try ASN1Decoder.decode(TestValue.self, from: Data(hexEncoded: "300b3009020101020102020103")) + + XCTAssertEqual(testValue.a, [1, 2, 3]) + } + + func testDecodeStructureOfWithExactNotAllowed() throws { + + struct TestValue: Codable, SchemaSpecified { + var a: [Int] + + static var asn1Schema: Schema = .sequence([ + "a": .sequenceOf(.integer(), size: .is(4)), + ]) + + } + + XCTAssertThrowsError( + try ASN1Decoder.decode(TestValue.self, from: Data(hexEncoded: "300b3009020101020102020103")) + ) { error in + AssertValueOutOfRange(error) + } + } + + func testDecodeSetOf() throws { + + struct TestValue: Codable, SchemaSpecified { + var a: [Int] + + static var asn1Schema: Schema = .sequence([ + "a": .setOf(.integer()), + ]) + + } + + let testValue = try ASN1Decoder.decode(TestValue.self, from: Data(hexEncoded: "300b3109020101020102020103")) + + XCTAssertEqual(testValue.a, [1, 2, 3]) + } + + func testDecodeSetOfWithMaxAllowed() throws { + + struct TestValue: Codable, SchemaSpecified { + var a: [Int] + + static var asn1Schema: Schema = .sequence([ + "a": .setOf(.integer(), size: .max(3)), + ]) + + } + + let testValue = try ASN1Decoder.decode(TestValue.self, from: Data(hexEncoded: "300b3109020101020102020103")) + + XCTAssertEqual(testValue.a, [1, 2, 3]) + } + + func testDecodeSetOfWithMaxNotAllowed() throws { + + struct TestValue: Codable, SchemaSpecified { + var a: [Int] + + static var asn1Schema: Schema = .sequence([ + "a": .setOf(.integer(), size: .max(2)), + ]) + + } + + XCTAssertThrowsError( + try ASN1Decoder.decode(TestValue.self, from: Data(hexEncoded: "300b3109020101020102020103")) + ) { error in + AssertValueOutOfRange(error) + } + } + + func testDecodeSetOfWithMinAllowed() throws { + + struct TestValue: Codable, SchemaSpecified { + var a: [Int] + + static var asn1Schema: Schema = .sequence([ + "a": .setOf(.integer(), size: .min(2)), + ]) + + } + + let testValue = try ASN1Decoder.decode(TestValue.self, from: Data(hexEncoded: "300b3109020101020102020103")) + + XCTAssertEqual(testValue.a, [1, 2, 3]) + } + + func testDecodeSetOfWithMinNotAllowed() throws { + + struct TestValue: Codable, SchemaSpecified { + var a: [Int] + + static var asn1Schema: Schema = .sequence([ + "a": .setOf(.integer(), size: .min(4)), + ]) + + } + + XCTAssertThrowsError( + try ASN1Decoder.decode(TestValue.self, from: Data(hexEncoded: "300b3109020101020102020103")) + ) { error in + AssertValueOutOfRange(error) + } + } + + func testDecodeSetOfWithRangeAllowed() throws { + + struct TestValue: Codable, SchemaSpecified { + var a: [Int] + + static var asn1Schema: Schema = .sequence([ + "a": .setOf(.integer(), size: .range(1, 3)), + ]) + + } + + let testValue = try ASN1Decoder.decode(TestValue.self, from: Data(hexEncoded: "300b3109020101020102020103")) + + XCTAssertEqual(testValue.a, [1, 2, 3]) + } + + func testDecodeSetOfWithRangeNotAllowed() throws { + + struct TestValue: Codable, SchemaSpecified { + var a: [Int] + + static var asn1Schema: Schema = .sequence([ + "a": .setOf(.integer(), size: .range(4, 5)), + ]) + + } + + XCTAssertThrowsError( + try ASN1Decoder.decode(TestValue.self, from: Data(hexEncoded: "300b3109020101020102020103")) + ) { error in + AssertValueOutOfRange(error) + } + } + + func testDecodeSetOfWithExactAllowed() throws { + + struct TestValue: Codable, SchemaSpecified { + var a: [Int] + + static var asn1Schema: Schema = .sequence([ + "a": .setOf(.integer(), size: .is(3)), + ]) + + } + + let testValue = try ASN1Decoder.decode(TestValue.self, from: Data(hexEncoded: "300b3109020101020102020103")) + + XCTAssertEqual(testValue.a, [1, 2, 3]) + } + + func testDecodeSetOfWithExactNotAllowed() throws { + + struct TestValue: Codable, SchemaSpecified { + var a: [Int] + + static var asn1Schema: Schema = .sequence([ + "a": .setOf(.integer(), size: .is(4)), + ]) + + } + + XCTAssertThrowsError( + try ASN1Decoder.decode(TestValue.self, from: Data(hexEncoded: "300b3109020101020102020103")) + ) { error in + AssertValueOutOfRange(error) + } + } + + func testDecodeObjectIdentifier() throws { + + struct TestValue: Codable, SchemaSpecified { + var a: ObjectIdentifier + + static var asn1Schema: Schema = .sequence([ + "a": .objectIdentifier(), + ]) + + } + + let testValue = try ASN1Decoder.decode(TestValue.self, from: Data(hexEncoded: "300506032a0304")) + + XCTAssertEqual(testValue.a, [1, 2, 3, 4]) + } + + func testDecodeObjectIdentifierWithAllowed() throws { + + struct TestValue: Codable, SchemaSpecified { + var a: ObjectIdentifier + + static var asn1Schema: Schema = .sequence([ + "a": .objectIdentifier(allowed: [ObjectIdentifier([1, 2, 3, 4])]), + ]) + + } + + let testValue = try ASN1Decoder.decode(TestValue.self, from: Data(hexEncoded: "300506032a0304")) + + XCTAssertEqual(testValue.a, [1, 2, 3, 4]) + } + + func testDecodeObjectIdentifierWithAllowedInvalid() throws { + + struct TestValue: Codable, SchemaSpecified { + var a: ObjectIdentifier + + static var asn1Schema: Schema = .sequence([ + "a": .objectIdentifier(allowed: [ObjectIdentifier([2, 3, 4])]), + ]) + + } + + XCTAssertThrowsError( + try ASN1Decoder.decode(TestValue.self, from: Data(hexEncoded: "300506032a0304")) + ) { error in + AssertDisallowedValue(error) + } + } + + func testDecodeBitString() throws { + + struct TestValue: Codable, SchemaSpecified { + var a: BitString + + static var asn1Schema: Schema = .sequence([ + "a": .bitString(), + ]) + + } + + let testValue = try ASN1Decoder.decode(TestValue.self, from: Data(hexEncoded: "30050303068040")) + + XCTAssertEqual(testValue.a, BitString(octets: [1, 2])) + } + + func testDecodeBitStringWithSize() throws { + + struct TestValue: Codable, SchemaSpecified { + var a: BitString + + static var asn1Schema: Schema = .sequence([ + "a": .bitString(size: .range(1, 15)), + ]) + + } + + let testValue = try ASN1Decoder.decode(TestValue.self, from: Data(hexEncoded: "30050303068040")) + + XCTAssertEqual(testValue.a, BitString(octets: [1, 2])) + } + + func testDecodeBitStringWithSizeInvalid() throws { + + struct TestValue: Codable, SchemaSpecified { + var a: BitString + + static var asn1Schema: Schema = .sequence([ + "a": .bitString(size: .min(17)), + ]) + + } + + XCTAssertThrowsError( + try ASN1Decoder.decode(TestValue.self, from: Data(hexEncoded: "30050303068040")) + ) { error in + AssertValueOutOfRange(error) + } + } + + func testDecodeOctetString() throws { + + struct TestValue: Codable, SchemaSpecified { + var a: Data + + static var asn1Schema: Schema = .sequence([ + "a": .octetString(), + ]) + + } + + let testValue = try ASN1Decoder.decode(TestValue.self, from: Data(hexEncoded: "300404020102")) + + XCTAssertEqual(testValue.a, Data([1, 2])) + } + + func testDecodeOctetStringWithSize() throws { + + struct TestValue: Codable, SchemaSpecified { + var a: Data + + static var asn1Schema: Schema = .sequence([ + "a": .octetString(size: .range(1, 2)), + ]) + + } + + let testValue = try ASN1Decoder.decode(TestValue.self, from: Data(hexEncoded: "300404020102")) + + XCTAssertEqual(testValue.a, Data([1, 2])) + } + + func testDecodeOctetStringWithSizeInvalid() throws { + + struct TestValue: Codable, SchemaSpecified { + var a: Data + + static var asn1Schema: Schema = .sequence([ + "a": .octetString(size: .min(5)), + ]) + + } + + XCTAssertThrowsError( + try ASN1Decoder.decode(TestValue.self, from: Data(hexEncoded: "300404020102")) + ) { error in + AssertValueOutOfRange(error) + } + } + + func testDecodeInteger() throws { + + struct TestValue: Codable, SchemaSpecified { + var a: Int + + static var asn1Schema: Schema = .sequence([ + "a": .integer(), + ]) + + } + + let testValue = try ASN1Decoder.decode(TestValue.self, from: Data(hexEncoded: "300302017b")) + + XCTAssertEqual(testValue.a, 123) + } + + func testDecodeIntegerWithAllowed() throws { + + struct TestValue: Codable, SchemaSpecified { + var a: Int + + static var asn1Schema: Schema = .sequence([ + "a": .integer(allowed: ASN1.Integer(123) ..< ASN1.Integer(124)), + ]) + + } + + let testValue = try ASN1Decoder.decode(TestValue.self, from: Data(hexEncoded: "300302017b")) + + XCTAssertEqual(testValue.a, 123) + } + + func testDecodeIntegerWithAllowedInvalid() throws { + + struct TestValue: Codable, SchemaSpecified { + var a: Int + + static var asn1Schema: Schema = .sequence([ + "a": .integer(allowed: ASN1.Integer(0) ..< ASN1.Integer(10)), + ]) + + } + + XCTAssertThrowsError( + try ASN1Decoder.decode(TestValue.self, from: Data(hexEncoded: "300302017b")) + ) { error in + AssertDisallowedValue(error) + } + } + + func testDecodeGeneralizedTime() throws { + + struct TestValue: Codable, SchemaSpecified { + var a: Date + + static var asn1Schema: Schema = .sequence([ + "a": .time(kind: .generalized), + ]) + + } + + let testValue = try ASN1Decoder.decode(TestValue.self, + from: Data(hexEncoded: "3015181332303233303230323138353933362e3730395a")) + + XCTAssertEqual(testValue.a, Date(timeIntervalSince1970: 1675364376.709)) + } + + func testDecodeGeneralizedTimeWrongKind() throws { + + struct TestValue: Codable, SchemaSpecified { + var a: Date + + static var asn1Schema: Schema = .sequence([ + "a": .time(kind: .utc), + ]) + + } + + XCTAssertThrowsError( + try ASN1Decoder.decode(TestValue.self, from: Data(hexEncoded: "3015181332303233303230323138353933362e3730395a")) + ) { error in + AssertDecodingKeyNotFound(error) + } + } + + func testDecodeUTCTime() throws { + + struct TestValue: Codable, SchemaSpecified { + var a: Date + + static var asn1Schema: Schema = .sequence([ + "a": .time(kind: .utc), + ]) + + } + + let testValue = try ASN1Decoder.decode(TestValue.self, + from: Data(hexEncoded: "300f170d3233303230323139323131315a")) + + XCTAssertEqual(testValue.a, Date(timeIntervalSince1970: 1675365671.0)) + } + + func testDecodeUTCTimeWrongKind() throws { + + struct TestValue: Codable, SchemaSpecified { + var a: Date + + static var asn1Schema: Schema = .sequence([ + "a": .time(kind: .generalized), + ]) + + } + + XCTAssertThrowsError( + try ASN1Decoder.decode(TestValue.self, from: Data(hexEncoded: "300f170d3233303230323139323131315a")) + ) { error in + AssertDecodingKeyNotFound(error) + } + } + + func testDecodeString() throws { + + struct TestValue: Codable, SchemaSpecified { + var a: String + + static var asn1Schema: Schema = .sequence([ + "a": .string(kind: .utf8), + ]) + + } + + let testValue = try ASN1Decoder.decode(TestValue.self, + from: Data(hexEncoded: "300d0c0b48656c6c6f20576f726c64")) + + XCTAssertEqual(testValue.a, "Hello World") + } + + func testDecodeStringWrongKind() throws { + + struct TestValue: Codable, SchemaSpecified { + var a: String + + static var asn1Schema: Schema = .sequence([ + "a": .string(kind: .printable), + ]) + + } + + XCTAssertThrowsError( + try ASN1Decoder.decode(TestValue.self, from: Data(hexEncoded: "300d0c0b48656c6c6f20576f726c64")) + ) { error in + AssertDecodingKeyNotFound(error) + } + } + + func testDecodeStringWithSize() throws { + + struct TestValue: Codable, SchemaSpecified { + var a: String + + static var asn1Schema: Schema = .sequence([ + "a": .string(kind: .utf8, size: .min(3)), + ]) + + } + + let testValue = try ASN1Decoder.decode(TestValue.self, + from: Data(hexEncoded: "300d0c0b48656c6c6f20576f726c64")) + + XCTAssertEqual(testValue.a, "Hello World") + } + + func testDecodeStringWithSizeInvalid() throws { + + struct TestValue: Codable, SchemaSpecified { + var a: String + + static var asn1Schema: Schema = .sequence([ + "a": .string(kind: .utf8, size: .max(3)), + ]) + + } + + XCTAssertThrowsError( + try ASN1Decoder.decode(TestValue.self, from: Data(hexEncoded: "300d0c0b48656c6c6f20576f726c64")) + ) { error in + AssertValueOutOfRange(error) + } + } + + func testDecodeImplictTaggedInteger() throws { + + struct TestValue: Codable, SchemaSpecified { + var a: Int + + static var asn1Schema: Schema = .sequence([ + "a": .implicit(1, .integer()), + ]) + + } + + let testValue = try ASN1Decoder.decode(TestValue.self, from: Data(hexEncoded: "300381017b")) + + XCTAssertEqual(testValue.a, 123) + } + + func testDecodeImplictTaggedSequenceOf() throws { + + struct TestValue: Codable, SchemaSpecified { + var a: [Int] + + static var asn1Schema: Schema = .sequence([ + "a": .implicit(1, .sequenceOf(.integer())), + ]) + + } + + let testValue = try ASN1Decoder.decode(TestValue.self, from: Data(hexEncoded: "300ba109020101020102020103")) + + XCTAssertEqual(testValue.a, [1, 2, 3]) + } + + func testDecodeImplictTaggedSetOf() throws { + + struct TestValue: Codable, SchemaSpecified { + var a: [Int] + + static var asn1Schema: Schema = .sequence([ + "a": .implicit(1, .setOf(.integer())), + ]) + + } + + let testValue = try ASN1Decoder.decode(TestValue.self, from: Data(hexEncoded: "300ba109020101020102020103")) + + XCTAssertEqual(testValue.a, [1, 2, 3]) + } + + func testDecodeExplictTaggedInteger() throws { + + struct TestValue: Codable, SchemaSpecified { + var a: Int + + static var asn1Schema: Schema = .sequence([ + "a": .explicit(1, .integer()), + ]) + + } + + let testValue = try ASN1Decoder.decode(TestValue.self, from: Data(hexEncoded: "3005a10302017b")) + + XCTAssertEqual(testValue.a, 123) + } + + func testDecodeExplictTaggedSequenceOf() throws { + + struct TestValue: Codable, SchemaSpecified { + var a: [Int] + + static var asn1Schema: Schema = .sequence([ + "a": .explicit(1, .sequenceOf(.integer())), + ]) + + } + + let testValue = try ASN1Decoder.decode(TestValue.self, from: Data(hexEncoded: "300da10b3009020101020102020103")) + + XCTAssertEqual(testValue.a, [1, 2, 3]) + } + + func testDecodeExplictTaggedSetOf() throws { + + struct TestValue: Codable, SchemaSpecified { + var a: [Int] + + static var asn1Schema: Schema = .sequence([ + "a": .explicit(1, .setOf(.integer())), + ]) + + } + + let testValue = try ASN1Decoder.decode(TestValue.self, from: Data(hexEncoded: "300da10b3109020101020102020103")) + + XCTAssertEqual(testValue.a, [1, 2, 3]) + } + + func testDecodeChoiceWithLotsOfOptions() throws { + + struct TestValue: Codable, SchemaSpecified { + var a: String + + static var asn1Schema: Schema = .sequence([ + "a": .choiceOf([ + .sequenceOf(.integer()), + .setOf(.integer()), + .null, + .objectIdentifier(), + .boolean(), + .bitString(), + .octetString(), + .boolean(), + .integer(), + .time(kind: .utc), + .real, + .implicit(1, .integer()), + .explicit(2, .integer()), + .string(kind: .utf8), + ]) + ]) + + } + + let testValue = try ASN1Decoder.decode(TestValue.self, from: Data(hexEncoded: "30050c03616263")) + + XCTAssertEqual(testValue.a, "abc") + } + + func testDecodeChoiceWithLotsOfOptions2() throws { + + struct TestValue: Codable, SchemaSpecified { + var a: Bool + + static var asn1Schema: Schema = .sequence([ + "a": .choiceOf([ + .sequence([:]), + .sequenceOf(.integer()), + .setOf(.integer()), + .null, + .objectIdentifier(), + .bitString(), + .octetString(), + .integer(), + .time(kind: .utc), + .string(kind: .utf8), + .real, + .implicit(1, .integer()), + .explicit(2, .integer()), + .boolean(), + ]) + ]) + + } + + let testValue = try ASN1Decoder.decode(TestValue.self, from: Data(hexEncoded: "30030101ff")) + + XCTAssertEqual(testValue.a, true) + } + + func testDecodeImplicitAmbiguousSchemaThrowsError() throws { + + let schema: Schema = .implicit(1, .choiceOf([.boolean(), .integer()])) + + XCTAssertThrowsError( + try ASN1Decoder(schema: schema).decode(Bool.self, from: Data(hexEncoded: "8101ff")) + ) { error in + AssertAmbiguousImplicitTag(error) + } + } + + func testDecodeExplicitAmbiguousSchema() throws { + + let schema: Schema = .explicit(1, .choiceOf([.boolean(), .integer()])) + + let testValue = try ASN1Decoder(schema: schema).decode(Bool.self, from: Data(hexEncoded: "a1030101ff")) + + XCTAssertEqual(testValue, true) + } + + func testDecodeExplicitWithDefault() throws { + + let schema: Schema = .explicit(1, .boolean(default: true)) + + let testValue = try ASN1Decoder(schema: schema).decode(Bool.self, from: Data(hexEncoded: "a100")) + + XCTAssertEqual(testValue, true) + } + + func testDecodeExplicitWithInvalidData() throws { + + let schema: Schema = .explicit(1, .boolean()) + + XCTAssertThrowsError( + try ASN1Decoder(schema: schema).decode(Bool.self, from: Data(hexEncoded: "a100")) + ) { error in + AssertBadValue(error) + } + } + + func testDecodeAny() throws { + + let testValue = try ASN1Decoder(schema: .any).decode(Bool.self, from: Data(hexEncoded: "0101ff")) + + XCTAssertEqual(testValue, true) + } + + func testDecodeVersioned() throws { + + struct TestValue: Codable, SchemaSpecified { + var ver: Int + var name1: String? + var name2: String? + + static var asn1Schema: Schema = .sequence([ + "ver": .version(.integer(allowed: 1..<3)), + "name1": .versioned(range: 1...1, .string(kind: .utf8)), + "name2": .versioned(range: 2...2, .string(kind: .printable)), + ]) + } + + let testValue1 = try ASN1Decoder.decode(TestValue.self, from: Data(hexEncoded: "30090201010c0474657374")) + + XCTAssertEqual(testValue1.ver, 1) + XCTAssertEqual(testValue1.name1, "test") + XCTAssertNil(testValue1.name2) + + let testValue2 = try ASN1Decoder.decode(TestValue.self, from: Data(hexEncoded: "3009020102130474657374")) + + XCTAssertEqual(testValue2.ver, 2) + XCTAssertEqual(testValue2.name2, "test") + XCTAssertNil(testValue2.name1) + } + + func testDecodeTaggedVersioned() throws { + + struct TestValue: Codable, SchemaSpecified { + var ver: Int + var name1: String? + var name2: String? + + static var asn1Schema: Schema = .sequence([ + "ver": .version(.implicit(1, .integer(allowed: 1..<3))), + "name1": .versioned(range: 1...1, .string(kind: .utf8)), + "name2": .versioned(range: 2...2, .string(kind: .printable)), + ]) + } + + let testValue1 = try ASN1Decoder.decode(TestValue.self, from: Data(hexEncoded: "30098101010c0474657374")) + + XCTAssertEqual(testValue1.ver, 1) + XCTAssertEqual(testValue1.name1, "test") + XCTAssertNil(testValue1.name2) + } + + func testDecodeTaggedVersionedDefault() throws { + + struct TestValue: Codable, SchemaSpecified { + var ver: Int + var name1: String? + var name2: String? + + static var asn1Schema: Schema = .sequence([ + "ver": .version(.implicit(1, .integer(allowed: 1..<3, default: 1))), + "name1": .versioned(range: 1...1, .string(kind: .utf8)), + "name2": .versioned(range: 2...2, .string(kind: .printable)), + ]) + } + + let testValue1 = try ASN1Decoder.decode(TestValue.self, from: Data(hexEncoded: "30060c0474657374")) + + XCTAssertEqual(testValue1.ver, 1) + XCTAssertEqual(testValue1.name1, "test") + XCTAssertNil(testValue1.name2) + } + + func testDecodeVersionedWithNoVersion() throws { + + struct TestValue: Codable, SchemaSpecified { + var ver: Int + var name1: String? + var name2: String? + + static var asn1Schema: Schema = .sequence([ + "ver": .integer(allowed: 1..<3), + "name1": .versioned(range: 1...1, .string(kind: .utf8)), + "name2": .versioned(range: 2...2, .string(kind: .printable)), + ]) + } + + XCTAssertThrowsError(try TestValue(ver: 1).encoded()) { error in + AssertNoVersionDefined(error) + } + } + + func testDecodeOptional() throws { + + struct TestValue: Codable, Equatable, SchemaSpecified { + var a: String? + + static var asn1Schema: Schema = .sequence([ + "a": .optional(.string(kind: .utf8)), + ]) + + } + + XCTAssertEqual( + try ASN1Decoder.decode(TestValue.self, from: Data(hexEncoded: "300d0c0b48656c6c6f20576f726c64")), + TestValue(a: "Hello World") + ) + + XCTAssertEqual( + try ASN1Decoder.decode(TestValue.self, from: Data(hexEncoded: "3000")), + TestValue(a: nil) + ) + } + +} + + +private func AssertAmbiguousImplicitTag(_ error: Error, file: StaticString = #file, line: UInt = #line) { + func check() -> Bool { + if case SchemaState.SchemaError.ambiguousImplicitTag = error { + return true + } + else { + return false + } + } + XCTAssertTrue(check(), file: file, line: line) +} + +private func AssertBadValue(_ error: Error, file: StaticString = #file, line: UInt = #line) { + func check() -> Bool { + if case SchemaState.DecodingError.badValue = error { + return true + } + else { + return false + } + } + XCTAssertTrue(check(), file: file, line: line) +} + + +private func AssertValueOutOfRange(_ error: Error, file: StaticString = #file, line: UInt = #line) { + func check() -> Bool { + if case SchemaState.DecodingError.valueOutOfRange = error { + return true + } + else { + return false + } + } + XCTAssertTrue(check(), file: file, line: line) +} + +private func AssertDisallowedValue(_ error: Error, file: StaticString = #file, line: UInt = #line) { + func check() -> Bool { + if case SchemaState.DecodingError.disallowedValue = error { + return true + } + else { + return false + } + } + XCTAssertTrue(check(), file: file, line: line) +} + +private func AssertNoVersionDefined(_ error: Error, file: StaticString = #file, line: UInt = #line) { + func check() -> Bool { + if case SchemaState.SchemaError.noVersionDefined = error { + return true + } + else { + return false + } + } + XCTAssertTrue(check(), file: file, line: line) +} diff --git a/Tests/ASN1SchemaEncodeTests.swift b/Tests/ASN1SchemaEncodeTests.swift new file mode 100644 index 000000000..3736948e5 --- /dev/null +++ b/Tests/ASN1SchemaEncodeTests.swift @@ -0,0 +1,1144 @@ +// +// ASN1SchemaEncodeTests.swift +// PotentCodables +// +// Copyright © 2021 Outfox, inc. +// +// +// Distributed under the MIT License, See LICENSE for details. +// + +import BigInt +import Foundation +import PotentCodables +import PotentASN1 +import XCTest + + +class ASN1SchemaEncodeTests: XCTestCase { + + func testEncodeStructureWithDynamic() throws { + + struct TestValue: Codable, SchemaSpecified { + var a: Int + var b: ASN1? + + static var asn1Schema: Schema = .sequence([ + "a": .type(.integer()), + "b": .dynamic(unknownTypeSchema: .nothing, [ + .integer(1): .integer(), + .integer(2): .boolean(), + ]), + ]) + } + + XCTAssertEqual( + try ASN1Encoder.encode(TestValue(a: 1, b: .integer(123))), + Data(hexEncoded: "300602010102017b") + ) + + XCTAssertEqual( + try ASN1Encoder.encode(TestValue(a: 2, b: .boolean(true))), + Data(hexEncoded: "30060201020101ff") + ) + + XCTAssertEqual( + try ASN1Encoder.encode(TestValue(a: 3, b: nil)), + Data(hexEncoded: "3003020103") + ) + } + + func testEncodeStructureWithDynamicToNothing() throws { + + struct TestValue: Codable, SchemaSpecified { + var a: Int + var b: ASN1? + + static var asn1Schema: Schema = .sequence([ + "a": .type(.integer()), + "b": .dynamic([ + .integer(1): .integer(), + .integer(2): .nothing, + ]), + ]) + } + + let testValue = try ASN1Decoder.decode(TestValue.self, from: Data(hexEncoded: "3003020102")) + + XCTAssertEqual(testValue.a, 2) + XCTAssertEqual(testValue.b, nil) + } + + func testEncodeStructureWithDynamicNoTypeDefinedFails() throws { + + struct TestValue: Codable, SchemaSpecified { + var a: Int + var b: ASN1? + + static var asn1Schema: Schema = .sequence([ + "a": .integer(), + "b": .dynamic(unknownTypeSchema: .nothing, [ + .integer(1): .integer(), + .integer(2): .boolean(), + ]), + ]) + } + + XCTAssertThrowsError(try TestValue(a: 1).encoded()) { error in + AssertNoDynamicTypeDefined(error) + } + } + + func testEncodeStructureWithUnkownDynamicValue() throws { + + struct TestValue: Codable, SchemaSpecified { + var a: Int + var b: ASN1? + + static var asn1Schema: Schema = .sequence([ + "a": .type(.integer()), + "b": .dynamic([ + .integer(1): .integer(), + .integer(2): .boolean(), + ]), + ]) + } + + XCTAssertThrowsError(try TestValue(a: 5).encoded()) { error in + AssertUnknownDynamicValue(error) + } + } + + func testEncodeStructure() throws { + + struct TestValue: Codable, SchemaSpecified { + var a: Bool + var b: Int + + static var asn1Schema: Schema = .sequence([ + "a": .boolean(), + "b": .integer(), + ]) + + } + + XCTAssertEqual( + try TestValue(a: true, b: 123).encoded(), + Data(hexEncoded: "30060101ff02017b") + ) + } + + func testEncodeStructureWithDefault() throws { + + struct TestValue: Codable, SchemaSpecified { + var a: Bool + var b: Int + + static var asn1Schema: Schema = .sequence([ + "a": .boolean(default: false), + "b": .integer(), + ]) + + } + + XCTAssertEqual( + try TestValue(a: false, b: 123).encoded(), + Data(hexEncoded: "300302017b") + ) + } + + func testEncodeStructureWithDefaultReversed() throws { + + struct TestValue: Codable, SchemaSpecified { + var a: Bool + var b: Int + + static var asn1Schema: Schema = .sequence([ + "a": .boolean(), + "b": .integer(default: ASN1.Integer(1)), + ]) + + } + + XCTAssertEqual( + try TestValue(a: true, b: 1).encoded(), + Data(hexEncoded: "30030101ff") + ) + } + + func testEncodeStructureWithDefaultedMissingValue() throws { + + struct TestValue: Codable, SchemaSpecified { + var a: Bool + + static var asn1Schema: Schema = .sequence([ + "a": .boolean(), + "b": .integer(default: 123), + ]) + + } + + XCTAssertEqual( + try TestValue(a: true).encoded(), + Data(hexEncoded: "30030101ff") + ) + } + + func testEncodeStructureOf() throws { + + struct TestValue: Codable, SchemaSpecified { + var a: [Int] + + static var asn1Schema: Schema = .sequence([ + "a": .sequenceOf(.integer()), + ]) + + } + + XCTAssertEqual( + try TestValue(a: [1, 2, 3]).encoded(), + Data(hexEncoded: "300b3009020101020102020103") + ) + } + + func testEncodeStructureOfWithMaxAllowed() throws { + + struct TestValue: Codable, SchemaSpecified { + var a: [Int] + + static var asn1Schema: Schema = .sequence([ + "a": .sequenceOf(.integer(), size: .max(3)), + ]) + + } + + XCTAssertEqual( + try TestValue(a: [1, 2, 3]).encoded(), + Data(hexEncoded: "300b3009020101020102020103") + ) + } + + func testEncodeStructureOfWithMaxNotAllowed() throws { + + struct TestValue: Codable, SchemaSpecified { + var a: [Int] + + static var asn1Schema: Schema = .sequence([ + "a": .sequenceOf(.integer(), size: .max(2)), + ]) + + } + + XCTAssertThrowsError( + try TestValue(a: [1, 2, 3]).encoded() + ) { error in + AssertValueOutOfRange(error) + } + } + + func testEncodeSetOf() throws { + + struct TestValue: Codable, SchemaSpecified { + var a: [Int] + + static var asn1Schema: Schema = .sequence([ + "a": .setOf(.integer()), + ]) + + } + + XCTAssertEqual( + try TestValue(a: [1, 2, 3]).encoded(), + Data(hexEncoded: "300b3109020101020102020103") + ) + } + + func testEncodeSetOfWithMaxAllowed() throws { + + struct TestValue: Codable, SchemaSpecified { + var a: [Int] + + static var asn1Schema: Schema = .sequence([ + "a": .setOf(.integer(), size: .max(3)), + ]) + + } + + XCTAssertEqual( + try TestValue(a: [1, 2, 3]).encoded(), + Data(hexEncoded: "300b3109020101020102020103") + ) + } + + func testEncodeSetOfWithMaxNotAllowed() throws { + + struct TestValue: Codable, SchemaSpecified { + var a: [Int] + + static var asn1Schema: Schema = .sequence([ + "a": .setOf(.integer(), size: .max(2)), + ]) + + } + + XCTAssertThrowsError( + try TestValue(a: [1, 2, 3]).encoded() + ) { error in + AssertValueOutOfRange(error) + } + } + + func testEncodeObjectIdentifier() throws { + + struct TestValue: Codable, SchemaSpecified { + var a: ObjectIdentifier + + static var asn1Schema: Schema = .sequence([ + "a": .objectIdentifier(), + ]) + + } + + XCTAssertEqual( + try TestValue(a: [1, 2, 3, 4]).encoded(), + Data(hexEncoded: "300506032a0304") + ) + } + + func testEncodeObjectIdentifierWithAllowed() throws { + + struct TestValue: Codable, SchemaSpecified { + var a: ObjectIdentifier + + static var asn1Schema: Schema = .sequence([ + "a": .objectIdentifier(allowed: [ObjectIdentifier([1, 2, 3, 4])]), + ]) + + } + + XCTAssertEqual( + try TestValue(a: [1, 2, 3, 4]).encoded(), + Data(hexEncoded: "300506032a0304") + ) + } + + func testEncodeObjectIdentifierWithAllowedInvalid() throws { + + struct TestValue: Codable, SchemaSpecified { + var a: ObjectIdentifier + + static var asn1Schema: Schema = .sequence([ + "a": .objectIdentifier(allowed: [ObjectIdentifier([2, 3, 4])]), + ]) + + } + + XCTAssertThrowsError( + try TestValue(a: [1, 2, 3, 4]).encoded() + ) { error in + AssertDisallowedValue(error) + } + } + + func testEncodeBitString() throws { + + struct TestValue: Codable, SchemaSpecified { + var a: BitString + + static var asn1Schema: Schema = .sequence([ + "a": .bitString(), + ]) + + } + + XCTAssertEqual( + try TestValue(a: BitString(octets: [1, 2])).encoded(), + Data(hexEncoded: "30050303068040") + ) + + XCTAssertEqual( + try ASN1Encoder(schema: .bitString()).encode(Data([0x7f, 0xfe])), + Data(hexEncoded: "0303007ffe") + ) + XCTAssertEqual( + try ASN1Encoder(schema: .bitString()).encode(Int8(bitPattern: 0xfe)), + Data(hexEncoded: "0302007f") + ) + XCTAssertEqual( + try ASN1Encoder(schema: .bitString()).encode(Int16(bitPattern: 0xfe7f)), + Data(hexEncoded: "030300fe7f") + ) + XCTAssertEqual( + try ASN1Encoder(schema: .bitString()).encode(Int32(bitPattern: 0xfeffff7f)), + Data(hexEncoded: "030500feffff7f") + ) + XCTAssertEqual( + try ASN1Encoder(schema: .bitString()).encode(Int64(bitPattern: 0xfeffffffffffff7f)), + Data(hexEncoded: "030900feffffffffffff7f") + ) + XCTAssertEqual( + try ASN1Encoder(schema: .bitString()).encode(UInt8(0xfe)), + Data(hexEncoded: "0302007f") + ) + XCTAssertEqual( + try ASN1Encoder(schema: .bitString()).encode(UInt16(0xfe7f)), + Data(hexEncoded: "030300fe7f") + ) + XCTAssertEqual( + try ASN1Encoder(schema: .bitString()).encode(UInt32(0xfeffff7f)), + Data(hexEncoded: "030500feffff7f") + ) + XCTAssertEqual( + try ASN1Encoder(schema: .bitString()).encode(UInt64(0xfeffffffffffff7f)), + Data(hexEncoded: "030900feffffffffffff7f") + ) + } + + func testEncodeBitStringWithSizeInvalid() throws { + + struct TestValue: Codable, SchemaSpecified { + var a: BitString + + static var asn1Schema: Schema = .sequence([ + "a": .bitString(size: .min(17)), + ]) + + } + + XCTAssertThrowsError( + try TestValue(a: BitString(octets: [1, 2])).encoded() + ) { error in + AssertValueOutOfRange(error) + } + } + + func testEncodeOctetString() throws { + + struct TestValue: Codable, SchemaSpecified { + var a: Data + + static var asn1Schema: Schema = .sequence([ + "a": .octetString(), + ]) + + } + + XCTAssertEqual( + try TestValue(a: Data([1, 2])).encoded(), + Data(hexEncoded: "300404020102") + ) + + XCTAssertEqual( + try ASN1Encoder(schema: .octetString()).encode(Data([0x7f, 0xfe])), + Data(hexEncoded: "04027ffe") + ) + XCTAssertEqual( + try ASN1Encoder(schema: .octetString()).encode(Int8(bitPattern: 0xfe)), + Data(hexEncoded: "0401fe") + ) + XCTAssertEqual( + try ASN1Encoder(schema: .octetString()).encode(Int16(bitPattern: 0xfe7f)), + Data(hexEncoded: "04027ffe") + ) + XCTAssertEqual( + try ASN1Encoder(schema: .octetString()).encode(Int32(bitPattern: 0xfeffff7f)), + Data(hexEncoded: "04047ffffffe") + ) + XCTAssertEqual( + try ASN1Encoder(schema: .octetString()).encode(Int64(bitPattern: 0xfeffffffffffff7f)), + Data(hexEncoded: "04087ffffffffffffffe") + ) + XCTAssertEqual( + try ASN1Encoder(schema: .octetString()).encode(UInt8(0xfe)), + Data(hexEncoded: "0401fe") + ) + XCTAssertEqual( + try ASN1Encoder(schema: .octetString()).encode(UInt16(0xfe7f)), + Data(hexEncoded: "04027ffe") + ) + XCTAssertEqual( + try ASN1Encoder(schema: .octetString()).encode(UInt32(0xfeffff7f)), + Data(hexEncoded: "04047ffffffe") + ) + XCTAssertEqual( + try ASN1Encoder(schema: .octetString()).encode(UInt64(0xfeffffffffffff7f)), + Data(hexEncoded: "04087ffffffffffffffe") + ) + } + + func testEncodeOctetStringWithSize() throws { + + struct TestValue: Codable, SchemaSpecified { + var a: Data + + static var asn1Schema: Schema = .sequence([ + "a": .octetString(size: .range(1, 2)), + ]) + + } + + XCTAssertEqual( + try TestValue(a: Data([1, 2])).encoded(), + Data(hexEncoded: "300404020102") + ) + } + + func testEncodeOctetStringWithSizeInvalid() throws { + + struct TestValue: Codable, SchemaSpecified { + var a: Data + + static var asn1Schema: Schema = .sequence([ + "a": .octetString(size: .min(5)), + ]) + + } + + XCTAssertThrowsError( + try TestValue(a: Data([1, 2])).encoded() + ) { error in + AssertValueOutOfRange(error) + } + } + + func testEncodeInteger() throws { + + struct TestValue: Codable, SchemaSpecified { + var a: Int + + static var asn1Schema: Schema = .sequence([ + "a": .integer(), + ]) + + } + + XCTAssertEqual( + try TestValue(a: 123).encoded(), + Data(hexEncoded: "300302017b") + ) + } + + func testEncodeIntegerWithAllowed() throws { + + struct TestValue: Codable, SchemaSpecified { + var a: Int + + static var asn1Schema: Schema = .sequence([ + "a": .integer(allowed: ASN1.Integer(123) ..< ASN1.Integer(124)), + ]) + + } + + XCTAssertEqual( + try TestValue(a: 123).encoded(), + Data(hexEncoded: "300302017b") + ) + } + + func testEncodeIntegerWithAllowedInvalid() throws { + + struct TestValue: Codable, SchemaSpecified { + var a: Int + + static var asn1Schema: Schema = .sequence([ + "a": .integer(allowed: ASN1.Integer(0) ..< ASN1.Integer(10)), + ]) + + } + + XCTAssertThrowsError( + try TestValue(a: 123).encoded() + ) { error in + AssertDisallowedValue(error) + } + } + + func testEncodeGeneralizedTime() throws { + + struct TestValue: Codable, SchemaSpecified { + var a: Date + + static var asn1Schema: Schema = .sequence([ + "a": .time(kind: .generalized), + ]) + + } + + XCTAssertEqual( + try TestValue(a: Date(timeIntervalSince1970: 1675364376.709)).encoded(), + Data(hexEncoded: "3015181332303233303230323138353933362e3730395a") + ) + } + + func testEncodeUTCTime() throws { + + struct TestValue: Codable, SchemaSpecified { + var a: Date + + static var asn1Schema: Schema = .sequence([ + "a": .time(kind: .utc), + ]) + + } + + XCTAssertEqual( + try TestValue(a: Date(timeIntervalSince1970: 1675365671.0)).encoded(), + Data(hexEncoded: "300f170d3233303230323139323131315a") + ) + } + + func testEncodeZonedTime() throws { + + XCTAssertEqual( + try ASN1Encoder(schema: .time(kind: .utc)) + .encode(ZonedDate(date: Date(timeIntervalSince1970: 1675365671.0), timeZone: .utc)), + Data(hexEncoded: "170d3233303230323139323131315a") + ) + } + + func testEncodeAnyTime() throws { + + XCTAssertEqual( + try ASN1Encoder(schema: .time(kind: .utc)) + .encode(AnyTime(ZonedDate(date: Date(timeIntervalSince1970: 1675365671.0), timeZone: .utc), kind: .utc)), + Data(hexEncoded: "170d3233303230323139323131315a") + ) + } + + func testEncodeString() throws { + + struct TestValue: Codable, SchemaSpecified { + var a: String + + static var asn1Schema: Schema = .sequence([ + "a": .string(kind: .utf8), + ]) + + } + + XCTAssertEqual( + try TestValue(a: "Hello World").encoded(), + Data(hexEncoded: "300d0c0b48656c6c6f20576f726c64") + ) + + XCTAssertEqual( + try ASN1Encoder(schema: .string(kind: .numeric)).encode("123"), + Data(hexEncoded: "1203313233") + ) + XCTAssertEqual( + try ASN1Encoder(schema: .string(kind: .printable)).encode("test"), + Data(hexEncoded: "130474657374") + ) + XCTAssertEqual( + try ASN1Encoder(schema: .string(kind: .teletex)).encode("test"), + Data(hexEncoded: "140474657374") + ) + XCTAssertEqual( + try ASN1Encoder(schema: .string(kind: .videotex)).encode("test"), + Data(hexEncoded: "150474657374") + ) + XCTAssertEqual( + try ASN1Encoder(schema: .string(kind: .ia5)).encode("test"), + Data(hexEncoded: "160474657374") + ) + XCTAssertEqual( + try ASN1Encoder(schema: .string(kind: .graphic)).encode("test"), + Data(hexEncoded: "190474657374") + ) + XCTAssertEqual( + try ASN1Encoder(schema: .string(kind: .visible)).encode("test"), + Data(hexEncoded: "1a0474657374") + ) + XCTAssertEqual( + try ASN1Encoder(schema: .string(kind: .general)).encode("test"), + Data(hexEncoded: "1b0474657374") + ) + XCTAssertEqual( + try ASN1Encoder(schema: .string(kind: .universal)).encode("test"), + Data(hexEncoded: "1c0474657374") + ) + XCTAssertEqual( + try ASN1Encoder(schema: .string(kind: .character)).encode("test"), + Data(hexEncoded: "1d0474657374") + ) + XCTAssertEqual( + try ASN1Encoder(schema: .string(kind: .bmp)).encode("test"), + Data(hexEncoded: "1e0474657374") + ) + } + + func testEncodeStringWithSize() throws { + + struct TestValue: Codable, SchemaSpecified { + var a: String + + static var asn1Schema: Schema = .sequence([ + "a": .string(kind: .utf8, size: .min(3)), + ]) + + } + + XCTAssertEqual( + try TestValue(a: "Hello World").encoded(), + Data(hexEncoded: "300d0c0b48656c6c6f20576f726c64") + ) + } + + func testEncodeStringWithSizeInvalid() throws { + + struct TestValue: Codable, SchemaSpecified { + var a: String + + static var asn1Schema: Schema = .sequence([ + "a": .string(kind: .utf8, size: .max(3)), + ]) + + } + + XCTAssertThrowsError( + try TestValue(a: "Hello World").encoded() + ) { error in + AssertValueOutOfRange(error) + } + } + + func testEncodeImplictTaggedInteger() throws { + + struct TestValue: Codable, SchemaSpecified { + var a: Int + + static var asn1Schema: Schema = .sequence([ + "a": .implicit(1, .integer()), + ]) + + } + + XCTAssertEqual( + try TestValue(a: 123).encoded(), + Data(hexEncoded: "300381017b") + ) + } + + func testEncodeImplictTaggedSequenceOf() throws { + + struct TestValue: Codable, SchemaSpecified { + var a: [Int] + + static var asn1Schema: Schema = .sequence([ + "a": .implicit(1, .sequenceOf(.integer())), + ]) + + } + + XCTAssertEqual( + try TestValue(a: [1, 2, 3]).encoded(), + Data(hexEncoded: "300ba109020101020102020103") + ) + } + + func testEncodeImplictTaggedSetOf() throws { + + struct TestValue: Codable, SchemaSpecified { + var a: [Int] + + static var asn1Schema: Schema = .sequence([ + "a": .implicit(1, .sequenceOf(.integer())), + ]) + + } + + XCTAssertEqual( + try TestValue(a: [1, 2, 3]).encoded(), + Data(hexEncoded: "300ba109020101020102020103") + ) + } + + func testEncodeExplictTaggedInteger() throws { + + struct TestValue: Codable, SchemaSpecified { + var a: Int + + static var asn1Schema: Schema = .sequence([ + "a": .explicit(1, .integer()), + ]) + + } + + XCTAssertEqual( + try TestValue(a: 123).encoded(), + Data(hexEncoded: "3005a10302017b") + ) + } + + func testEncodeExplictTaggedSequenceOf() throws { + + struct TestValue: Codable, SchemaSpecified { + var a: [Int] + + static var asn1Schema: Schema = .sequence([ + "a": .explicit(1, .sequenceOf(.integer())), + ]) + + } + + XCTAssertEqual( + try TestValue(a: [1, 2, 3]).encoded(), + Data(hexEncoded: "300da10b3009020101020102020103") + ) + } + + func testEncodeExplictTaggedSetOf() throws { + + struct TestValue: Codable, SchemaSpecified { + var a: [Int] + + static var asn1Schema: Schema = .sequence([ + "a": .explicit(1, .setOf(.integer())), + ]) + + } + + XCTAssertEqual( + try TestValue(a: [1, 2, 3]).encoded(), + Data(hexEncoded: "300da10b3109020101020102020103") + ) + } + + func testEncodeChoiceWithLotsOfOptions() throws { + + struct TestValue: Codable, SchemaSpecified { + var a: String + + static var asn1Schema: Schema = .sequence([ + "a": .choiceOf([ + .sequenceOf(.integer()), + .setOf(.integer()), + .null, + .objectIdentifier(), + .boolean(), + .bitString(), + .octetString(), + .boolean(), + .integer(), + .time(kind: .utc), + .real, + .implicit(1, .integer()), + .explicit(2, .integer()), + .string(kind: .utf8), + ]) + ]) + + } + + XCTAssertEqual( + try TestValue(a: "abc").encoded(), + Data(hexEncoded: "30050c03616263") + ) + } + + func testEncodeChoiceWithLotsOfOptions2() throws { + + struct TestValue: Codable, SchemaSpecified { + var a: Bool + + static var asn1Schema: Schema = .sequence([ + "a": .choiceOf([ + .sequence([:]), + .sequenceOf(.integer()), + .setOf(.integer()), + .null, + .objectIdentifier(), + .bitString(), + .octetString(), + .integer(), + .time(kind: .utc), + .string(kind: .utf8), + .real, + .implicit(1, .integer()), + .explicit(2, .integer()), + .boolean(), + ]) + ]) + + } + + XCTAssertEqual( + try TestValue(a: true).encoded(), + Data(hexEncoded: "30030101ff") + ) + } + + func testEncodeChoiceWithStringKinds() throws { + + struct TestValue: Codable, SchemaSpecified { + var a: AnyString + + static var asn1Schema: Schema = .sequence([ + "a": .choiceOf([ + .string(kind: .utf8), + .string(kind: .printable), + ]) + ]) + + } + + XCTAssertEqual( + try TestValue(a: AnyString("abc", kind: .printable)).encoded(), + Data(hexEncoded: "30051303616263") + ) + } + + func testEncodeChoiceWithTimeKinds() throws { + + struct TestValue: Codable, SchemaSpecified { + var a: AnyTime + + static var asn1Schema: Schema = .sequence([ + "a": .choiceOf([ + .time(kind: .utc), + .time(kind: .generalized), + ]) + ]) + + } + + XCTAssertEqual( + try TestValue(a: AnyTime(ZonedDate(date: Date(timeIntervalSince1970: 1675364376.709), + timeZone: .utc), kind: .generalized)).encoded(), + Data(hexEncoded: "3015181332303233303230323138353933362e3730395a") + ) + } + + func testEncodeImplicitChoices() throws { + + struct TaggedInt: Equatable, Codable, Tagged { + var tag: ASN1.AnyTag + var int: BigInt + + init?(tag: ASN1.AnyTag, value: Any?) { + guard let int = value as? BigInt else { + return nil + } + self.tag = tag + self.int = int + } + + var value: Any? { int } + + func encode(schema: Schema) throws -> ASN1 { + return .tagged(tag, try ASN1Encoder(schema: .integer()).encode(int)) + } + + } + + let schema: Schema = + .choiceOf([ + .implicit(1, .integer()), + .implicit(2, .integer()), + ]) + + XCTAssertEqual( + try ASN1Encoder(schema: schema).encode(TaggedInt(tag: 2, value: BigInt(1))), + Data([ASN1.Tag.tag(from: 2, in: .contextSpecific, constructed: false), + 0x03, ASN1.Tag.integer.rawValue, 0x01, 0x01]) + ) + } + + func testEncodeExplicitChoices() throws { + + struct TaggedInt: Equatable, Codable, Tagged { + var tag: ASN1.AnyTag + var int: BigInt + + init?(tag: ASN1.AnyTag, value: Any?) { + guard let int = value as? BigInt else { + return nil + } + self.tag = tag + self.int = int + } + + var value: Any? { int } + + func encode(schema: Schema) throws -> ASN1 { + return .integer(int) + } + + } + + let schema: Schema = + .choiceOf([ + .explicit(1, .integer()), + .explicit(2, .integer()), + ]) + + XCTAssertEqual( + try ASN1Encoder(schema: schema).encode(TaggedInt(tag: 2, value: BigInt(1))), + Data([ASN1.Tag.tag(from: 2, in: .contextSpecific, constructed: true), 0x03, + ASN1.Tag.integer.rawValue, 0x01, 0x01]) + ) + } + + func testEncodeAny() { + + XCTAssertEqual( + try ASN1Encoder(schema: .any).encode(ASN1.boolean(true)), + Data(hexEncoded: "0101ff") + ) + } + + func testEncodeVersioned() throws { + + struct TestValue: Codable, SchemaSpecified { + var ver: Int + var name1: String? + var name2: String? + + static var asn1Schema: Schema = .sequence([ + "ver": .version(.integer(allowed: 1..<3)), + "name1": .versioned(range: 1...1, .string(kind: .utf8)), + "name2": .versioned(range: 2...2, .string(kind: .printable)), + ]) + } + + XCTAssertEqual( + try TestValue(ver: 1, name1: "test").encoded(), + Data(hexEncoded: "30090201010c0474657374") + ) + + XCTAssertEqual( + try TestValue(ver: 2, name2: "test").encoded(), + Data(hexEncoded: "3009020102130474657374") + ) + } + + func testEncodeTaggedVersioned() throws { + + struct TestValue: Codable, SchemaSpecified { + var ver: Int + var name1: String? + var name2: String? + + static var asn1Schema: Schema = .sequence([ + "ver": .version(.implicit(1, .integer(allowed: 1..<3))), + "name1": .versioned(range: 1...1, .string(kind: .utf8)), + "name2": .versioned(range: 2...2, .string(kind: .printable)), + ]) + } + + XCTAssertEqual( + try TestValue(ver: 1, name1: "test").encoded(), + Data(hexEncoded: "30098101010c0474657374") + ) + + XCTAssertEqual( + try TestValue(ver: 2, name2: "test").encoded(), + Data(hexEncoded: "3009810102130474657374") + ) + } + + func testEncodeTaggedVersionedDefault() throws { + + struct TestValue: Codable, SchemaSpecified { + var ver: Int + var name1: String? + var name2: String? + + static var asn1Schema: Schema = .sequence([ + "ver": .version(.implicit(1, .integer(allowed: 1..<3, default: 1))), + "name1": .versioned(range: 1...1, .string(kind: .utf8)), + "name2": .versioned(range: 2...2, .string(kind: .printable)), + ]) + } + + XCTAssertEqual( + try TestValue(ver: 1, name1: "test").encoded(), + Data(hexEncoded: "30060c0474657374") + ) + } + + func testEncodeOptional() throws { + + struct TestValue: Codable, SchemaSpecified { + var a: String? + + static var asn1Schema: Schema = .sequence([ + "a": .optional(.string(kind: .utf8)), + ]) + + } + + XCTAssertEqual( + try TestValue(a: "Hello World").encoded(), + Data(hexEncoded: "300d0c0b48656c6c6f20576f726c64") + ) + + XCTAssertEqual( + try TestValue(a: nil).encoded().hexEncodedString(), + Data(hexEncoded: "3000").hexEncodedString() + ) + } + +} + + +private func AssertBadValue(_ error: Error, file: StaticString = #file, line: UInt = #line) { + func check() -> Bool { + if case SchemaState.EncodingError.badValue = error { + return true + } + else { + return false + } + } + XCTAssertTrue(check(), file: file, line: line) +} + + +private func AssertValueOutOfRange(_ error: Error, file: StaticString = #file, line: UInt = #line) { + func check() -> Bool { + if case SchemaState.EncodingError.valueOutOfRange = error { + return true + } + else { + return false + } + } + XCTAssertTrue(check(), file: file, line: line) +} + +private func AssertDisallowedValue(_ error: Error, file: StaticString = #file, line: UInt = #line) { + func check() -> Bool { + if case SchemaState.EncodingError.disallowedValue = error { + return true + } + else { + return false + } + } + XCTAssertTrue(check(), file: file, line: line) +} + +private func AssertNoDynamicTypeDefined(_ error: Error, file: StaticString = #file, line: UInt = #line) { + func check() -> Bool { + if case SchemaState.SchemaError.noDynamicTypeDefined = error { + return true + } + else { + return false + } + } + XCTAssertTrue(check(), file: file, line: line) +} + +private func AssertUnknownDynamicValue(_ error: Error, file: StaticString = #file, line: UInt = #line) { + func check() -> Bool { + if case SchemaState.SchemaError.unknownDynamicValue = error { + return true + } + else { + return false + } + } + XCTAssertTrue(check(), file: file, line: line) +} diff --git a/Tests/ASN1SchemaTests.swift b/Tests/ASN1SchemaTests.swift new file mode 100644 index 000000000..953c9a28f --- /dev/null +++ b/Tests/ASN1SchemaTests.swift @@ -0,0 +1,479 @@ +// +// ASN1SchemaTests.swift +// PotentCodables +// +// Copyright © 2021 Outfox, inc. +// +// +// Distributed under the MIT License, See LICENSE for details. +// + +import Foundation +@testable import PotentASN1 +import XCTest + + +class ASN1SchemaTests: XCTestCase { + + func testPossibleTags() { + XCTAssertEqual(Schema.sequenceOf(.integer()).possibleTags, [ASN1.Tag.sequence.universal]) + XCTAssertEqual(Schema.setOf(.integer()).possibleTags, [ASN1.Tag.set.universal]) + XCTAssertEqual(Schema.choiceOf([.integer(), .boolean()]).possibleTags, [ASN1.Tag.integer.universal, + ASN1.Tag.boolean.universal]) + XCTAssertEqual(Schema.choiceOf([.nothing]).possibleTags, []) + XCTAssertEqual(Schema.type(.integer()).possibleTags, [ASN1.Tag.integer.universal]) + XCTAssertEqual(Schema.version(.integer()).possibleTags, [ASN1.Tag.integer.universal]) + XCTAssertEqual(Schema.versioned(range: 1...3, .integer()).possibleTags, [ASN1.Tag.integer.universal]) + XCTAssertEqual(Schema.optional(.integer()).possibleTags, [ASN1.Tag.integer.universal]) + XCTAssertEqual(Schema.implicit(1, .integer()).possibleTags, + [ASN1.Tag.tag(from: 1, in: .contextSpecific, constructed: false)]) + XCTAssertEqual(Schema.implicit(1, .sequenceOf(.integer())).possibleTags, + [ASN1.Tag.tag(from: 1, in: .contextSpecific, constructed: true)]) + XCTAssertEqual(Schema.explicit(1, .integer()).possibleTags, + [ASN1.Tag.tag(from: 1, in: .contextSpecific, constructed: true)]) + XCTAssertEqual(Schema.explicit(1, .sequenceOf(.integer())).possibleTags, + [ASN1.Tag.tag(from: 1, in: .contextSpecific, constructed: true)]) + XCTAssertEqual(Schema.boolean().possibleTags, [ASN1.Tag.boolean.universal]) + XCTAssertEqual(Schema.integer().possibleTags, [ASN1.Tag.integer.universal]) + XCTAssertEqual(Schema.real.possibleTags, [ASN1.Tag.real.universal]) + XCTAssertEqual(Schema.bitString().possibleTags, [ASN1.Tag.bitString.universal]) + XCTAssertEqual(Schema.octetString().possibleTags, [ASN1.Tag.octetString.universal]) + XCTAssertEqual(Schema.objectIdentifier().possibleTags, [ASN1.Tag.objectIdentifier.universal]) + XCTAssertEqual(Schema.string(kind: .utf8).possibleTags, [ASN1.Tag.utf8String.universal]) + XCTAssertEqual(Schema.string(kind: .numeric).possibleTags, [ASN1.Tag.numericString.universal]) + XCTAssertEqual(Schema.string(kind: .printable).possibleTags, [ASN1.Tag.printableString.universal]) + XCTAssertEqual(Schema.string(kind: .teletex).possibleTags, [ASN1.Tag.teletexString.universal]) + XCTAssertEqual(Schema.string(kind: .videotex).possibleTags, [ASN1.Tag.videotexString.universal]) + XCTAssertEqual(Schema.string(kind: .ia5).possibleTags, [ASN1.Tag.ia5String.universal]) + XCTAssertEqual(Schema.string(kind: .graphic).possibleTags, [ASN1.Tag.graphicString.universal]) + XCTAssertEqual(Schema.string(kind: .visible).possibleTags, [ASN1.Tag.visibleString.universal]) + XCTAssertEqual(Schema.string(kind: .general).possibleTags, [ASN1.Tag.generalString.universal]) + XCTAssertEqual(Schema.string(kind: .universal).possibleTags, [ASN1.Tag.universalString.universal]) + XCTAssertEqual(Schema.string(kind: .character).possibleTags, [ASN1.Tag.characterString.universal]) + XCTAssertEqual(Schema.string(kind: .bmp).possibleTags, [ASN1.Tag.bmpString.universal]) + XCTAssertEqual(Schema.time(kind: .utc).possibleTags, [ASN1.Tag.utcTime.universal]) + XCTAssertEqual(Schema.time(kind: .generalized).possibleTags, [ASN1.Tag.generalizedTime.universal]) + XCTAssertEqual(Schema.null.possibleTags, [ASN1.Tag.null.universal]) + XCTAssertEqual(Schema.any.possibleTags, nil) + XCTAssertEqual(Schema.dynamic([.integer(1): .integer()]).possibleTags, nil) + XCTAssertEqual(Schema.nothing.possibleTags, nil) + } + + func testDefaultValues() { + XCTAssertEqual(Schema.type(.integer(default: 5)).defaultValue, .integer(5)) + XCTAssertEqual(Schema.version(.integer(default: 5)).defaultValue, .integer(5)) + XCTAssertEqual(Schema.versioned(range: 1...3, .integer(default: 5)).defaultValue, .integer(5)) + XCTAssertEqual(Schema.optional(.integer(default: 5)).defaultValue, .integer(5)) + XCTAssertEqual(Schema.implicit(1, .integer(default: 5)).defaultValue, .integer(5)) + XCTAssertEqual(Schema.explicit(2, .integer(default: 5)).defaultValue, .integer(5)) + XCTAssertEqual(Schema.boolean(default: false).defaultValue, .boolean(false)) + XCTAssertEqual(Schema.integer(default: 5).defaultValue, .integer(5)) + XCTAssertNil(Schema.string(kind: .utf8).defaultValue) + } + + func testDefaultValueEncoded() { + XCTAssertEqual(try Schema.integer(default: 5).defaultValueEncoded(), .default(.integer(5))) + } + + func testUnwrapDirectives() { + XCTAssertEqual(Schema.type(.integer()).unwrapDirectives, .integer()) + XCTAssertEqual(Schema.version(.integer()).unwrapDirectives, .integer()) + XCTAssertEqual(Schema.versioned(range: 1...3, .integer()).unwrapDirectives, .integer()) + XCTAssertEqual(Schema.integer().unwrapDirectives, .integer()) + } + + func testIsVersionedAccessor() { + XCTAssertTrue(Schema.versioned(range: 1...3, .integer()).isVersioned) + XCTAssertFalse(Schema.integer().isVersioned) + } + + func testIsDynamicAccessor() { + XCTAssertTrue(Schema.dynamic([.integer(1): .integer()]).isDynamic) + XCTAssertFalse(Schema.integer().isDynamic) + } + + func testIsOptionalAccessor() { + XCTAssertTrue(Schema.type(.optional(.integer())).isOptional) + XCTAssertTrue(Schema.version(.optional(.integer())).isOptional) + XCTAssertTrue(Schema.versioned(range: 1...3, .optional(.integer())).isOptional) + XCTAssertTrue(Schema.optional(.integer()).isOptional) + XCTAssertTrue(Schema.implicit(1, .optional(.integer())).isOptional) + XCTAssertTrue(Schema.explicit(1, .optional(.integer())).isOptional) + XCTAssertFalse(Schema.integer().isOptional) + } + + func testIsBooleanAccessor() { + XCTAssertTrue(Schema.boolean().isBoolean) + XCTAssertFalse(Schema.integer().isBoolean) + } + + func testIsIntegerAccessor() { + XCTAssertTrue(Schema.integer().isInteger) + XCTAssertFalse(Schema.boolean().isInteger) + } + + func testIsRealAccessor() { + XCTAssertTrue(Schema.real.isReal) + XCTAssertFalse(Schema.integer().isReal) + } + + func testIsBitStringAccessor() { + XCTAssertTrue(Schema.bitString().isBitString) + XCTAssertFalse(Schema.integer().isBitString) + } + + func testIsOctetStringAccessor() { + XCTAssertTrue(Schema.octetString().isOctetString) + XCTAssertFalse(Schema.integer().isOctetString) + } + + func testIsObjectIdentifierAccessor() { + XCTAssertTrue(Schema.objectIdentifier().isObjectIdentifier) + XCTAssertFalse(Schema.integer().isObjectIdentifier) + } + + func testIsStringAccessor() { + XCTAssertTrue(Schema.string(kind: .utf8).isString) + XCTAssertFalse(Schema.integer().isString) + } + + func testIsTimeAccessor() { + XCTAssertTrue(Schema.time(kind: .utc).isTime) + XCTAssertFalse(Schema.integer().isTime) + } + + func testIsNullAccessor() { + XCTAssertTrue(Schema.null.isNull) + XCTAssertFalse(Schema.integer().isNull) + } + + func testIsSequenceAccessor() { + XCTAssertTrue(Schema.sequence().isSequence) + XCTAssertFalse(Schema.integer().isSequence) + } + + func testIsSequenceOfAccessor() { + XCTAssertTrue(Schema.sequenceOf(.integer()).isSequenceOf) + XCTAssertFalse(Schema.integer().isSequenceOf) + } + + func testIsSetOfAccessor() { + XCTAssertTrue(Schema.setOf(.integer()).isSetOf) + XCTAssertFalse(Schema.integer().isSetOf) + } + + func testIsCollectionAccessor() { + XCTAssertTrue(Schema.sequence().isCollection) + XCTAssertTrue(Schema.sequenceOf(.integer()).isCollection) + XCTAssertTrue(Schema.setOf(.integer()).isCollection) + XCTAssertFalse(Schema.integer().isCollection) + } + + func testDescription() { + + XCTAssertEqual( + Schema.sequence(["a": .integer(), "b": .boolean()]).description, + """ + SEQUENCE ::= { + a INTEGER + b BOOLEAN + } + """ + ) + + XCTAssertEqual( + Schema.sequenceOf(.integer()).description, + """ + SEQUENCE OF + INTEGER + """ + ) + + XCTAssertEqual( + Schema.sequenceOf(.integer(), size: .min(3)).description, + """ + SEQUENCE SIZE (3..MAX) OF + INTEGER + """ + ) + + XCTAssertEqual( + Schema.sequenceOf(.integer(), size: .max(3)).description, + """ + SEQUENCE SIZE (0..3) OF + INTEGER + """ + ) + + XCTAssertEqual( + Schema.sequenceOf(.integer(), size: .range(2, 4)).description, + """ + SEQUENCE SIZE (2..4) OF + INTEGER + """ + ) + + XCTAssertEqual( + Schema.sequenceOf(.integer(), size: .is(2)).description, + """ + SEQUENCE SIZE (2) OF + INTEGER + """ + ) + + XCTAssertEqual( + Schema.setOf(.integer()).description, + """ + SET OF + INTEGER + """ + ) + + XCTAssertEqual( + Schema.setOf(.integer(), size: .min(3)).description, + """ + SET SIZE (3..MAX) OF + INTEGER + """ + ) + + XCTAssertEqual( + Schema.choiceOf([.integer(), .boolean()]).description, + """ + CHOICE ::= { + INTEGER + BOOLEAN + } + """ + ) + + XCTAssertEqual( + Schema.any.description, + """ + ANY + """ + ) + + XCTAssertEqual( + Schema.type(.integer()).description, + """ + INTEGER + """ + ) + + XCTAssertEqual( + Schema.dynamic(unknownTypeSchema: .boolean(), [.integer(1): .integer()]).description, + """ + DYNAMIC { + CASE 1 (INTEGER): INTEGER + ELSE: BOOLEAN + } + """ + ) + + XCTAssertEqual( + Schema.version(.integer()).description, + """ + INTEGER + """ + ) + + XCTAssertEqual( + Schema.versioned(range: 1...3, .integer()).description, + """ + INTEGER -- for version (1..3) + """ + ) + + XCTAssertEqual( + Schema.optional(.integer()).description, + """ + INTEGER OPTIONAL + """ + ) + + XCTAssertEqual( + Schema.implicit(1, .integer()).description, + """ + [1] IMPLICIT INTEGER + """ + ) + + XCTAssertEqual( + Schema.explicit(1, .integer()).description, + """ + [1] EXPLICIT INTEGER + """ + ) + + XCTAssertEqual( + Schema.boolean().description, + """ + BOOLEAN + """ + ) + + XCTAssertEqual( + Schema.boolean(default: false).description, + """ + BOOLEAN DEFAULT FALSE + """ + ) + + XCTAssertEqual( + Schema.integer().description, + """ + INTEGER + """ + ) + + XCTAssertEqual( + Schema.integer(allowed: ASN1.Integer(1).. = ["a": 1, "b": 2, "c": 3] + } + let value = TestValue() + + let tree: AnyValue = [ + "bool": true, + "string": "test", + "int": .int(123), + "int8": .int8(123), + "int16": .int16(123), + "int32": .int32(123), + "int64": .int64(123), + "uint": .uint(123), + "uint8": .uint8(123), + "uint16": .uint16(123), + "uint32": .uint32(123), + "uint64": .uint64(123), + "integer": .integer(123), + "unsignedInteger": .unsignedInteger(123), + "float16": .float16(1.5), + "float": .float(1.5), + "double": .double(1.5), + "decimal": .decimal(1.5), + "data": .data(Data([1, 2, 3])), + "url": .url(value.url), + "uuid": .uuid(value.uuid), + "date": .date(value.date), + "array": .array([1, 2, 3]), + "dictionary": .dictionary(["a": 1, "b": 2, "c": 3]), + ] + + XCTAssertEqual(try AnyValue.Encoder.default.encodeTree(value), tree) + XCTAssertEqual(try AnyValue.Decoder.default.decodeTree(TestValue.self, from: tree), value) + } + +} diff --git a/Tests/AnyValueTests.swift b/Tests/AnyValueTests.swift index ddcfd41ea..39c0da99f 100644 --- a/Tests/AnyValueTests.swift +++ b/Tests/AnyValueTests.swift @@ -8,102 +8,511 @@ // Distributed under the MIT License, See LICENSE for details. // +import BigInt +import OrderedCollections @testable import PotentCodables -@testable import PotentJSON import XCTest class AnyValueTests: XCTestCase { - let json = """ - { - "a": 1, - "b": "2", - "c": [true, false, true], - "d": { - "a": 3, - "b": "4", - "c": [false, false, true], - "d": { - "a": 5, - "b": "6", - "c": [true, true, false] - } - }, - "e": [ - { - "aaa": 7, - "bbb": "8", - "ccc": [true] - }, - { - "aaaa": 9, - "bbbb": "10", - "cccc": [true] - } - ] - } - """.data(using: .utf8)! - - let e = AnyValue.array([ - .dictionary([ - "aaa": .int64(7), - "bbb": .string("8"), - "ccc": .array([.bool(true)]), - ]), - .dictionary([ - "aaaa": .int64(9), - "bbbb": .string("10"), - "cccc": .array([.bool(true)]), - ]), - ]) - - let f: AnyValue = [ - [ - "a": 7, - "b": "8", - "c": [true], - ], - [ - "a": 9, - "b": "10", - "c": [true], - ], - ] - - func testSimple() throws { - let tree = try JSONSerialization.json(from: json) - let value = try JSON.Decoder().decode(TestValue.self, from: json) - let recoded = try JSON.Encoder().encodeTree(value) - XCTAssertEqual(recoded.stableText, tree.stableText) - XCTAssertEqual(value.e!, e) + func testLiterals() { + XCTAssertEqual(nil as AnyValue, .nil) + XCTAssertEqual(true as AnyValue, .bool(true)) + XCTAssertEqual("test" as AnyValue, .string("test")) + XCTAssertEqual(123 as AnyValue, .int(123)) + XCTAssertEqual(1.5 as AnyValue, .double(1.5)) + XCTAssertEqual([1, 2, 3] as AnyValue, .array([1, 2, 3])) + XCTAssertEqual(["a": 1, "b": 2, "c": 3] as AnyValue, .dictionary(["a": 1, "b": 2, "c": 3])) + } + + func testAnyValueSubscript() { + let value: AnyValue = ["a": 1] + XCTAssertEqual(value["a"], 1) + XCTAssertNil(value["b"]) + XCTAssertNil(([1, 2, 3] as AnyValue)["a"]) + } + + func testIntSubscript() { + let value: AnyValue = ["a", "b", "c"] + XCTAssertEqual(value[1], "b") + XCTAssertNil(value[4]) + } + + func testDynamicMemberAccess() { + let value: AnyValue = ["aaa": 1] + XCTAssertEqual(value.aaa, 1) + XCTAssertNil(value.bbb) + XCTAssertNil(([1, 2, 3] as AnyValue).aaa) + } + + func testIsNullAccessor() { + XCTAssertTrue(AnyValue.nil.isNull) + XCTAssertFalse(AnyValue.bool(false).isNull) + } + + func testBoolAccessor() { + XCTAssertEqual(AnyValue.bool(true).boolValue, true) + XCTAssertNil(AnyValue.int(1).boolValue) + } + + func testIntAccessor() { + XCTAssertEqual(AnyValue.int(123).intValue, 123) + XCTAssertNil(AnyValue.bool(false).intValue) + } + + func testInt8Accessor() { + XCTAssertEqual(AnyValue.int8(123).int8Value, 123) + XCTAssertNil(AnyValue.bool(false).int8Value) + } + + func testInt16Accessor() { + XCTAssertEqual(AnyValue.int16(123).int16Value, 123) + XCTAssertNil(AnyValue.bool(false).int16Value) + } + + func testInt32Accessor() { + XCTAssertEqual(AnyValue.int32(123).int32Value, 123) + XCTAssertNil(AnyValue.bool(false).int32Value) + } + + func testInt64Accessor() { + XCTAssertEqual(AnyValue.int64(123).int64Value, 123) + XCTAssertNil(AnyValue.bool(false).int64Value) + } + + func testUIntAccessor() { + XCTAssertEqual(AnyValue.uint(123).uintValue, 123) + XCTAssertNil(AnyValue.bool(false).uintValue) + } + + func testUInt8Accessor() { + XCTAssertEqual(AnyValue.uint8(123).uint8Value, 123) + XCTAssertNil(AnyValue.bool(false).uint8Value) + } + + func testUInt16Accessor() { + XCTAssertEqual(AnyValue.uint16(123).uint16Value, 123) + XCTAssertNil(AnyValue.bool(false).uint16Value) + } + + func testUInt32Accessor() { + XCTAssertEqual(AnyValue.uint32(123).uint32Value, 123) + XCTAssertNil(AnyValue.bool(false).uint32Value) + } + + func testUInt64Accessor() { + XCTAssertEqual(AnyValue.uint64(123).uint64Value, 123) + XCTAssertNil(AnyValue.bool(false).uint64Value) + } + + func testIntegerAccessor() { + XCTAssertEqual(AnyValue.int8(123).integerValue(Int.self), 123) + XCTAssertEqual(AnyValue.int16(123).integerValue(Int.self), 123) + XCTAssertEqual(AnyValue.int32(123).integerValue(Int.self), 123) + XCTAssertEqual(AnyValue.int64(123).integerValue(Int.self), 123) + XCTAssertEqual(AnyValue.uint8(123).integerValue(Int.self), 123) + XCTAssertEqual(AnyValue.uint16(123).integerValue(Int.self), 123) + XCTAssertEqual(AnyValue.uint32(123).integerValue(Int.self), 123) + XCTAssertEqual(AnyValue.uint64(123).integerValue(Int.self), 123) + XCTAssertNil(AnyValue.float16(123).integerValue(Int.self)) + } + + func testFloat16Accessor() { + XCTAssertEqual(AnyValue.float16(1.5).float16Value, 1.5) + XCTAssertNil(AnyValue.bool(false).float16Value) + } + + func testFloatAccessor() { + XCTAssertEqual(AnyValue.float(1.5).floatValue, 1.5) + XCTAssertNil(AnyValue.bool(false).floatValue) + } + + func testDoubleAccessor() { + XCTAssertEqual(AnyValue.double(1.5).doubleValue, 1.5) + XCTAssertNil(AnyValue.bool(false).doubleValue) + } + + func testDecimalAccessor() { + XCTAssertEqual(AnyValue.decimal(1.5).decimalValue, 1.5) + XCTAssertNil(AnyValue.bool(false).decimalValue) + } + + func testFloatingPointAccessor() { + XCTAssertEqual(AnyValue.int8(123).floatingPointValue(Float.self), 123) + XCTAssertEqual(AnyValue.int16(123).floatingPointValue(Float.self), 123) + XCTAssertEqual(AnyValue.int32(123).floatingPointValue(Float.self), 123) + XCTAssertEqual(AnyValue.int64(123).floatingPointValue(Float.self), 123) + XCTAssertEqual(AnyValue.uint8(123).floatingPointValue(Float.self), 123) + XCTAssertEqual(AnyValue.uint16(123).floatingPointValue(Float.self), 123) + XCTAssertEqual(AnyValue.uint32(123).floatingPointValue(Float.self), 123) + XCTAssertEqual(AnyValue.uint64(123).floatingPointValue(Float.self), 123) + XCTAssertEqual(AnyValue.float16(1.5).floatingPointValue(Float.self), 1.5) + XCTAssertEqual(AnyValue.float(1.5).floatingPointValue(Float.self), 1.5) + XCTAssertEqual(AnyValue.double(1.5).floatingPointValue(Float.self), 1.5) + XCTAssertEqual(AnyValue.decimal(1.5).floatingPointValue(Float.self), 1.5) + XCTAssertNil(AnyValue.bool(false).floatingPointValue(Float.self)) + } + + func testStringAccessor() { + XCTAssertEqual(AnyValue.string("test").stringValue, "test") + XCTAssertNil(AnyValue.int(1).stringValue) + } + + func testUrlAccessor() { + XCTAssertEqual(AnyValue.url(URL(string: "https://example.com")!).urlValue, URL(string: "https://example.com")) + XCTAssertNil(AnyValue.int(1).urlValue) + } + + func testUUIDAccessor() { + let uuid = UUID() + XCTAssertEqual(AnyValue.uuid(uuid).uuidValue, uuid) + XCTAssertNil(AnyValue.int(1).uuidValue) + } + + func testDataAccessor() { + XCTAssertEqual(AnyValue.data(Data([1, 2, 3])).dataValue, Data([1, 2, 3])) + XCTAssertNil(AnyValue.int(1).dataValue) + } + + func testDat3Accessor() { + let date = Date() + XCTAssertEqual(AnyValue.date(date).dateValue, date) + XCTAssertNil(AnyValue.int(1).dateValue) + } + + func testArrayAccessor() { + XCTAssertEqual(AnyValue.array([1, 2, 3]).arrayValue, [1, 2, 3] as AnyValue.AnyArray) + XCTAssertNil(AnyValue.int(1).arrayValue) + } + + func testDictionaryAccessor() { + XCTAssertEqual(AnyValue.dictionary(["a": 1, "b": 2]).dictionaryValue, ["a": 1, "b": 2] as AnyValue.AnyDictionary) + XCTAssertNil(AnyValue.int(1).dictionaryValue) + } + + func testDescription() { + XCTAssertEqual(AnyValue.nil.description, "nil") + XCTAssertEqual(AnyValue.bool(true).description, "true") + XCTAssertEqual(AnyValue.int8(123).description, "123") + XCTAssertEqual(AnyValue.int16(123).description, "123") + XCTAssertEqual(AnyValue.int32(123).description, "123") + XCTAssertEqual(AnyValue.int64(123).description, "123") + XCTAssertEqual(AnyValue.uint8(123).description, "123") + XCTAssertEqual(AnyValue.uint16(123).description, "123") + XCTAssertEqual(AnyValue.uint32(123).description, "123") + XCTAssertEqual(AnyValue.uint64(123).description, "123") + XCTAssertEqual(AnyValue.integer(123).description, "123") + XCTAssertEqual(AnyValue.unsignedInteger(123).description, "123") + XCTAssertEqual(AnyValue.float16(1.5).description, "1.5") + XCTAssertEqual(AnyValue.float(1.5).description, "1.5") + XCTAssertEqual(AnyValue.double(1.5).description, "1.5") + XCTAssertEqual(AnyValue.decimal(123.45).description, "123.45") + XCTAssertEqual(AnyValue.string("test").description, "test") + let date = Date() + XCTAssertEqual( + AnyValue.date(date).description, + ZonedDate(date: date, timeZone: .utc).iso8601EncodedString() + ) + XCTAssertEqual(AnyValue.data(Data([1, 2, 3, 4, 5])).description, "5 bytes") + let uuid = UUID() + XCTAssertEqual( + AnyValue.uuid(uuid).description, + uuid.uuidString + ) + let url = URL(string: "https://example.com/some/thing")! + XCTAssertEqual( + AnyValue.url(url).description, + url.absoluteString + ) + XCTAssertEqual(AnyValue.array([1, 2, 3, 4, 5]).description, "[1, 2, 3, 4, 5]") + XCTAssertEqual( + AnyValue.dictionary(["a": 1, "b": 2, "c": 3, "d": 4, "e": 5]).description, + "[a: 1, b: 2, c: 3, d: 4, e: 5]" + ) + } + + func testWrapped() { + XCTAssertEqual(try AnyValue.wrapped(nil), .nil) + XCTAssertEqual(try AnyValue.wrapped("test"), .string("test")) + XCTAssertEqual(try AnyValue.wrapped(true), .bool(true)) + XCTAssertEqual(try AnyValue.wrapped(Int.max), .int(Int.max)) + XCTAssertEqual(try AnyValue.wrapped(Int.min), .int(Int.min)) + XCTAssertEqual(try AnyValue.wrapped(UInt.max), .uint(UInt.max)) + XCTAssertEqual(try AnyValue.wrapped(UInt.min), .uint(UInt.min)) + XCTAssertEqual(try AnyValue.wrapped(Int8.max), .int8(Int8.max)) + XCTAssertEqual(try AnyValue.wrapped(Int8.min), .int8(Int8.min)) + XCTAssertEqual(try AnyValue.wrapped(Int16.max), .int16(Int16.max)) + XCTAssertEqual(try AnyValue.wrapped(Int16.min), .int16(Int16.min)) + XCTAssertEqual(try AnyValue.wrapped(Int32.max), .int32(Int32.max)) + XCTAssertEqual(try AnyValue.wrapped(Int32.min), .int32(Int32.min)) + XCTAssertEqual(try AnyValue.wrapped(Int64.max), .int64(Int64.max)) + XCTAssertEqual(try AnyValue.wrapped(Int64.min), .int64(Int64.min)) + XCTAssertEqual(try AnyValue.wrapped(UInt8.max), .uint8(UInt8.max)) + XCTAssertEqual(try AnyValue.wrapped(UInt8.min), .uint8(UInt8.min)) + XCTAssertEqual(try AnyValue.wrapped(UInt16.max), .uint16(UInt16.max)) + XCTAssertEqual(try AnyValue.wrapped(UInt16.min), .uint16(UInt16.min)) + XCTAssertEqual(try AnyValue.wrapped(UInt32.max), .uint32(UInt32.max)) + XCTAssertEqual(try AnyValue.wrapped(UInt32.min), .uint32(UInt32.min)) + XCTAssertEqual(try AnyValue.wrapped(UInt64.max), .uint64(UInt64.max)) + XCTAssertEqual(try AnyValue.wrapped(UInt64.min), .uint64(UInt64.min)) + XCTAssertEqual(try AnyValue.wrapped(BigInt(1)), .integer(1)) + XCTAssertEqual(try AnyValue.wrapped(BigInt(-1)), .integer(-1)) + XCTAssertEqual(try AnyValue.wrapped(BigUInt(1)), .unsignedInteger(1)) + XCTAssertEqual(try AnyValue.wrapped(Float16(1.5)), .float16(1.5)) + XCTAssertEqual(try AnyValue.wrapped(Float(1.5)), .float(1.5)) + XCTAssertEqual(try AnyValue.wrapped(Double(1.5)), .double(1.5)) + XCTAssertEqual(try AnyValue.wrapped(Decimal(1.23)), .decimal(1.23)) + XCTAssertEqual(try AnyValue.wrapped(Data([1, 2, 3])), .data(Data([1, 2, 3]))) + let url = URL(string: "https://example.com")! + XCTAssertEqual(try AnyValue.wrapped(url), .url(url)) + let uuid = UUID() + XCTAssertEqual(try AnyValue.wrapped(uuid), .uuid(uuid)) + let date = Date() + XCTAssertEqual(try AnyValue.wrapped(date), .date(date)) + XCTAssertEqual(try AnyValue.wrapped([1, "test", true]), .array([1, "test", true])) + + // Unorderd dictionaries + XCTAssertEqual( + try AnyValue.wrapped(["a": 1, "b": 2, "c": 3]) + .dictionaryValue.map { val in Dictionary(uniqueKeysWithValues: val.map { ($0, $1) }) }, + [.string("a"): .int(1), .string("b"): .int(2), .string("c"): .int(3)] + ) + XCTAssertEqual( + try AnyValue.wrapped([1: "a", 2: "b", 3: "c"]) + .dictionaryValue.map { val in Dictionary(uniqueKeysWithValues: val.map { ($0, $1) }) }, + [.int(1): .string("a"), .int(2): .string("b"), .int(3): .string("c")] + ) + XCTAssertEqual(try AnyValue.wrapped(["a": 1, "b": "test", "c": true] as OrderedDictionary), + .dictionary(["a": 1, "b": "test", "c": true])) + XCTAssertEqual(try AnyValue.wrapped([1: 1, 2: "test", 3: true] as OrderedDictionary), + .dictionary([1: 1, 2: "test", 3: true])) + + // Passthrough + XCTAssertEqual(try AnyValue.wrapped(AnyValue.bool(true)), .bool(true)) + XCTAssertEqual(try AnyValue.wrapped([1, 2, 3] as AnyValue.AnyArray), .array([1, 2, 3])) + XCTAssertEqual(try AnyValue.wrapped([1: "a", 2: "b", 3: "c"] as AnyValue.AnyDictionary), .dictionary([1: "a", 2: "b", 3: "c"])) + } + + func testUnwrapped() throws { + XCTAssertNil(AnyValue.nil.unwrapped) + XCTAssertEqual(AnyValue.bool(true).unwrapped as? Bool, true) + XCTAssertEqual(AnyValue.string("test").unwrapped as? String, "test") + XCTAssertEqual(AnyValue.int8(123).unwrapped as? Int8, 123) + XCTAssertEqual(AnyValue.int16(123).unwrapped as? Int16, 123) + XCTAssertEqual(AnyValue.int32(123).unwrapped as? Int32, 123) + XCTAssertEqual(AnyValue.int64(123).unwrapped as? Int64, 123) + XCTAssertEqual(AnyValue.uint8(123).unwrapped as? UInt8, 123) + XCTAssertEqual(AnyValue.uint16(123).unwrapped as? UInt16, 123) + XCTAssertEqual(AnyValue.uint32(123).unwrapped as? UInt32, 123) + XCTAssertEqual(AnyValue.uint64(123).unwrapped as? UInt64, 123) + XCTAssertEqual(AnyValue.integer(123).unwrapped as? BigInt, 123) + XCTAssertEqual(AnyValue.unsignedInteger(123).unwrapped as? BigUInt, 123) + XCTAssertEqual(AnyValue.float16(1.5).unwrapped as? Float16, 1.5) + XCTAssertEqual(AnyValue.float(1.5).unwrapped as? Float, 1.5) + XCTAssertEqual(AnyValue.double(1.5).unwrapped as? Double, 1.5) + XCTAssertEqual(AnyValue.decimal(1.5).unwrapped as? Decimal, 1.5) + XCTAssertEqual(AnyValue.data(Data([1, 2, 3])).unwrapped as? Data, Data([1, 2, 3])) + let url = URL(string: "http://example.com")! + XCTAssertEqual(AnyValue.url(url).unwrapped as? URL, url) + let uuid = UUID() + XCTAssertEqual(AnyValue.uuid(uuid).unwrapped as? UUID, uuid) + let date = Date() + XCTAssertEqual(AnyValue.date(date).unwrapped as? Date, date) + XCTAssertEqual(AnyValue.array([1, 2, 3]).unwrapped as? [Int64], [1, 2, 3]) + XCTAssertEqual( + AnyValue.dictionary(["a": 1, "b": 2, "c": 3]).unwrapped as? [String: Int64], + ["a": 1, "b": 2, "c": 3] + ) + } + + func testEncodable() throws { + XCTAssertEqual(try JSONEncoder().encodeString(AnyValue.nil), #"null"#) + XCTAssertEqual(try JSONEncoder().encodeString(AnyValue.string("test")), #""test""#) + XCTAssertEqual(try JSONEncoder().encodeString(AnyValue.bool(true)), #"true"#) + XCTAssertEqual(try JSONEncoder().encodeString(AnyValue.int8(123)), #"123"#) + XCTAssertEqual(try JSONEncoder().encodeString(AnyValue.int8(-123)), #"-123"#) + XCTAssertEqual(try JSONEncoder().encodeString(AnyValue.int16(123)), #"123"#) + XCTAssertEqual(try JSONEncoder().encodeString(AnyValue.int16(-123)), #"-123"#) + XCTAssertEqual(try JSONEncoder().encodeString(AnyValue.int32(123)), #"123"#) + XCTAssertEqual(try JSONEncoder().encodeString(AnyValue.int32(-123)), #"-123"#) + XCTAssertEqual(try JSONEncoder().encodeString(AnyValue.int64(123)), #"123"#) + XCTAssertEqual(try JSONEncoder().encodeString(AnyValue.int64(-123)), #"-123"#) + XCTAssertEqual(try JSONEncoder().encodeString(AnyValue.integer(123)), #"["+",123]"#) + XCTAssertEqual(try JSONEncoder().encodeString(AnyValue.integer(-123)), #"["-",123]"#) + XCTAssertEqual(try JSONEncoder().encodeString(AnyValue.uint8(123)), #"123"#) + XCTAssertEqual(try JSONEncoder().encodeString(AnyValue.uint16(123)), #"123"#) + XCTAssertEqual(try JSONEncoder().encodeString(AnyValue.uint32(123)), #"123"#) + XCTAssertEqual(try JSONEncoder().encodeString(AnyValue.uint64(123)), #"123"#) + XCTAssertEqual(try JSONEncoder().encodeString(AnyValue.unsignedInteger(123)), #"["+",123]"#) + XCTAssertEqual(try JSONEncoder().encodeString(AnyValue.float16(1.5)), #"1.5"#) + XCTAssertEqual(try JSONEncoder().encodeString(AnyValue.float(1.5)), #"1.5"#) + XCTAssertEqual(try JSONEncoder().encodeString(AnyValue.double(1.5)), #"1.5"#) + XCTAssertEqual(try JSONEncoder().encodeString(AnyValue.decimal(1.5)), #"1.5"#) + XCTAssertEqual(try JSONEncoder().encodeString(AnyValue.data(Data([1, 2, 3]))), #""AQID""#) + + let url = URL(string: "https://example.com")! + XCTAssertEqual(try JSONEncoder().encodeString(AnyValue.url(url)), #""https:\/\/example.com""#) + + let uuid = UUID() + XCTAssertEqual(try JSONEncoder().encodeString(AnyValue.uuid(uuid)), #""\#(uuid.uuidString)""#) + + let date = Date().truncatedToSecs + XCTAssertEqual(try JSONEncoder().encodeString(AnyValue.date(date)), #"\#(Int64(date.timeIntervalSinceReferenceDate))"#) + + XCTAssertEqual(try JSONEncoder().encodeString(AnyValue.array([1, 2, 3])), #"[1,2,3]"#) + + XCTAssertEqual(try JSONEncoder().encodeString(AnyValue.dictionary(["a": 1, "b": 2, "c": 3])), #"["a",1,"b",2,"c",3]"#) + } + + func testDecodable() throws { + XCTAssertEqual(try JSONDecoder().decodeString(AnyValue.self, from: #"null"#), .nil) + XCTAssertEqual(try JSONDecoder().decodeString(AnyValue.self, from: #"true"#), .bool(true)) + XCTAssertEqual(try JSONDecoder().decodeString(AnyValue.self, from: #"123"#), .int(123)) + XCTAssertEqual(try JSONDecoder().decodeString(AnyValue.self, from: #"-123"#), .int(-123)) + XCTAssertEqual(try JSONDecoder().decodeString(AnyValue.self, from: #"\#(Int64.max)"#), .int64(Int64.max)) + XCTAssertEqual(try JSONDecoder().decodeString(AnyValue.self, from: #"\#(Int64.min)"#), .int64(Int64.min)) + XCTAssertEqual(try JSONDecoder().decodeString(AnyValue.self, from: #"\#(UInt64.max)"#), .uint64(UInt64.max)) + XCTAssertEqual(try JSONDecoder().decodeString(AnyValue.self, from: #"["+",123]"#), .integer(123)) + XCTAssertEqual(try JSONDecoder().decodeString(AnyValue.self, from: #"["-",123]"#), .integer(-123)) + XCTAssertEqual(try JSONDecoder().decodeString(AnyValue.self, from: #"1.5"#), .double(1.5)) + XCTAssertEqual(try JSONDecoder().decodeString(AnyValue.self, from: #""test""#), .string("test")) + XCTAssertEqual(try JSONDecoder().decodeString(AnyValue.self, from: #"[1,2,3]"#), .array([1, 2, 3])) + XCTAssertEqual( + try JSONDecoder().decodeString(AnyValue.self, from: #"{"a":1,"b":2,"c":3}"#).dictionaryValue?.unordered, + AnyValue.dictionary(["a": 1, "b": 2, "c": 3]).dictionaryValue?.unordered + ) + } + + func testRoundtripOrder() throws { + + struct TestValue: Codable, Equatable { + var `nil`: AnyValue = .nil + var bool: AnyValue = .bool(true) + var string: AnyValue = .string("Hello World!") + var pi8: AnyValue = .int8(2) + var pi16: AnyValue = .int16(500) + var pi32: AnyValue = .int32(70_000) + var pi64: AnyValue = .int64(5_000_000_000) + var ni8: AnyValue = .int8(-2) + var ni16: AnyValue = .int16(-500) + var ni32: AnyValue = .int32(-70_000) + var ni64: AnyValue = .int64(-5_000_000_000) + var u8: AnyValue = .uint8(UInt8.max) + var u16: AnyValue = .uint16(UInt16.max) + var u32: AnyValue = .uint32(UInt32.max) + var u64: AnyValue = .uint64(UInt64.max) + var nint: AnyValue = .integer(BigInt("-999000000000000000000000000000")) + var pint: AnyValue = .integer(BigInt("999000000000000000000000000000")) + var uint: AnyValue = .unsignedInteger(BigUInt("999000000000000000000000000000")) + var f16: AnyValue = .float16(1.234) + var f32: AnyValue = .float(12.34567) + var f64: AnyValue = .double(123.4567) + var pdec: AnyValue = .decimal(Decimal(sign: .plus, exponent: -3, significand: 1234567)) + var ndec: AnyValue = .decimal(Decimal(sign: .minus, exponent: -3, significand: 1234567)) + var data: AnyValue = .data("Binary Data".data(using: .utf8)!) + var url: AnyValue = .url(URL(string: "https://example.com/some/thing")!) + var uuid: AnyValue = .uuid(UUID(uuidString: "46076D06-86E8-4B3B-80EF-B24115D4C609")!) + var date: AnyValue = .date(Date(timeIntervalSinceReferenceDate: 1234567.89)) + var array: AnyValue = .array([nil, false, 456, "a"]) + var object: AnyValue = .dictionary([ + "c": 1, + "a": 2, + "d": 3, + "b": 4, + ]) + } + + let srcValue = TestValue() + + let tree = try AnyValue.Encoder.default.encodeTree(srcValue) + + XCTAssertEqual(tree.nil, srcValue.nil) + XCTAssertEqual(tree.bool, srcValue.bool) + XCTAssertEqual(tree.string, srcValue.string) + XCTAssertEqual(tree.pi8, srcValue.pi8) + XCTAssertEqual(tree.pi16, srcValue.pi16) + XCTAssertEqual(tree.pi32, srcValue.pi32) + XCTAssertEqual(tree.pi64, srcValue.pi64) + XCTAssertEqual(tree.ni8, srcValue.ni8) + XCTAssertEqual(tree.ni16, srcValue.ni16) + XCTAssertEqual(tree.ni32, srcValue.ni32) + XCTAssertEqual(tree.ni64, srcValue.ni64) + XCTAssertEqual(tree.u8, srcValue.u8) + XCTAssertEqual(tree.u16, srcValue.u16) + XCTAssertEqual(tree.u32, srcValue.u32) + XCTAssertEqual(tree.u64, srcValue.u64) + XCTAssertEqual(tree.pint, srcValue.pint) + XCTAssertEqual(tree.nint, srcValue.nint) + XCTAssertEqual(tree.uint, srcValue.uint) + XCTAssertEqual(tree.f16, srcValue.f16) + XCTAssertEqual(tree.f32, srcValue.f32) + XCTAssertEqual(tree.f64, srcValue.f64) + XCTAssertEqual(tree.pdec, srcValue.pdec) + XCTAssertEqual(tree.ndec, srcValue.ndec) + XCTAssertEqual(tree.data, srcValue.data) + XCTAssertEqual(tree.url, srcValue.url) + XCTAssertEqual(tree.uuid, srcValue.uuid) + XCTAssertEqual(tree.date, srcValue.date) + XCTAssertEqual(tree.array, srcValue.array) + XCTAssertEqual(tree.object, srcValue.object) + + let dstValue = try AnyValue.Decoder.default.decodeTree(TestValue.self, from: tree) + + XCTAssertEqual(dstValue.nil, srcValue.nil) + XCTAssertEqual(dstValue.bool, srcValue.bool) + XCTAssertEqual(dstValue.string, srcValue.string) + XCTAssertEqual(dstValue.pi8, srcValue.pi8) + XCTAssertEqual(dstValue.pi16, srcValue.pi16) + XCTAssertEqual(dstValue.pi32, srcValue.pi32) + XCTAssertEqual(dstValue.pi64, srcValue.pi64) + XCTAssertEqual(dstValue.ni8, srcValue.ni8) + XCTAssertEqual(dstValue.ni16, srcValue.ni16) + XCTAssertEqual(dstValue.ni32, srcValue.ni32) + XCTAssertEqual(dstValue.ni64, srcValue.ni64) + XCTAssertEqual(dstValue.u8, srcValue.u8) + XCTAssertEqual(dstValue.u16, srcValue.u16) + XCTAssertEqual(dstValue.u32, srcValue.u32) + XCTAssertEqual(dstValue.u64, srcValue.u64) + XCTAssertEqual(dstValue.pint, srcValue.pint) + XCTAssertEqual(dstValue.nint, srcValue.nint) + XCTAssertEqual(dstValue.uint, srcValue.uint) + XCTAssertEqual(dstValue.f16, srcValue.f16) + XCTAssertEqual(dstValue.f32, srcValue.f32) + XCTAssertEqual(dstValue.f64, srcValue.f64) + XCTAssertEqual(dstValue.pdec, srcValue.pdec) + XCTAssertEqual(dstValue.ndec, srcValue.ndec) + XCTAssertEqual(dstValue.data, srcValue.data) + XCTAssertEqual(dstValue.url, srcValue.url) + XCTAssertEqual(dstValue.uuid, srcValue.uuid) + XCTAssertEqual(dstValue.date, srcValue.date) + XCTAssertEqual(dstValue.array, srcValue.array) + XCTAssertEqual(dstValue.object, srcValue.object) + } + +} + + +extension JSONEncoder { + + func encodeString(_ value: E?) throws -> String { + return String(data: try encode(value), encoding: .utf8)! + } + +} + +extension JSONDecoder { + + func decodeString(_ type: D.Type, from string: String) throws -> D { + return try decode(type, from: string.data(using: .utf8)!) } } +extension OrderedDictionary where Key == AnyValue, Value == AnyValue { -private class TestValue: Codable { - let a: Int - let b: String - let c: [Bool] - let d: TestValue? - let e: AnyValue? - - init(a: Int, b: String, c: [Bool], d: TestValue? = nil, e: AnyValue? = nil) { - self.a = a - self.b = b - self.c = c - self.d = d - self.e = e - } - - required init(from: Decoder) throws { - let container = try from.container(keyedBy: Self.CodingKeys.self) - a = try container.decode(Int.self, forKey: .a) - b = try container.decode(String.self, forKey: .b) - c = try container.decode([Bool].self, forKey: .c) - d = try container.decodeIfPresent(TestValue.self, forKey: .d) - e = try container.decodeIfPresent(AnyValue.self, forKey: .e) + var unordered: [AnyValue: AnyValue] { + return Dictionary(uniqueKeysWithValues: self.map { ($0, $1) }) } } diff --git a/Tests/Assertions.swift b/Tests/Assertions.swift new file mode 100644 index 000000000..12eb03601 --- /dev/null +++ b/Tests/Assertions.swift @@ -0,0 +1,86 @@ +// +// Assertions.swift +// PotentCodables +// +// Copyright © 2021 Outfox, inc. +// +// +// Distributed under the MIT License, See LICENSE for details. +// + +import Foundation +@testable import PotentJSON +import XCTest + + +public func AssertDecodingTypeMismatch(_ error: Error, file: StaticString = #file, line: UInt = #line) { + func check() -> Bool { + if case DecodingError.typeMismatch = error { + return true + } + else { + return false + } + } + XCTAssertTrue(check(), file: file, line: line) +} + +public func AssertDecodingDataCorrupted(_ error: Error, file: StaticString = #file, line: UInt = #line) { + func check() -> Bool { + if case DecodingError.dataCorrupted = error { + return true + } + else { + return false + } + } + XCTAssertTrue(check(), file: file, line: line) +} + +public func AssertDecodingKeyNotFound(_ error: Error, file: StaticString = #file, line: UInt = #line) { + func check() -> Bool { + if case DecodingError.keyNotFound = error { + return true + } + else { + return false + } + } + XCTAssertTrue(check(), file: file, line: line) +} + +public func AssertDecodingValueNotFound(_ error: Error, file: StaticString = #file, line: UInt = #line) { + func check() -> Bool { + if case DecodingError.valueNotFound = error { + return true + } + else { + return false + } + } + XCTAssertTrue(check(), file: file, line: line) +} + +public func AssertEncodingInvalidValue(_ error: Error, file: StaticString = #file, line: UInt = #line) { + func check() -> Bool { + if case EncodingError.invalidValue = error { + return true + } + else { + return false + } + } + XCTAssertTrue(check(), file: file, line: line) +} + +public func AssertJSONErrorInvalidData(_ error: Error, file: StaticString = #file, line: UInt = #line) { + func check() -> Bool { + if case JSONReader.Error.invalidData = error { + return true + } + else { + return false + } + } + XCTAssertTrue(check(), file: file, line: line) +} diff --git a/Tests/CBORAnyValueTests.swift b/Tests/CBORAnyValueTests.swift new file mode 100644 index 000000000..2d7350223 --- /dev/null +++ b/Tests/CBORAnyValueTests.swift @@ -0,0 +1,164 @@ +// +// CBORAnyValueTests.swift +// PotentCodables +// +// Copyright © 2021 Outfox, inc. +// +// +// Distributed under the MIT License, See LICENSE for details. +// + +import Foundation +import PotentCBOR +import PotentCodables +import BigInt +import XCTest + + +class CBORAnyValueTests: XCTestCase { + + func testDecode() throws { + + struct TestValue: Codable { + var unsigned: AnyValue + var negative: AnyValue + var byteString: AnyValue + var utf8String: AnyValue + var array: AnyValue + var map: AnyValue + var tagged: AnyValue + var simple: AnyValue + var boolean: AnyValue + var null: AnyValue + var undefined: AnyValue + var half: AnyValue + var float: AnyValue + var double: AnyValue + } + + // swiftlint:disable:next line_length + let cbor = Data(base64Encoded: "rmh1bnNpZ25lZBh7aG5lZ2F0aXZlOQHHamJ5dGVTdHJpbmdLQmluYXJ5IERhdGFqdXRmOFN0cmluZ2xIZWxsbyBXb3JsZCFlYXJyYXmE9vQZAchhYWNtYXCkYWMBYWECYWQDYWIEZnRhZ2dlZMB4GDIwMjAtMTItMjFUMTI6MzQ6NTYuMTIzWmZzaW1wbGXlZ2Jvb2xlYW71ZG51bGz2aXVuZGVmaW5lZPdkaGFsZvk88GVmbG9hdPpBRYfdZmRvdWJsZftAXt06kqMFUw==")! + + let value = try CBOR.Decoder.default.decode(TestValue.self, from: cbor) + XCTAssertEqual(value.unsigned, AnyValue.int64(123)) + XCTAssertEqual(value.negative, AnyValue.int64(-456)) + XCTAssertEqual(value.byteString, AnyValue.data("Binary Data".data(using: .utf8)!)) + XCTAssertEqual(value.utf8String, AnyValue.string("Hello World!")) + XCTAssertEqual(value.array, AnyValue.array([.nil, .bool(false), .int64(456), .string("a")])) + XCTAssertEqual(value.map, AnyValue.dictionary(["c": .int64(1), "a": .int64(2), "d": .int64(3), "b": .int64(4)])) + XCTAssertEqual(value.tagged, AnyValue.date(ZonedDate(iso8601Encoded: "2020-12-21T12:34:56.123Z")!.utcDate)) + XCTAssertEqual(value.simple, AnyValue.uint8(5)) + XCTAssertEqual(value.boolean, AnyValue.bool(true)) + XCTAssertEqual(value.null, AnyValue.nil) + XCTAssertEqual(value.undefined, AnyValue.nil) + XCTAssertEqual(value.half, AnyValue.float16(CBOR.Half("1.234")!)) + XCTAssertEqual(value.float, AnyValue.float(CBOR.Float("12.34567")!)) + XCTAssertEqual(value.double, AnyValue.double(123.4567)) + } + + func testEncode() throws { + + struct TestValue: Codable, Equatable { + var `nil`: AnyValue = .nil + var bool: AnyValue = .bool(true) + var string: AnyValue = .string("Hello World!") + var pi8: AnyValue = .int8(2) + var pi16: AnyValue = .int16(500) + var pi32: AnyValue = .int32(70_000) + var pi64: AnyValue = .int64(5_000_000_000) + var ni8: AnyValue = .int8(-2) + var ni16: AnyValue = .int16(-500) + var ni32: AnyValue = .int32(-70_000) + var ni64: AnyValue = .int64(-5_000_000_000) + var u8: AnyValue = .uint8(UInt8.max) + var u16: AnyValue = .uint16(UInt16.max) + var u32: AnyValue = .uint32(UInt32.max) + var u64: AnyValue = .uint64(UInt64.max) + var nint: AnyValue = .integer(BigInt("-999000000000000000000000000000")) + var pint: AnyValue = .integer(BigInt("999000000000000000000000000000")) + var uint: AnyValue = .unsignedInteger(BigUInt("999000000000000000000000000000")) + var f16: AnyValue = .float16(1.234) + var f32: AnyValue = .float(12.34567) + var f64: AnyValue = .double(123.4567) + var ppdec: AnyValue = .decimal(Decimal(sign: .plus, exponent: 1, significand: 1234567)) + var pndec: AnyValue = .decimal(Decimal(sign: .plus, exponent: -3, significand: 1234567)) + var npdec: AnyValue = .decimal(Decimal(sign: .minus, exponent: 1, significand: 1234567)) + var nndec: AnyValue = .decimal(Decimal(sign: .minus, exponent: -3, significand: 1234567)) + var data: AnyValue = .data("Binary Data".data(using: .utf8)!) + var url: AnyValue = .url(URL(string: "https://example.com/some/thing")!) + var uuid: AnyValue = .uuid(UUID(uuidString: "46076D06-86E8-4B3B-80EF-B24115D4C609")!) + var date: AnyValue = .date(Date(timeIntervalSinceReferenceDate: 1234567.89)) + var array: AnyValue = .array([nil, false, 456, "a"]) + var object: AnyValue = .dictionary([ + "c": 1, + "a": 2, + "d": 3, + "b": 4, + ]) + } + let srcValue = TestValue() + + // swiftlint:disable:next line_length + let cbor = Data(hexEncoded: "b81f636e696cf664626f6f6cf566737472696e676c48656c6c6f20576f726c6421637069380264706931361901f464706933321a0001117064706936341b000000012a05f200636e693821646e6931363901f3646e6933323a0001116f646e6936343b000000012a05f1ff62753818ff6375313619ffff637533321affffffff637536341bffffffffffffffff646e696e74c34d0c9bf16e93a6a46dad57ffffff6470696e74c24d0c9bf16e93a6a46dad580000006475696e74c24d0c9bf16e93a6a46dad5800000063663136f93cf063663332fa414587dd63663634fb405edd3a92a30553657070646563c48201c24312d68765706e646563c48222c24312d687656e70646563c48201c34312d686656e6e646563c48222c34312d68664646174614b42696e61727920446174616375726cd820781e68747470733a2f2f6578616d706c652e636f6d2f736f6d652f7468696e676475756964d8255046076d0686e84b3b80efb24115d4c6096464617465c07818323030312d30312d31355430363a35363a30372e3839305a65617272617984f6f41901c86161666f626a656374a4616301616102616403616204") + + XCTAssertEqual(try CBOR.Encoder.default.encode(srcValue), cbor) + + let dstValue = try CBOR.Decoder.default.decode(TestValue.self, from: cbor) + XCTAssertEqual(dstValue.nil, srcValue.nil) + XCTAssertEqual(dstValue.bool, srcValue.bool) + XCTAssertEqual(dstValue.string, srcValue.string) + XCTAssertEqual(dstValue.pi8, .int64(2)) + XCTAssertEqual(dstValue.pi16, .int64(500)) + XCTAssertEqual(dstValue.pi32, .int64(70_000)) + XCTAssertEqual(dstValue.pi64, srcValue.pi64) + XCTAssertEqual(dstValue.ni8, .int64(-2)) + XCTAssertEqual(dstValue.ni16, .int64(-500)) + XCTAssertEqual(dstValue.ni32, .int64(-70_000)) + XCTAssertEqual(dstValue.ni64, srcValue.ni64) + XCTAssertEqual(dstValue.u8, .int64(Int64(UInt8.max))) + XCTAssertEqual(dstValue.u16, .int64(Int64(UInt16.max))) + XCTAssertEqual(dstValue.u32, .int64(Int64(UInt32.max))) + XCTAssertEqual(dstValue.u64, .uint64(UInt64.max)) + XCTAssertEqual(dstValue.pint, srcValue.pint) + XCTAssertEqual(dstValue.nint, srcValue.nint) + XCTAssertEqual(dstValue.uint, .integer(BigInt("999000000000000000000000000000"))) + XCTAssertEqual(dstValue.f16, srcValue.f16) + XCTAssertEqual(dstValue.f32, srcValue.f32) + XCTAssertEqual(dstValue.f64, srcValue.f64) + XCTAssertEqual(dstValue.ppdec, srcValue.ppdec) + XCTAssertEqual(dstValue.pndec, srcValue.pndec) + XCTAssertEqual(dstValue.npdec, srcValue.npdec) + XCTAssertEqual(dstValue.nndec, srcValue.nndec) + XCTAssertEqual(dstValue.data, srcValue.data) + XCTAssertEqual(dstValue.url, srcValue.url) + XCTAssertEqual(dstValue.uuid, srcValue.uuid) + XCTAssertEqual(dstValue.date.dateValue?.timeIntervalSince1970, srcValue.date.dateValue?.timeIntervalSince1970) + XCTAssertEqual(dstValue.array, .array([nil, false, .int64(456), "a"])) + XCTAssertEqual(dstValue.object, .dictionary(["c": .int64(1), "a": .int64(2), "d": .int64(3), "b": .int64(4)])) + } + + func testDecodeBase64Data() throws { + + struct TestValue: Codable { + var b64Data: AnyValue + } + + let cbor = Data(hexEncoded: "A16762363444617461D82270516D6C7559584A35494552686447453D") + + let dstValue = try CBOR.Decoder.default.decode(TestValue.self, from: cbor) + XCTAssertEqual(dstValue.b64Data, .data("Binary Data".data(using: .utf8)!)) + } + + func testDecodeTaggedInt() throws { + + struct TestValue: Codable { + var value: AnyValue + } + + let cbor = Data(hexEncoded: "A16576616C7565C501") + + let dstValue = try CBOR.Decoder.default.decode(TestValue.self, from: cbor) + XCTAssertEqual(dstValue.value, .int64(1)) + } + +} diff --git a/Tests/CBORCodableRoundtripTests.swift b/Tests/CBORCodableRoundtripTests.swift index e966ae5de..15ea72268 100644 --- a/Tests/CBORCodableRoundtripTests.swift +++ b/Tests/CBORCodableRoundtripTests.swift @@ -184,13 +184,9 @@ class CBORCodableRoundtripTests: XCTestCase { let oneTwoThree = try CBOREncoder().encode([1, 2, 3]) let oneTwoThreeDecoded = try CBORDecoder().decode([Int].self, from: oneTwoThree) XCTAssertEqual(oneTwoThreeDecoded, [1, 2, 3]) - let lotsOfInts = try CBOREncoder() - .encode([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25]) + let lotsOfInts = try CBOREncoder().encode(Array(-512 ... 512)) let lotsOfIntsDecoded = try CBORDecoder().decode([Int].self, from: lotsOfInts) - XCTAssertEqual( - lotsOfIntsDecoded, - [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25] - ) + XCTAssertEqual(lotsOfIntsDecoded, Array(-512 ... 512)) let nestedSimple = try CBOREncoder().encode([[1], [2, 3], [4, 5]]) let nestedSimpleDecoded = try CBORDecoder().decode([[Int]].self, from: nestedSimple) XCTAssertEqual(nestedSimpleDecoded, [[1], [2, 3], [4, 5]]) diff --git a/Tests/CBORDecoderTests.swift b/Tests/CBORDecoderTests.swift index df6d6162f..c833fa2a9 100644 --- a/Tests/CBORDecoderTests.swift +++ b/Tests/CBORDecoderTests.swift @@ -8,156 +8,769 @@ // Distributed under the MIT License, See LICENSE for details. // +import BigInt @testable import PotentCBOR @testable import PotentCodables import XCTest class CBORDecoderTests: XCTestCase { - static var allTests = [ - ("testDecodeNull", testDecodeNull), - ("testDecodeBools", testDecodeBools), - ("testDecodeInts", testDecodeInts), - ("testDecodeNegativeInts", testDecodeNegativeInts), - ("testDecodeStrings", testDecodeStrings), - ("testDecodeByteStrings", testDecodeByteStrings), - ("testDecodeArrays", testDecodeArrays), - ("testDecodeMaps", testDecodeMaps), - ("testDecodeDates", testDecodeDates), - ] func testDecodeNull() { - XCTAssertNil(try CBORDecoder().decodeIfPresent(String.self, from: Data([0xF6]))) + XCTAssertNil(try CBORDecoder.default.decodeIfPresent(String.self, from: Data([0xF6]))) } func testDecodeBools() { - XCTAssertEqual(try CBORDecoder().decode(Bool.self, from: Data([0xF4])), false) - XCTAssertEqual(try CBORDecoder().decode(Bool.self, from: Data([0xF5])), true) + XCTAssertEqual(try CBORDecoder.default.decode(Bool.self, from: Data([0xF4])), false) + XCTAssertEqual(try CBORDecoder.default.decode(Bool.self, from: Data([0xF5])), true) + XCTAssertThrowsError(try CBORDecoder.default.decode(Bool.self, from: Data([0x1]))) { error in + AssertDecodingTypeMismatch(error) + } + XCTAssertEqual(try CBORDecoder.default.decodeIfPresent(Bool.self, from: Data([0xf6])), nil) } func testDecodeInts() { // Less than 24 - XCTAssertEqual(try CBORDecoder().decode(Int.self, from: Data([0x00])), 0) - XCTAssertEqual(try CBORDecoder().decode(Int.self, from: Data([0x08])), 8) - XCTAssertEqual(try CBORDecoder().decode(Int.self, from: Data([0x0A])), 10) - XCTAssertEqual(try CBORDecoder().decode(Int.self, from: Data([0x17])), 23) + XCTAssertEqual(try CBORDecoder.default.decode(Int.self, from: Data([0x00])), 0) + XCTAssertEqual(try CBORDecoder.default.decode(Int.self, from: Data([0x08])), 8) + XCTAssertEqual(try CBORDecoder.default.decode(Int.self, from: Data([0x0A])), 10) + XCTAssertEqual(try CBORDecoder.default.decode(Int.self, from: Data([0x17])), 23) // Just bigger than 23 - XCTAssertEqual(try CBORDecoder().decode(Int.self, from: Data([0x18, 0x18])), 24) - XCTAssertEqual(try CBORDecoder().decode(Int.self, from: Data([0x18, 0x19])), 25) + XCTAssertEqual(try CBORDecoder.default.decode(Int.self, from: Data([0x18, 0x18])), 24) + XCTAssertEqual(try CBORDecoder.default.decode(Int.self, from: Data([0x18, 0x19])), 25) // Bigger - XCTAssertEqual(try CBORDecoder().decode(Int.self, from: Data([0x18, 0x64])), 100) - XCTAssertEqual(try CBORDecoder().decode(Int.self, from: Data([0x19, 0x03, 0xE8])), 1000) - XCTAssertEqual(try CBORDecoder().decode(Int.self, from: Data([0x1A, 0x00, 0x0F, 0x42, 0x40])), 1_000_000) + XCTAssertEqual(try CBORDecoder.default.decode(Int.self, from: Data([0x18, 0x64])), 100) + XCTAssertEqual(try CBORDecoder.default.decode(Int.self, from: Data([0x19, 0x03, 0xE8])), 1000) + XCTAssertEqual(try CBORDecoder.default.decode(Int.self, from: Data([0x1A, 0x00, 0x0F, 0x42, 0x40])), 1_000_000) XCTAssertEqual( - try CBORDecoder().decode(Int64.self, from: Data([0x1B, 0x00, 0x00, 0x00, 0xE8, 0xD4, 0xA5, 0x10, 0x00])), + try CBORDecoder.default.decode(Int64.self, from: Data([0x1B, 0x00, 0x00, 0x00, 0xE8, 0xD4, 0xA5, 0x10, 0x00])), Int64(1_000_000_000_000) ) // Biggest XCTAssertEqual( - try CBORDecoder().decode(UInt64.self, from: Data([0x1B, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF])), + try CBORDecoder.default.decode(UInt64.self, from: Data([0x1B, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF])), 18_446_744_073_709_551_615 ) } func testDecodeNegativeInts() { // Less than 24 - XCTAssertEqual(try CBORDecoder().decode(Int.self, from: Data([0x20])), -1) - XCTAssertEqual(try CBORDecoder().decode(Int.self, from: Data([0x29])), -10) + XCTAssertEqual(try CBORDecoder.default.decode(Int.self, from: Data([0x20])), -1) + XCTAssertEqual(try CBORDecoder.default.decode(Int.self, from: Data([0x29])), -10) // Bigger - XCTAssertEqual(try CBORDecoder().decode(Int.self, from: Data([0x38, 0x63])), -100) - XCTAssertEqual(try CBORDecoder().decode(Int.self, from: Data([0x39, 0x03, 0xE7])), -1000) + XCTAssertEqual(try CBORDecoder.default.decode(Int.self, from: Data([0x38, 0x63])), -100) + XCTAssertEqual(try CBORDecoder.default.decode(Int.self, from: Data([0x39, 0x03, 0xE7])), -1000) // Overflow - XCTAssertThrowsError(try CBORDecoder().decode(Int8.self, from: Data([0x38, 0x80]))) + XCTAssertThrowsError(try CBORDecoder.default.decode(Int8.self, from: Data([0x38, 0x80]))) // Biggest XCTAssertEqual( - try CBORDecoder().decode(Int64.self, from: Data([0x3B, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF])), + try CBORDecoder.default.decode(Int64.self, from: Data([0x3B, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF])), -9_223_372_036_854_775_808 ) } + func testDecodeInt() { + // From simple + XCTAssertEqual( + try CBORDecoder.default.decode(Int.self, from: Data([0xE3])), + Int(3) + ) + // From unsigned int + XCTAssertEqual( + try CBORDecoder.default.decode(Int.self, from: Data([0x03])), + Int(3) + ) + // From negative int + XCTAssertEqual( + try CBORDecoder.default.decode(Int.self, from: Data([0x22])), + Int(-3) + ) + // From positive bignum + XCTAssertEqual( + try CBORDecoder.default.decode(Int.self, from: Data([0xC2, 0x41, 0x03])), + Int(3) + ) + // From negative bignum + XCTAssertEqual( + try CBORDecoder.default.decode(Int.self, from: Data([0xC3, 0x41, 0x02])), + Int(-3) + ) + } + + func testDecodeInt8() { + // From simple + XCTAssertEqual( + try CBORDecoder.default.decode(Int8.self, from: Data([0xE3])), + Int8(3) + ) + // From unsigned int + XCTAssertEqual( + try CBORDecoder.default.decode(Int8.self, from: Data([0x03])), + Int8(3) + ) + // From negative int + XCTAssertEqual( + try CBORDecoder.default.decode(Int8.self, from: Data([0x22])), + Int8(-3) + ) + // From positive bignum + XCTAssertEqual( + try CBORDecoder.default.decode(Int8.self, from: Data([0xC2, 0x41, 0x03])), + Int8(3) + ) + // From negative bignum + XCTAssertEqual( + try CBORDecoder.default.decode(Int8.self, from: Data([0xC3, 0x41, 0x02])), + Int8(-3) + ) + } + + func testDecodeInt16() { + // From simple + XCTAssertEqual( + try CBORDecoder.default.decode(Int16.self, from: Data([0xE3])), + Int16(3) + ) + // From unsigned int + XCTAssertEqual( + try CBORDecoder.default.decode(Int16.self, from: Data([0x03])), + Int16(3) + ) + // From negative int + XCTAssertEqual( + try CBORDecoder.default.decode(Int16.self, from: Data([0x22])), + Int16(-3) + ) + // From positive bignum + XCTAssertEqual( + try CBORDecoder.default.decode(Int16.self, from: Data([0xC2, 0x41, 0x03])), + Int16(3) + ) + // From negative bignum + XCTAssertEqual( + try CBORDecoder.default.decode(Int16.self, from: Data([0xC3, 0x41, 0x02])), + Int16(-3) + ) + } + + func testDecodeInt32() { + // From simple + XCTAssertEqual( + try CBORDecoder.default.decode(Int32.self, from: Data([0xE3])), + Int32(3) + ) + // From unsigned int + XCTAssertEqual( + try CBORDecoder.default.decode(Int32.self, from: Data([0x03])), + Int32(3) + ) + // From negative int + XCTAssertEqual( + try CBORDecoder.default.decode(Int32.self, from: Data([0x22])), + Int32(-3) + ) + // From positive bignum + XCTAssertEqual( + try CBORDecoder.default.decode(Int32.self, from: Data([0xC2, 0x41, 0x03])), + Int32(3) + ) + // From negative bignum + XCTAssertEqual( + try CBORDecoder.default.decode(Int32.self, from: Data([0xC3, 0x41, 0x02])), + Int32(-3) + ) + } + + func testDecodeInt64() { + // From simple + XCTAssertEqual( + try CBORDecoder.default.decode(Int64.self, from: Data([0xE3])), + Int64(3) + ) + // From unsigned int + XCTAssertEqual( + try CBORDecoder.default.decode(Int64.self, from: Data([0x03])), + Int64(3) + ) + // From negative int + XCTAssertEqual( + try CBORDecoder.default.decode(Int64.self, from: Data([0x22])), + Int64(-3) + ) + // From positive bignum + XCTAssertEqual( + try CBORDecoder.default.decode(Int64.self, from: Data([0xC2, 0x41, 0x03])), + Int64(3) + ) + // From negative bignum + XCTAssertEqual( + try CBORDecoder.default.decode(Int64.self, from: Data([0xC3, 0x41, 0x02])), + Int64(-3) + ) + } + + func testDecodeUInt() { + // From simple + XCTAssertEqual( + try CBORDecoder.default.decode(UInt.self, from: Data([0xE3])), + UInt(3) + ) + // From unsigned int + XCTAssertEqual( + try CBORDecoder.default.decode(UInt.self, from: Data([0x03])), + UInt(3) + ) + // From negative int + XCTAssertThrowsError(try CBORDecoder.default.decode(UInt.self, from: Data([0x22]))) { error in + AssertDecodingTypeMismatch(error) + } + // From positive bignum + XCTAssertEqual( + try CBORDecoder.default.decode(UInt.self, from: Data([0xC2, 0x41, 0x03])), + UInt(3) + ) + // From negative bignum + XCTAssertThrowsError(try CBORDecoder.default.decode(UInt.self, from: Data([0xC3, 0x41, 0x02]))) { error in + AssertDecodingTypeMismatch(error) + } + } + + func testDecodeUInt8() { + // From simple + XCTAssertEqual( + try CBORDecoder.default.decode(UInt8.self, from: Data([0xE3])), + UInt8(3) + ) + // From unsigned int + XCTAssertEqual( + try CBORDecoder.default.decode(UInt8.self, from: Data([0x03])), + UInt8(3) + ) + // From negative int + XCTAssertThrowsError(try CBORDecoder.default.decode(UInt8.self, from: Data([0x22]))) { error in + AssertDecodingTypeMismatch(error) + } + // From positive bignum + XCTAssertEqual( + try CBORDecoder.default.decode(UInt8.self, from: Data([0xC2, 0x41, 0x03])), + UInt8(3) + ) + // From negative bignum + XCTAssertThrowsError(try CBORDecoder.default.decode(UInt8.self, from: Data([0xC3, 0x41, 0x02]))) { error in + AssertDecodingTypeMismatch(error) + } + } + + func testDecodeUInt16() { + // From simple + XCTAssertEqual( + try CBORDecoder.default.decode(UInt16.self, from: Data([0xE3])), + UInt16(3) + ) + // From unsigned int + XCTAssertEqual( + try CBORDecoder.default.decode(UInt16.self, from: Data([0x03])), + UInt16(3) + ) + // From negative int + XCTAssertThrowsError(try CBORDecoder.default.decode(UInt16.self, from: Data([0x22]))) { error in + AssertDecodingTypeMismatch(error) + } + // From positive bignum + XCTAssertEqual( + try CBORDecoder.default.decode(UInt16.self, from: Data([0xC2, 0x41, 0x03])), + UInt16(3) + ) + // From negative bignum + XCTAssertThrowsError(try CBORDecoder.default.decode(UInt16.self, from: Data([0xC3, 0x41, 0x02]))) { error in + AssertDecodingTypeMismatch(error) + } + } + + func testDecodeUInt32() { + // From simple + XCTAssertEqual( + try CBORDecoder.default.decode(UInt32.self, from: Data([0xE3])), + UInt32(3) + ) + // From unsigned int + XCTAssertEqual( + try CBORDecoder.default.decode(UInt32.self, from: Data([0x03])), + UInt32(3) + ) + // From negative int + XCTAssertThrowsError(try CBORDecoder.default.decode(UInt32.self, from: Data([0x22]))) { error in + AssertDecodingTypeMismatch(error) + } + // From positive bignum + XCTAssertEqual( + try CBORDecoder.default.decode(UInt32.self, from: Data([0xC2, 0x41, 0x03])), + UInt32(3) + ) + // From negative bignum + XCTAssertThrowsError(try CBORDecoder.default.decode(UInt32.self, from: Data([0xC3, 0x41, 0x02]))) { error in + AssertDecodingTypeMismatch(error) + } + } + + func testDecodeUInt64() { + // From simple + XCTAssertEqual( + try CBORDecoder.default.decode(UInt64.self, from: Data([0xE3])), + UInt64(3) + ) + // From unsigned int + XCTAssertEqual( + try CBORDecoder.default.decode(UInt64.self, from: Data([0x03])), + UInt64(3) + ) + // From negative int + XCTAssertThrowsError(try CBORDecoder.default.decode(UInt64.self, from: Data([0x22]))) { error in + AssertDecodingTypeMismatch(error) + } + // From positive bignum + XCTAssertEqual( + try CBORDecoder.default.decode(UInt64.self, from: Data([0xC2, 0x41, 0x03])), + UInt64(3) + ) + // From negative bignum + XCTAssertThrowsError(try CBORDecoder.default.decode(UInt64.self, from: Data([0xC3, 0x41, 0x02]))) { error in + AssertDecodingTypeMismatch(error) + } + } + + func testDecodeTaggedInt() { + XCTAssertEqual(try CBORDecoder.default.decode(Int.self, from: Data([0xC5, 0x1])), 1) + } + + func testDecodeIntFromUnsupportedValue() { + XCTAssertThrowsError(try CBORDecoder.default.decode(Int.self, from: Data([0xF5]))) { error in + AssertDecodingTypeMismatch(error) + } + } + + func testDecodeBigInt() { + // From simple + XCTAssertEqual( + try CBORDecoder.default.decode(BigInt.self, from: Data([0xE3])), + BigInt(3) + ) + // From unsigned int + XCTAssertEqual( + try CBORDecoder.default.decode(BigInt.self, from: Data([0x03])), + BigInt(3) + ) + // From negative int + XCTAssertEqual( + try CBORDecoder.default.decode(BigInt.self, from: Data([0x22])), + BigInt(-3) + ) + // From positive bignum + XCTAssertEqual( + try CBORDecoder.default.decode(BigInt.self, from: Data([0xC2, 0x41, 0x03])), + BigInt(3) + ) + // From negative bignum + XCTAssertEqual( + try CBORDecoder.default.decode(BigInt.self, from: Data([0xC3, 0x41, 0x02])), + BigInt(-3) + ) + } + + func testDecodeTaggedBigInt() { + XCTAssertEqual(try CBORDecoder.default.decode(BigInt.self, from: Data([0xC5, 0x1])), 1) + } + + func testDecodeBigIntFromUnsupportedValue() { + XCTAssertThrowsError(try CBORDecoder.default.decode(BigInt.self, from: Data([0xF5]))) { error in + AssertDecodingTypeMismatch(error) + } + } + + func testDecodeBigUInt() { + // From simple + XCTAssertEqual( + try CBORDecoder.default.decode(BigUInt.self, from: Data([0xE3])), + BigUInt(3) + ) + // From unsigned int + XCTAssertEqual( + try CBORDecoder.default.decode(BigUInt.self, from: Data([0x03])), + BigUInt(3) + ) + // From negative int + XCTAssertThrowsError(try CBORDecoder.default.decode(BigUInt.self, from: Data([0x22]))) { error in + AssertDecodingTypeMismatch(error) + } + // From positive bignum + XCTAssertEqual( + try CBORDecoder.default.decode(BigUInt.self, from: Data([0xC2, 0x41, 0x03])), + BigUInt(3) + ) + // From negative bignum + XCTAssertThrowsError(try CBORDecoder.default.decode(UInt.self, from: Data([0xC3, 0x41, 0x02]))) { error in + AssertDecodingTypeMismatch(error) + } + } + + func testDecodeTaggedBigUInt() { + XCTAssertEqual(try CBORDecoder.default.decode(BigUInt.self, from: Data([0xC5, 0x1])), 1) + } + + func testDecodeBigUIntFromUnsupportedValue() { + XCTAssertThrowsError(try CBORDecoder.default.decode(BigUInt.self, from: Data([0xF5]))) { error in + AssertDecodingTypeMismatch(error) + } + } + + func testDecodeInvalidDecimalFractions() { + XCTAssertThrowsError( + try CBORDecoder.default.decode(Double.self, from: Data([0xC4, 0x82, 0xF6, 0xF6])) + ) { error in + AssertDecodingTypeMismatch(error) + } + XCTAssertThrowsError( + try CBORDecoder.default.decode(Decimal.self, from: Data([0xC4, 0x82, 0xF6, 0xF6])) + ) { error in + AssertDecodingTypeMismatch(error) + } + } + + func testDecodeHalf() { + // From simple + XCTAssertEqual( + try CBORDecoder.default.decode(CBOR.Half.self, from: Data([0xE3])), + CBOR.Half(3) + ) + // From unsigned int + XCTAssertEqual( + try CBORDecoder.default.decode(CBOR.Half.self, from: Data([0x03])), + CBOR.Half(3) + ) + // From negative int + XCTAssertEqual( + try CBORDecoder.default.decode(CBOR.Half.self, from: Data([0x22])), + CBOR.Half(-3) + ) + // From positive bignum + XCTAssertEqual( + try CBORDecoder.default.decode(CBOR.Half.self, from: Data([0xC2, 0x41, 0x03])), + CBOR.Half(3) + ) + // From negative bignum + XCTAssertEqual( + try CBORDecoder.default.decode(CBOR.Half.self, from: Data([0xC3, 0x41, 0x02])), + CBOR.Half(-3) + ) + // From positive decimal fraction (neg exp) + XCTAssertEqual( + try CBORDecoder.default.decode(CBOR.Half.self, from: Data([0xC4, 0x82, 0x22, 0xC2, 0x43, 0x12, 0xD6, 0x87])), + CBOR.Half(1234.567) + ) + // From positive decimal fraction (pos exp) + XCTAssertEqual( + try CBORDecoder.default.decode(CBOR.Half.self, from: Data([0xC4, 0x82, 0x01, 0xC2, 0x41, 0x12])), + CBOR.Half(180.0) + ) + // From negative decimal fraction (neg exp) + XCTAssertEqual( + try CBORDecoder.default.decode(CBOR.Half.self, from: Data([0xC4, 0x82, 0x22, 0xC3, 0x43, 0x12, 0xD6, 0x86])), + CBOR.Half(-1234.567) + ) + // From negative decimal fraction (pos exp) + XCTAssertEqual( + try CBORDecoder.default.decode(CBOR.Half.self, from: Data([0xC4, 0x82, 0x01, 0xC3, 0x41, 0x11])), + CBOR.Half(-180.0) + ) + } + + func testDecodeFloat() { + // From simple + XCTAssertEqual( + try CBORDecoder.default.decode(Float.self, from: Data([0xE3])), + Float(3) + ) + // From unsigned int + XCTAssertEqual( + try CBORDecoder.default.decode(Float.self, from: Data([0x03])), + Float(3) + ) + // From negative int + XCTAssertEqual( + try CBORDecoder.default.decode(Float.self, from: Data([0x22])), + Float(-3) + ) + // From positive bignum + XCTAssertEqual( + try CBORDecoder.default.decode(Float.self, from: Data([0xC2, 0x41, 0x03])), + Float(3) + ) + // From negative bignum + XCTAssertEqual( + try CBORDecoder.default.decode(Float.self, from: Data([0xC3, 0x41, 0x02])), + Float(-3) + ) + // From positive decimal fraction (neg exp) + XCTAssertEqual( + try CBORDecoder.default.decode(Float.self, from: Data([0xC4, 0x82, 0x22, 0xC2, 0x43, 0x12, 0xD6, 0x87])), + Float(1234.567) + ) + // From positive decimal fraction (pos exp) + XCTAssertEqual( + try CBORDecoder.default.decode(Float.self, from: Data([0xC4, 0x82, 0x01, 0xC2, 0x43, 0x12, 0xD6, 0x87])), + Float(12345670.0) + ) + // From negative decimal fraction (neg exp) + XCTAssertEqual( + try CBORDecoder.default.decode(Float.self, from: Data([0xC4, 0x82, 0x22, 0xC3, 0x43, 0x12, 0xD6, 0x86])), + Float(-1234.567) + ) + // From negative decimal fraction (pos exp) + XCTAssertEqual( + try CBORDecoder.default.decode(Float.self, from: Data([0xC4, 0x82, 0x01, 0xC3, 0x43, 0x12, 0xD6, 0x86])), + Float(-12345670.0) + ) + } + + func testDecodeDouble() { + // From simple + XCTAssertEqual( + try CBORDecoder.default.decode(Double.self, from: Data([0xE3])), + Double(3) + ) + // From unsigned int + XCTAssertEqual( + try CBORDecoder.default.decode(Double.self, from: Data([0x03])), + Double(3) + ) + // From negative int + XCTAssertEqual( + try CBORDecoder.default.decode(Double.self, from: Data([0x22])), + Double(-3) + ) + // From positive bignum + XCTAssertEqual( + try CBORDecoder.default.decode(Double.self, from: Data([0xC2, 0x41, 0x03])), + Double(3) + ) + // From negative bignum + XCTAssertEqual( + try CBORDecoder.default.decode(Double.self, from: Data([0xC3, 0x41, 0x02])), + Double(-3) + ) + // From positve half + XCTAssertEqual( + try CBORDecoder.default.decode(Double.self, from: Data([0xf9, 0x3e, 0x00])), + Double(1.5) + ) + // From negative half + XCTAssertEqual( + try CBORDecoder.default.decode(Double.self, from: Data([0xf9, 0xbe, 0x00])), + Double(-1.5) + ) + // From positve float + XCTAssertEqual( + try CBORDecoder.default.decode(Double.self, from: Data([0xfa, 0x3f, 0xfe, 0x66, 0x66])), + Double(1.9874999523162842) + ) + // From negative float + XCTAssertEqual( + try CBORDecoder.default.decode(Double.self, from: Data([0xfa, 0xbf, 0xfe, 0x66, 0x66])), + Double(-1.9874999523162842) + ) + // From positve double + XCTAssertEqual( + try CBORDecoder.default.decode(Double.self, from: Data([0xfb, 0x3f, 0xf3, 0xbe, 0x76, 0xc8, 0xb4, 0x39, 0x58])), + Double(1.234) + ) + // From negative double + XCTAssertEqual( + try CBORDecoder.default.decode(Double.self, from: Data([0xfb, 0xbf, 0xf3, 0xbe, 0x76, 0xc8, 0xb4, 0x39, 0x58])), + Double(-1.234) + ) + // From positive decimal fraction (neg exp) + XCTAssertEqual( + try CBORDecoder.default.decode(Double.self, from: Data([0xC4, 0x82, 0x22, 0xC2, 0x43, 0x12, 0xD6, 0x87])), + Double(1234.567) + ) + // From positive decimal fraction (pos exp) + XCTAssertEqual( + try CBORDecoder.default.decode(Double.self, from: Data([0xC4, 0x82, 0x01, 0xC2, 0x43, 0x12, 0xD6, 0x87])), + Double(12345670.0) + ) + // From negative decimal fraction (neg exp) + XCTAssertEqual( + try CBORDecoder.default.decode(Double.self, from: Data([0xC4, 0x82, 0x22, 0xC3, 0x43, 0x12, 0xD6, 0x86])), + Double(-1234.567) + ) + // From negative decimal fraction (pos exp) + XCTAssertEqual( + try CBORDecoder.default.decode(Double.self, from: Data([0xC4, 0x82, 0x01, 0xC3, 0x43, 0x12, 0xD6, 0x86])), + Double(-12345670.0) + ) + } + + func testDecodeTaggedFloat() { + XCTAssertEqual(try CBORDecoder.default.decode(CBOR.Half.self, from: Data([0xC5, 0xF9, 0x3E, 0x00])), 1.5) + } + + func testDecodeFloatFromUnsupportedValue() { + XCTAssertThrowsError(try CBORDecoder.default.decode(Float.self, from: Data([0xF5]))) { error in + AssertDecodingTypeMismatch(error) + } + } + + func testDecodeDecimal() { + // From simple + XCTAssertEqual( + try CBORDecoder.default.decode(Decimal.self, from: Data([0xE3])), + Decimal(3) + ) + // From unsigned int + XCTAssertEqual( + try CBORDecoder.default.decode(Decimal.self, from: Data([0x03])), + Decimal(3) + ) + // From negative int + XCTAssertEqual( + try CBORDecoder.default.decode(Decimal.self, from: Data([0x22])), + Decimal(-3) + ) + // From positive bignum + XCTAssertEqual( + try CBORDecoder.default.decode(Decimal.self, from: Data([0xC2, 0x41, 0x03])), + Decimal(3) + ) + // From negative bignum + XCTAssertEqual( + try CBORDecoder.default.decode(Decimal.self, from: Data([0xC3, 0x41, 0x02])), + Decimal(-3) + ) + // From positve half + XCTAssertEqual( + try CBORDecoder.default.decode(Decimal.self, from: Data([0xf9, 0x3e, 0x00])), + Decimal(1.5) + ) + // From negative half + XCTAssertEqual( + try CBORDecoder.default.decode(Decimal.self, from: Data([0xf9, 0xbe, 0x00])), + Decimal(-1.5) + ) + // From positve float + XCTAssertEqual( + try CBORDecoder.default.decode(Decimal.self, from: Data([0xfa, 0x3f, 0xfe, 0x66, 0x66])), + Decimal(1.9874999523162842) + ) + // From negative float + XCTAssertEqual( + try CBORDecoder.default.decode(Decimal.self, from: Data([0xfa, 0xbf, 0xfe, 0x66, 0x66])), + Decimal(-1.9874999523162842) + ) + // From positve double + XCTAssertEqual( + try CBORDecoder.default.decode(Decimal.self, from: Data([0xfb, 0x3f, 0xf3, 0xbe, 0x76, 0xc8, 0xb4, 0x39, 0x58])), + Decimal(1.234) + ) + // From negative double + XCTAssertEqual( + try CBORDecoder.default.decode(Decimal.self, from: Data([0xfb, 0xbf, 0xf3, 0xbe, 0x76, 0xc8, 0xb4, 0x39, 0x58])), + Decimal(-1.234) + ) + // From positive decimal fraction (neg exp) + XCTAssertEqual( + try CBORDecoder.default.decode(Decimal.self, from: Data([0xC4, 0x82, 0x22, 0xC2, 0x43, 0x12, 0xD6, 0x87])), + Decimal(1234.567) + ) + // From positive decimal fraction (pos exp) + XCTAssertEqual( + try CBORDecoder.default.decode(Decimal.self, from: Data([0xC4, 0x82, 0x01, 0xC2, 0x43, 0x12, 0xD6, 0x87])), + Decimal(12345670.0) + ) + // From negative decimal fraction (neg exp) + XCTAssertEqual( + try CBORDecoder.default.decode(Decimal.self, from: Data([0xC4, 0x82, 0x22, 0xC3, 0x43, 0x12, 0xD6, 0x86])), + Decimal(-1234.567) + ) + // From negative decimal fraction (pos exp) + XCTAssertEqual( + try CBORDecoder.default.decode(Decimal.self, from: Data([0xC4, 0x82, 0x01, 0xC3, 0x43, 0x12, 0xD6, 0x86])), + Decimal(-12345670.0) + ) + } + + func testDecodeTaggedDecimal() { + XCTAssertEqual(try CBORDecoder.default.decode(Decimal.self, from: Data([0xc5, 0x01])), Decimal(1)) + } + + func testDecodeDecimalFromUnsupportedValue() { + XCTAssertThrowsError(try CBORDecoder.default.decode(Decimal.self, from: Data([0xF5]))) { error in + AssertDecodingTypeMismatch(error) + } + } + + func testDecodeDecimalNil() { + XCTAssertEqual(try CBORDecoder.default.decodeIfPresent(Decimal.self, from: Data([0xf6])), nil) + } + func testDecodeStrings() { - XCTAssertEqual(try CBORDecoder().decode(String.self, from: Data([0x60])), "") - XCTAssertEqual(try CBORDecoder().decode(String.self, from: Data([0x61, 0x61])), "a") - XCTAssertEqual(try CBORDecoder().decode(String.self, from: Data([0x64, 0x49, 0x45, 0x54, 0x46])), "IETF") - XCTAssertEqual(try CBORDecoder().decode(String.self, from: Data([0x62, 0x22, 0x5C])), "\"\\") - XCTAssertEqual(try CBORDecoder().decode(String.self, from: Data([0x62, 0xC3, 0xBC])), "\u{00FC}") + XCTAssertEqual(try CBORDecoder.default.decode(String.self, from: Data([0x60])), "") + XCTAssertEqual(try CBORDecoder.default.decode(String.self, from: Data([0x61, 0x61])), "a") + XCTAssertEqual(try CBORDecoder.default.decode(String.self, from: Data([0x64, 0x49, 0x45, 0x54, 0x46])), "IETF") + XCTAssertEqual(try CBORDecoder.default.decode(String.self, from: Data([0x62, 0x22, 0x5C])), "\"\\") + XCTAssertEqual(try CBORDecoder.default.decode(String.self, from: Data([0x62, 0xC3, 0xBC])), "\u{00FC}") + + } + func testDecodeStringFromUnsupportedValue() { + XCTAssertThrowsError(try CBORDecoder.default.decode(String.self, from: Data([0xF5]))) { error in + AssertDecodingTypeMismatch(error) + } } func testDecodeByteStrings() { XCTAssertEqual( - try CBORDecoder().decode(Data.self, from: Data([0x44, 0x01, 0x02, 0x03, 0x04])), + try CBORDecoder.default.decode(Data.self, from: Data([0x44, 0x01, 0x02, 0x03, 0x04])), Data([0x01, 0x02, 0x03, 0x04]) ) XCTAssertEqual( - try CBORDecoder().decode(Data.self, from: Data([0x5F, 0x42, 0x01, 0x02, 0x43, 0x03, 0x04, 0x05, 0xFF])), + try CBORDecoder.default.decode(Data.self, from: Data([0x5F, 0x42, 0x01, 0x02, 0x43, 0x03, 0x04, 0x05, 0xFF])), Data([0x01, 0x02, 0x03, 0x04, 0x05]) ) } func testDecodeArrays() { XCTAssertEqual( - try CBORDecoder().decode([String].self, from: Data([0x80])), + try CBORDecoder.default.decode([String].self, from: Data([0x80])), [] ) XCTAssertEqual( - try CBORDecoder().decode([Int].self, from: Data([0x83, 0x01, 0x02, 0x03])), + try CBORDecoder.default.decode([Int].self, from: Data([0x83, 0x01, 0x02, 0x03])), [1, 2, 3] ) XCTAssertEqual( - try CBORDecoder().decode( - [Int].self, - from: Data([ - 0x98, - 0x19, - 0x01, - 0x02, - 0x03, - 0x04, - 0x05, - 0x06, - 0x07, - 0x08, - 0x09, - 0x0A, - 0x0B, - 0x0C, - 0x0D, - 0x0E, - 0x0F, - 0x10, - 0x11, - 0x12, - 0x13, - 0x14, - 0x15, - 0x16, - 0x17, - 0x18, - 0x18, - 0x18, - 0x19, - ]) - ), + try CBORDecoder.default.decode([Int].self, from: Data([0x98, 0x19, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10, + 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x18, + 0x18, 0x19])), [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25] ) XCTAssertEqual( - try CBORDecoder().decode([[Int]].self, from: Data([0x83, 0x81, 0x01, 0x82, 0x02, 0x03, 0x82, 0x04, 0x05])), + try CBORDecoder.default.decode([[Int]].self, from: Data([0x83, 0x81, 0x01, 0x82, 0x02, 0x03, 0x82, 0x04, 0x05])), [[1], [2, 3], [4, 5]] ) // indefinite XCTAssertEqual( - try CBORDecoder().decode([Int].self, from: Data([0x9F, 0x04, 0x05, 0xFF])), + try CBORDecoder.default.decode([Int].self, from: Data([0x9F, 0x04, 0x05, 0xFF])), [4, 5] ) XCTAssertEqual( - try CBORDecoder() + try CBORDecoder.default .decode([[Int]].self, from: Data([0x9F, 0x81, 0x01, 0x82, 0x02, 0x03, 0x9F, 0x04, 0x05, 0xFF, 0xFF])), [[1], [2, 3], [4, 5]] ) @@ -165,77 +778,45 @@ class CBORDecoderTests: XCTestCase { func testDecodeMaps() { XCTAssertEqual( - try CBORDecoder().decode([String: String].self, from: Data([0xA0])), + try CBORDecoder.default.decode([String: String].self, from: Data([0xA0])), [:] ) XCTAssertEqual( - try CBORDecoder().decode( - [String: String].self, - from: Data([ - 0xA5, - 0x61, - 0x61, - 0x61, - 0x41, - 0x61, - 0x62, - 0x61, - 0x42, - 0x61, - 0x63, - 0x61, - 0x43, - 0x61, - 0x64, - 0x61, - 0x44, - 0x61, - 0x65, - 0x61, - 0x45, - ]) - ), + try CBORDecoder.default.decode([String: String].self, from: Data([0xA5, 0x61, 0x61, 0x61, 0x41, 0x61, 0x62, + 0x61, 0x42, 0x61, 0x63, 0x61, 0x43, 0x61, + 0x64, 0x61, 0x44, 0x61, 0x65, 0x61, 0x45])), ["a": "A", "b": "B", "c": "C", "d": "D", "e": "E"] ) - XCTAssertEqual(try CBORDecoder().decode([Int: Int].self, from: Data([0xA2, 0x01, 0x02, 0x03, 0x04])), [1: 2, 3: 4]) XCTAssertEqual( - try CBORDecoder().decode( - [String: String].self, - from: Data([0xBF, 0x63, 0x46, 0x75, 0x6E, 0x61, 0x62, 0x63, 0x41, 0x6D, 0x74, 0x61, 0x63, 0xFF]) - ), + try CBORDecoder.default.decode([Int: Int].self, from: Data([0xA2, 0x01, 0x02, 0x03, 0x04])), + [1: 2, 3: 4] + ) + XCTAssertEqual( + try CBORDecoder.default.decode([Int: Int].self, from: Data([0xA2, 0x20, 0x21, 0x22, 0x23])), + [-1: -2, -3: -4] + ) + XCTAssertEqual( + try CBORDecoder.default.decode([String: String].self, from: Data([0xBF, 0x63, 0x46, 0x75, 0x6E, 0x61, 0x62, + 0x63, 0x41, 0x6D, 0x74, 0x61, 0x63, 0xFF])), ["Fun": "b", "Amt": "c"] ) XCTAssertEqual( - try CBORDecoder().decode( - [String: [String: String]].self, - from: Data([ - 0xBF, - 0x63, - 0x46, - 0x75, - 0x6E, - 0xA1, - 0x61, - 0x62, - 0x61, - 0x42, - 0x63, - 0x41, - 0x6D, - 0x74, - 0xBF, - 0x61, - 0x63, - 0x61, - 0x43, - 0xFF, - 0xFF, - ]) - ), + try CBORDecoder.default.decode([String: [String: String]].self, from: Data([0xBF, 0x63, 0x46, 0x75, 0x6E, 0xA1, + 0x61, 0x62, 0x61, 0x42, 0x63, 0x41, + 0x6D, 0x74, 0xBF, 0x61, 0x63, 0x61, + 0x43, 0xFF, 0xFF])), ["Fun": ["b": "B"], "Amt": ["c": "C"]] ) } + func testDecodeInvalidMapKeysThrowsError() throws { + XCTAssertThrowsError( + try CBORDecoder.default.decode([Int: Int].self, from: Data([0xA2, 0xf6, 0x02, 0x03, 0x04])) + ) { error in + AssertDecodingDataCorrupted(error) + } + } + func testDecodingDoesntTranslateMapKeys() throws { let decoder = CBORDecoder() decoder.keyDecodingStrategy = .convertFromSnakeCase @@ -243,16 +824,603 @@ class CBORDecoderTests: XCTestCase { XCTAssertEqual(dict.keys.first, "this_is_an_example_key") } - func testDecodeDates() { - let expectedDateOne = Date(timeIntervalSince1970: 1_363_896_240) + func testDecodeNumericDates() { + XCTAssertEqual( + try CBORDecoder.default.decode(Date.self, from: Data([0xC1, 0x1A, 0x51, 0x4B, 0x67, 0xB0])), + Date(timeIntervalSince1970: 1_363_896_240) + ) + XCTAssertEqual( + try CBORDecoder.default.decode(Date.self, from: Data([0xC1, 0xFB, 0x41, 0xD4, 0x52, + 0xD9, 0xEC, 0x20, 0x00, 0x00])), + Date(timeIntervalSince1970: 1_363_896_240.5) + ) + } + + func testDecodeISO8601Dates() { + XCTAssertEqual( + try CBORDecoder.default.decode(Date.self, from: Data([0xC0, 0x78, 0x18, 0x32, 0x30, 0x32, 0x30, 0x2D, 0x31, + 0x32, 0x2D, 0x31, 0x31, 0x54, 0x31, 0x32, 0x3A, 0x33, + 0x34, 0x3A, 0x35, 0x36, 0x2E, 0x37, 0x38, 0x39, 0x5A])), + Date(timeIntervalSince1970: 1607690096.789) + ) + XCTAssertThrowsError( + try CBORDecoder.default.decode(Date.self, from: Data([0xC0, 0x78, 0x0f, 0x32, 0x30, 0x32, 0x30, 0x2D, 0x31, + 0x32, 0x2D, 0x31, 0x31, 0x54, 0x31, 0x32, 0x3A, 0x33])) + ) { error in + AssertDecodingDataCorrupted(error) + } + } + func testDecodeExtraTaggedDate() { + XCTAssertEqual( + try CBORDecoder.default.decode(Date.self, from: Data([0xC5, 0xC1, 0x1A, 0x51, 0x4B, 0x67, 0xB0])), + Date(timeIntervalSince1970: 1_363_896_240) + ) + } + + func testDecodeUntaggedDateAsUnits() { + let decoder = CBORDecoder() + decoder.untaggedDateDecodingStrategy = .unitsSince1970 + XCTAssertEqual( + try decoder.decode(Date.self, from: Data([0xC1, 0x1A, 0x51, 0x4B, 0x67, 0xB0])), + Date(timeIntervalSince1970: 1_363_896_240) + ) + XCTAssertEqual( + try decoder.decode(Date.self, from: Data([0x1B, 0x00, 0x00, 0x01, 0x3D, 0x8E, 0x8D, 0x07, 0x80])), + Date(timeIntervalSince1970: 1_363_896_240) + ) + XCTAssertEqual( + try decoder.decode(Date.self, from: Data([0xFB, 0x41, 0xD4, 0x52, 0xD9, 0xEC, 0x00, 0x00, 0x00])), + Date(timeIntervalSince1970: 1_363_896_240) + ) + } + + func testDecodeUntaggedDateAsMilliseconds() { + let decoder = CBORDecoder() + decoder.untaggedDateDecodingStrategy = .millisecondsSince1970 + XCTAssertEqual( + try decoder.decode(Date.self, from: Data([0xC1, 0x1A, 0x51, 0x4B, 0x67, 0xB0])), + Date(timeIntervalSince1970: 1_363_896_240) + ) + XCTAssertEqual( + try decoder.decode(Date.self, from: Data([0x1B, 0x00, 0x00, 0x01, 0x3D, 0x8E, 0x8D, 0x07, 0x80])), + Date(timeIntervalSince1970: 1_363_896_240) + ) + XCTAssertEqual( + try decoder.decode(Date.self, from: Data([0xFB, 0x42, 0x73, 0xD8, 0xE8, 0xD0, 0x78, 0x00, 0x00])), + Date(timeIntervalSince1970: 1_363_896_240) + ) + } + + func testDecodeUntaggedDateAsSeconds() { + let decoder = CBORDecoder() + decoder.untaggedDateDecodingStrategy = .secondsSince1970 + XCTAssertEqual( + try decoder.decode(Date.self, from: Data([0xC1, 0x1A, 0x51, 0x4B, 0x67, 0xB0])), + Date(timeIntervalSince1970: 1_363_896_240) + ) + XCTAssertEqual( + try decoder.decode(Date.self, from: Data([0x1A, 0x51, 0x4B, 0x67, 0xB0])), + Date(timeIntervalSince1970: 1_363_896_240) + ) + XCTAssertEqual( + try decoder.decode(Date.self, from: Data([0xFB, 0x41, 0xD4, 0x52, 0xD9, 0xEC, 0x00, 0x00, 0x00])), + Date(timeIntervalSince1970: 1_363_896_240) + ) + } + + func testDecodeDateFromUnsupportedValue() { + XCTAssertThrowsError(try CBORDecoder.default.decode(Date.self, from: Data([0xF5]))) { error in + AssertDecodingTypeMismatch(error) + } + } + + func testDecodeData() { + XCTAssertEqual( + try CBORDecoder.default.decode(Data.self, from: Data([0x45, 0x1, 0x2, 0x3, 0x4, 0x5])), + Data([1, 2, 3, 4, 5]) + ) + } + + func testDecodeBase64Data() { + XCTAssertEqual( + try CBORDecoder.default.decode(Data.self, from: Data([0xD8, 0x22, 0x68, 0x41, 0x51, 0x49, + 0x44, 0x42, 0x41, 0x55, 0x2B])), + Data([1, 2, 3, 4, 5, 62]) + ) + XCTAssertThrowsError( + try CBORDecoder.default.decode(Data.self, from: Data([0xD8, 0x22, 0x68, 0x41, 0x51, 0x49, + 0x44, 0x42, 0x41, 0x55, 0x60])) + ) { error in + AssertDecodingDataCorrupted(error) + } + } + + func testDecodeBase64UrlData() { + XCTAssertEqual( + try CBORDecoder.default.decode(Data.self, from: Data([0xD8, 0x21, 0x68, 0x41, 0x51, 0x49, + 0x44, 0x42, 0x41, 0x55, 0x2D])), + Data([1, 2, 3, 4, 5, 62]) + ) + XCTAssertThrowsError( + try CBORDecoder.default.decode(Data.self, from: Data([0xD8, 0x21, 0x68, 0x41, 0x51, 0x49, + 0x44, 0x42, 0x41, 0x55, 0x60])) + ) { error in + AssertDecodingDataCorrupted(error) + } + } + + func testDecodeDataFromUnsupportedValue() { + XCTAssertThrowsError(try CBORDecoder.default.decode(Data.self, from: Data([0xF5]))) { error in + AssertDecodingTypeMismatch(error) + } + } + + func testDecodeTaggedData() { + XCTAssertEqual( + try CBORDecoder.default.decode(Data.self, from: Data([0xc5, 0x45, 0x1, 0x2, 0x3, 0x4, 0x5])), + Data([1, 2, 3, 4, 5]) + ) + } + + func testDecodeURL() { + XCTAssertEqual( + try CBORDecoder.default.decode(URL.self, from: Data([0xD8, 0x20, 0x78, 0x1E, 0x68, 0x74, 0x74, 0x70, 0x73, 0x3A, + 0x2F, 0x2F, 0x65, 0x78, 0x61, 0x6D, 0x70, 0x6C, 0x65, 0x2E, + 0x63, 0x6F, 0x6D, 0x2F, 0x73, 0x6F, 0x6D, 0x65, 0x2F, 0x74, + 0x68, 0x69, 0x6E, 0x67])), + URL(string: "https://example.com/some/thing") + ) + XCTAssertThrowsError( + try CBORDecoder.default.decode(URL.self, from: Data([0xD8, 0x20, 0x67, 0x62, 0x61, + 0x64, 0x20, 0x75, 0x72, 0x6C])) + ) { error in + AssertDecodingDataCorrupted(error) + } + } + + func testDecodeTaggedURL() { + XCTAssertEqual( + try CBORDecoder.default.decode(URL.self, from: Data([0xc5, 0xD8, 0x20, 0x78, 0x1E, 0x68, 0x74, 0x74, 0x70, 0x73, + 0x3A, 0x2F, 0x2F, 0x65, 0x78, 0x61, 0x6D, 0x70, 0x6C, 0x65, + 0x2E, 0x63, 0x6F, 0x6D, 0x2F, 0x73, 0x6F, 0x6D, 0x65, 0x2F, + 0x74, 0x68, 0x69, 0x6E, 0x67])), + URL(string: "https://example.com/some/thing") + ) + } + + func testDecodeURLFromUnsupportedValue() { + XCTAssertThrowsError(try CBORDecoder.default.decode(URL.self, from: Data([0xF5]))) { error in + AssertDecodingTypeMismatch(error) + } + } + + func testDecodeUUID() { + XCTAssertEqual( + try CBORDecoder.default.decode(UUID.self, from: Data([0xD8, 0x25, 0x50, 0x97, 0x5A, 0xEB, 0xED, 0x80, 0x60, 0x4E, + 0x4D, 0x8A, 0x11, 0x28, 0xC5, 0xE8, 0xDD, 0xD2, 0x4C])), + UUID(uuidString: "975AEBED-8060-4E4D-8A11-28C5E8DDD24C") + ) + XCTAssertThrowsError( + try CBORDecoder.default.decode(UUID.self, from: Data([0xD8, 0x25, 0x4c, 0x97, 0x5A, 0xEB, 0xED, 0x80, 0x60, 0x4E, + 0x4D, 0x8A, 0x11, 0x28, 0xC5, 0xE8, 0xDD])) + ) { error in + AssertDecodingTypeMismatch(error) + } + XCTAssertEqual( + try CBORDecoder.default.decode(UUID.self, from: Data([0xD8, 0x25, 0x78, 0x24, 0x39, 0x37, 0x35, 0x41, 0x45, + 0x42, 0x45, 0x44, 0x2D, 0x38, 0x30, 0x36, 0x30, 0x2D, + 0x34, 0x45, 0x34, 0x44, 0x2D, 0x38, 0x41, 0x31, 0x31, + 0x2D, 0x32, 0x38, 0x43, 0x35, 0x45, 0x38, 0x44, 0x44, + 0x44, 0x32, 0x34, 0x43])), + UUID(uuidString: "975AEBED-8060-4E4D-8A11-28C5E8DDD24C") + ) + XCTAssertThrowsError( + try CBORDecoder.default.decode(UUID.self, from: Data([0xD8, 0x25, 0x78, 0x20, 0x39, 0x37, 0x35, 0x41, 0x45, 0x42, + 0x45, 0x44, 0x2D, 0x38, 0x30, 0x36, 0x30, 0x2D, 0x34, 0x45, + 0x34, 0x44, 0x2D, 0x38, 0x41, 0x31, 0x31, 0x2D, 0x32, 0x38, + 0x43, 0x35, 0x45, 0x38, 0x44, 0x44])) + ) { error in + AssertDecodingTypeMismatch(error) + } + } + func testDecodeTaggedUUID() { + XCTAssertEqual( + try CBORDecoder.default.decode(UUID.self, from: Data([0xC5, 0xD8, 0x25, 0x50, 0x97, 0x5A, 0xEB, 0xED, 0x80, + 0x60, 0x4E, 0x4D, 0x8A, 0x11, 0x28, 0xC5, 0xE8, 0xDD, + 0xD2, 0x4C])), + UUID(uuidString: "975AEBED-8060-4E4D-8A11-28C5E8DDD24C") + ) + } + + func testDecodeUUIDFromUnsupportedValue() { + XCTAssertThrowsError(try CBORDecoder.default.decode(UUID.self, from: Data([0xF5]))) { error in + AssertDecodingTypeMismatch(error) + } + } + + func testDecodePositiveDecimalNegativeExponent() { + XCTAssertEqual( + try CBORDecoder.default.decode(Decimal.self, from: Data([0xC4, 0x82, 0x22, 0xC2, 0x44, 0x00, 0x12, 0xD6, 0x87])), + Decimal(sign: .plus, exponent: -3, significand: 1234567) + ) + } + + func testDecodePositiveDecimalPositiveExponent() { + XCTAssertEqual( + try CBORDecoder.default.decode(Decimal.self, from: Data([0xC4, 0x82, 0x01, 0xC2, 0x44, 0x00, 0x12, 0xD6, 0x87])), + Decimal(sign: .plus, exponent: 1, significand: 1234567) + ) + } + + func testDecodeNegativeDecimalNegativeExponent() throws { + XCTAssertEqual( + try CBORDecoder.default.decode(Decimal.self, from: Data([0xC4, 0x82, 0x22, 0xC3, 0x44, 0x00, 0x12, 0xD6, 0x86])), + Decimal(sign: .minus, exponent: -3, significand: 1234567) + ) + } + + func testDecodeNegativeDecimalPositiveExponent() { + XCTAssertEqual( + try CBORDecoder.default.decode(Decimal.self, from: Data([0xC4, 0x82, 0x01, 0xC3, 0x44, 0x00, 0x12, 0xD6, 0x86])), + Decimal(sign: .minus, exponent: 1, significand: 1234567) + ) + } + + func testDecodePositiveBigInt() { + XCTAssertEqual( + try CBORDecoder.default.decode(BigInt.self, from: Data([0xC2, 0x44, 0x00, 0x12, 0xD6, 0x87])), + BigInt(1234567) + ) + } + + func testDecodeNegativeBigInt() { XCTAssertEqual( - try CBORDecoder().decode(Date.self, from: Data([0xC1, 0x1A, 0x51, 0x4B, 0x67, 0xB0])), - expectedDateOne + try CBORDecoder.default.decode(BigInt.self, from: Data([0xC3, 0x44, 0x00, 0x12, 0xD6, 0x86])), + BigInt(-1234567) ) - let expectedDateTwo = Date(timeIntervalSince1970: 1_363_896_240.5) + } + + func testDecodePositiveBigUInt() { XCTAssertEqual( - try CBORDecoder().decode(Date.self, from: Data([0xC1, 0xFB, 0x41, 0xD4, 0x52, 0xD9, 0xEC, 0x20, 0x00, 0x00])), - expectedDateTwo + try CBORDecoder.default.decode(BigUInt.self, from: Data([0xC2, 0x44, 0x00, 0x12, 0xD6, 0x87])), + BigUInt(1234567) ) } + + func testDecodeBoolFromNullItem() throws { + + struct TestValue: Codable { + init(from decoder: Decoder) throws { + var container = try decoder.unkeyedContainer() + _ = try container.decode(Bool.self) + } + func encode(to encoder: Encoder) throws { + } + } + + XCTAssertThrowsError(try CBOR.Decoder.default.decodeTree(TestValue.self, from: .array([nil]))) { error in + AssertDecodingValueNotFound(error) + } + } + + func testDecodeStringFromNullItem() throws { + + struct TestValue: Codable { + init(from decoder: Decoder) throws { + var container = try decoder.unkeyedContainer() + _ = try container.decode(String.self) + } + func encode(to encoder: Encoder) throws { + } + } + + XCTAssertThrowsError(try CBOR.Decoder.default.decodeTree(TestValue.self, from: .array([nil]))) { error in + AssertDecodingValueNotFound(error) + } + } + + func testDecodeIntFromNullItem() throws { + + struct TestValue: Codable { + init(from decoder: Decoder) throws { + var container = try decoder.unkeyedContainer() + _ = try container.decode(Int.self) + } + func encode(to encoder: Encoder) throws { + } + } + + XCTAssertThrowsError(try CBOR.Decoder.default.decodeTree(TestValue.self, from: .array([nil]))) { error in + AssertDecodingValueNotFound(error) + } + } + + func testDecodeInt8FromNullItem() throws { + + struct TestValue: Codable { + init(from decoder: Decoder) throws { + var container = try decoder.unkeyedContainer() + _ = try container.decode(Int8.self) + } + func encode(to encoder: Encoder) throws { + } + } + + XCTAssertThrowsError(try CBOR.Decoder.default.decodeTree(TestValue.self, from: .array([nil]))) { error in + AssertDecodingValueNotFound(error) + } + } + + func testDecodeInt16FromNullItem() throws { + + struct TestValue: Codable { + init(from decoder: Decoder) throws { + var container = try decoder.unkeyedContainer() + _ = try container.decode(Int16.self) + } + func encode(to encoder: Encoder) throws { + } + } + + XCTAssertThrowsError(try CBOR.Decoder.default.decodeTree(TestValue.self, from: .array([nil]))) { error in + AssertDecodingValueNotFound(error) + } + } + + func testDecodeInt32FromNullItem() throws { + + struct TestValue: Codable { + init(from decoder: Decoder) throws { + var container = try decoder.unkeyedContainer() + _ = try container.decode(Int32.self) + } + func encode(to encoder: Encoder) throws { + } + } + + XCTAssertThrowsError(try CBOR.Decoder.default.decodeTree(TestValue.self, from: .array([nil]))) { error in + AssertDecodingValueNotFound(error) + } + } + + func testDecodeInt64FromNullItem() throws { + + struct TestValue: Codable { + init(from decoder: Decoder) throws { + var container = try decoder.unkeyedContainer() + _ = try container.decode(Int64.self) + } + func encode(to encoder: Encoder) throws { + } + } + + XCTAssertThrowsError(try CBOR.Decoder.default.decodeTree(TestValue.self, from: .array([nil]))) { error in + AssertDecodingValueNotFound(error) + } + } + + func testDecodeUIntFromNullItem() throws { + + struct TestValue: Codable { + init(from decoder: Decoder) throws { + var container = try decoder.unkeyedContainer() + _ = try container.decode(UInt.self) + } + func encode(to encoder: Encoder) throws { + } + } + + XCTAssertThrowsError(try CBOR.Decoder.default.decodeTree(TestValue.self, from: .array([nil]))) { error in + AssertDecodingValueNotFound(error) + } + } + + func testDecodeUInt8FromNullItem() throws { + + struct TestValue: Codable { + init(from decoder: Decoder) throws { + var container = try decoder.unkeyedContainer() + _ = try container.decode(UInt8.self) + } + func encode(to encoder: Encoder) throws { + } + } + + XCTAssertThrowsError(try CBOR.Decoder.default.decodeTree(TestValue.self, from: .array([nil]))) { error in + AssertDecodingValueNotFound(error) + } + } + + func testDecodeUInt16FromNullItem() throws { + + struct TestValue: Codable { + init(from decoder: Decoder) throws { + var container = try decoder.unkeyedContainer() + _ = try container.decode(UInt16.self) + } + func encode(to encoder: Encoder) throws { + } + } + + XCTAssertThrowsError(try CBOR.Decoder.default.decodeTree(TestValue.self, from: .array([nil]))) { error in + AssertDecodingValueNotFound(error) + } + } + + func testDecodeUInt32FromNullItem() throws { + + struct TestValue: Codable { + init(from decoder: Decoder) throws { + var container = try decoder.unkeyedContainer() + _ = try container.decode(UInt32.self) + } + func encode(to encoder: Encoder) throws { + } + } + + XCTAssertThrowsError(try CBOR.Decoder.default.decodeTree(TestValue.self, from: .array([nil]))) { error in + AssertDecodingValueNotFound(error) + } + } + + func testDecodeUInt64FromNullItem() throws { + + struct TestValue: Codable { + init(from decoder: Decoder) throws { + var container = try decoder.unkeyedContainer() + _ = try container.decode(UInt64.self) + } + func encode(to encoder: Encoder) throws { + } + } + + XCTAssertThrowsError(try CBOR.Decoder.default.decodeTree(TestValue.self, from: .array([nil]))) { error in + AssertDecodingValueNotFound(error) + } + } + + func testDecodeBigIntFromNullItem() throws { + + struct TestValue: Codable { + init(from decoder: Decoder) throws { + var container = try decoder.unkeyedContainer() + _ = try container.decode(BigInt.self) + } + func encode(to encoder: Encoder) throws { + } + } + + XCTAssertThrowsError(try CBOR.Decoder.default.decodeTree(TestValue.self, from: .array([nil]))) { error in + AssertDecodingValueNotFound(error) + } + } + + func testDecodeBigUIntFromNullItem() throws { + + struct TestValue: Codable { + init(from decoder: Decoder) throws { + var container = try decoder.unkeyedContainer() + _ = try container.decode(BigUInt.self) + } + func encode(to encoder: Encoder) throws { + } + } + + XCTAssertThrowsError(try CBOR.Decoder.default.decodeTree(TestValue.self, from: .array([nil]))) { error in + AssertDecodingValueNotFound(error) + } + } + + func testDecodeFloatFromNullItem() throws { + + struct TestValue: Codable { + init(from decoder: Decoder) throws { + var container = try decoder.unkeyedContainer() + _ = try container.decode(Float.self) + } + func encode(to encoder: Encoder) throws { + } + } + + XCTAssertThrowsError(try CBOR.Decoder.default.decodeTree(TestValue.self, from: .array([nil]))) { error in + AssertDecodingValueNotFound(error) + } + } + + func testDecodeDoubleFromNullItem() throws { + + struct TestValue: Codable { + init(from decoder: Decoder) throws { + var container = try decoder.unkeyedContainer() + _ = try container.decode(Double.self) + } + func encode(to encoder: Encoder) throws { + } + } + + XCTAssertThrowsError(try CBOR.Decoder.default.decodeTree(TestValue.self, from: .array([nil]))) { error in + AssertDecodingValueNotFound(error) + } + } + + func testDecodeDecimalFromNullItem() throws { + + struct TestValue: Codable { + init(from decoder: Decoder) throws { + var container = try decoder.unkeyedContainer() + _ = try container.decode(Decimal.self) + } + func encode(to encoder: Encoder) throws { + } + } + + XCTAssertThrowsError(try CBOR.Decoder.default.decodeTree(TestValue.self, from: .array([nil]))) { error in + AssertDecodingValueNotFound(error) + } + } + + func testDecodeUUIDFromNullItem() throws { + + struct TestValue: Codable { + init(from decoder: Decoder) throws { + var container = try decoder.unkeyedContainer() + _ = try container.decode(UUID.self) + } + func encode(to encoder: Encoder) throws { + } + } + + XCTAssertThrowsError(try CBOR.Decoder.default.decodeTree(TestValue.self, from: .array([nil]))) { error in + AssertDecodingValueNotFound(error) + } + } + + func testDecodeURLFromNullItem() throws { + + struct TestValue: Codable { + init(from decoder: Decoder) throws { + var container = try decoder.unkeyedContainer() + _ = try container.decode(URL.self) + } + func encode(to encoder: Encoder) throws { + } + } + + XCTAssertThrowsError(try CBOR.Decoder.default.decodeTree(TestValue.self, from: .array([nil]))) { error in + AssertDecodingValueNotFound(error) + } + } + + func testDecodeDateFromNullItem() throws { + + struct TestValue: Codable { + init(from decoder: Decoder) throws { + var container = try decoder.unkeyedContainer() + _ = try container.decode(Date.self) + } + func encode(to encoder: Encoder) throws { + } + } + + XCTAssertThrowsError(try CBOR.Decoder.default.decodeTree(TestValue.self, from: .array([nil]))) { error in + AssertDecodingValueNotFound(error) + } + } + + func testDecodeDataFromNullItem() throws { + + struct TestValue: Codable { + init(from decoder: Decoder) throws { + var container = try decoder.unkeyedContainer() + _ = try container.decode(Data.self) + } + func encode(to encoder: Encoder) throws { + } + } + + XCTAssertThrowsError(try CBOR.Decoder.default.decodeTree(TestValue.self, from: .array([nil]))) { error in + AssertDecodingValueNotFound(error) + } + } + } diff --git a/Tests/CBOREncoderTests.swift b/Tests/CBOREncoderTests.swift index ac1ca5c44..915a201c5 100644 --- a/Tests/CBOREncoderTests.swift +++ b/Tests/CBOREncoderTests.swift @@ -8,23 +8,13 @@ // Distributed under the MIT License, See LICENSE for details. // +import BigInt @testable import PotentCBOR @testable import PotentCodables import XCTest class CBOREncoderTests: XCTestCase { - static var allTests = [ - ("testEncodeNull", testEncodeNull), - ("testEncodeBools", testEncodeBools), - ("testEncodeInts", testEncodeInts), - ("testEncodeNegativeInts", testEncodeNegativeInts), - ("testEncodeStrings", testEncodeStrings), - ("testEncodeByteStrings", testEncodeByteStrings), - ("testEncodeArrays", testEncodeArrays), - ("testEncodeMaps", testEncodeMaps), - ("testEncodeSimpleStructs", testEncodeSimpleStructs), - ] func encode(_ value: T, dates: CBOREncoder.DateEncodingStrategy = .iso8601) throws -> [UInt8] { return Array(try CBOREncoder().encode(value)) @@ -61,6 +51,26 @@ class CBOREncoderTests: XCTestCase { try encode(UInt64(18_446_744_073_709_551_615)), [0x1B, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF] ) + + // Specific Types + XCTAssertEqual(try encode(Int8.max), [0x18, 0x7f]) + XCTAssertEqual(try encode(Int16.max), [0x19, 0x7f, 0xff]) + XCTAssertEqual(try encode(Int32.max), [0x1A, 0x7f, 0xff, 0xff, 0xff]) + XCTAssertEqual(try encode(Int64.max), [0x1B, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff]) + XCTAssertEqual( + try encode(Int.max), + MemoryLayout.size == 4 + ? [0x1A, 0x7f, 0xff, 0xff, 0xff] + : [0x1B, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff]) + XCTAssertEqual(try encode(UInt8.max), [0x18, 0xff]) + XCTAssertEqual(try encode(UInt16.max), [0x19, 0xff, 0xff]) + XCTAssertEqual(try encode(UInt32.max), [0x1A, 0xff, 0xff, 0xff, 0xff]) + XCTAssertEqual(try encode(UInt64.max), [0x1B, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff]) + XCTAssertEqual( + try encode(UInt.max), + MemoryLayout.size == 4 + ? [0x1A, 0xff, 0xff, 0xff, 0xff] + : [0x1B, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff]) } func testEncodeNegativeInts() { @@ -77,6 +87,34 @@ class CBOREncoderTests: XCTestCase { try encode(Int64(-9_223_372_036_854_775_808)), [0x3B, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF] ) + + XCTAssertEqual(try encode(Int8.min), [0x38, 0x7f]) + XCTAssertEqual(try encode(Int16.min), [0x39, 0x7f, 0xff]) + XCTAssertEqual(try encode(Int32.min), [0x3A, 0x7f, 0xff, 0xff, 0xff]) + XCTAssertEqual(try encode(Int64.min), [0x3B, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff]) + XCTAssertEqual( + try encode(Int.min), + MemoryLayout.size == 4 + ? [0x3A, 0x7f, 0xff, 0xff, 0xff] + : [0x3B, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff]) + } + + func testEncodeHalfs() { + + XCTAssertEqual(try encode(CBOR.Half(1.5)), [0xf9, 0x3e, 0x00]) + XCTAssertEqual(try encode(CBOR.Half(-1.5)), [0xf9, 0xbe, 0x00]) + } + + func testEncodeFloats() { + + XCTAssertEqual(try encode(Float(1.9874999523162842)), [0xfa, 0x3f, 0xfe, 0x66, 0x66]) + XCTAssertEqual(try encode(Float(-1.9874999523162842)), [0xfa, 0xbf, 0xfe, 0x66, 0x66]) + } + + func testEncodeDoubles() { + + XCTAssertEqual(try encode(Double(1.234)), [0xfb, 0x3f, 0xf3, 0xbe, 0x76, 0xc8, 0xb4, 0x39, 0x58]) + XCTAssertEqual(try encode(Double(-1.234)), [0xfb, 0xbf, 0xf3, 0xbe, 0x76, 0xc8, 0xb4, 0x39, 0x58]) } func testEncodeStrings() { @@ -102,37 +140,8 @@ class CBOREncoderTests: XCTestCase { ) XCTAssertEqual( try encode([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25]), - [ - 0x98, - 0x19, - 0x01, - 0x02, - 0x03, - 0x04, - 0x05, - 0x06, - 0x07, - 0x08, - 0x09, - 0x0A, - 0x0B, - 0x0C, - 0x0D, - 0x0E, - 0x0F, - 0x10, - 0x11, - 0x12, - 0x13, - 0x14, - 0x15, - 0x16, - 0x17, - 0x18, - 0x18, - 0x18, - 0x19, - ] + [0x98, 0x19, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, + 0x0F, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x18, 0x18, 0x19] ) XCTAssertEqual( try encode([[1], [2, 3], [4, 5]]), @@ -190,67 +199,13 @@ class CBOREncoderTests: XCTestCase { encoder.dateEncodingStrategy = .iso8601 XCTAssertEqual( try Array(encoder.encode(dateOne)), - [ - 0xC0, - 0x78, - 0x18, - 0x32, - 0x30, - 0x31, - 0x33, - 0x2D, - 0x30, - 0x33, - 0x2D, - 0x32, - 0x31, - 0x54, - 0x32, - 0x30, - 0x3A, - 0x30, - 0x34, - 0x3A, - 0x30, - 0x30, - 0x2E, - 0x30, - 0x30, - 0x30, - 0x5A, - ] + [0xC0, 0x78, 0x18, 0x32, 0x30, 0x31, 0x33, 0x2D, 0x30, 0x33, 0x2D, 0x32, 0x31, 0x54, 0x32, 0x30, + 0x3A, 0x30, 0x34, 0x3A, 0x30, 0x30, 0x2E, 0x30, 0x30, 0x30, 0x5A] ) XCTAssertEqual( try Array(encoder.encode(dateTwo)), - [ - 0xC0, - 0x78, - 0x18, - 0x32, - 0x30, - 0x31, - 0x33, - 0x2D, - 0x30, - 0x33, - 0x2D, - 0x32, - 0x31, - 0x54, - 0x32, - 0x30, - 0x3A, - 0x30, - 0x34, - 0x3A, - 0x30, - 0x30, - 0x2E, - 0x35, - 0x30, - 0x30, - 0x5A, - ] + [0xC0, 0x78, 0x18, 0x32, 0x30, 0x31, 0x33, 0x2D, 0x30, 0x33, 0x2D, 0x32, 0x31, 0x54, 0x32, 0x30, + 0x3A, 0x30, 0x34, 0x3A, 0x30, 0x30, 0x2E, 0x35, 0x30, 0x30, 0x5A] ) } @@ -267,6 +222,58 @@ class CBOREncoderTests: XCTestCase { || encoded == [0xA2, 0x64, 0x6E, 0x61, 0x6D, 0x65, 0x63, 0x48, 0x61, 0x6D, 0x63, 0x61, 0x67, 0x65, 0x18, 0x1B] ) } + + func testEncodeData() { + XCTAssertEqual(try encode(Data([1, 2, 3, 4, 5])), [0x45, 0x1, 0x2, 0x3, 0x4, 0x5]) + } + + func testEncodeURL() { + XCTAssertEqual(try encode(URL(string: "https://example.com/some/thing")), + [0xD8, 0x20, 0x78, 0x1E, 0x68, 0x74, 0x74, 0x70, 0x73, 0x3A, 0x2F, 0x2F, + 0x65, 0x78, 0x61, 0x6D, 0x70, 0x6C, 0x65, 0x2E, 0x63, 0x6F, 0x6D, 0x2F, + 0x73, 0x6F, 0x6D, 0x65, 0x2F, 0x74, 0x68, 0x69, 0x6E, 0x67]) + } + + func testEncodeUUID() { + XCTAssertEqual(try encode(UUID(uuidString: "975AEBED-8060-4E4D-8A11-28C5E8DDD24C")), + [0xD8, 0x25, 0x50, 0x97, 0x5A, 0xEB, 0xED, 0x80, 0x60, 0x4E, + 0x4D, 0x8A, 0x11, 0x28, 0xC5, 0xE8, 0xDD, 0xD2, 0x4C]) + } + + func testEncodePositiveDecimalNegativeExponent() { + XCTAssertEqual(try encode(Decimal(sign: .plus, exponent: -3, significand: 1234567)), + [0xC4, 0x82, 0x22, 0xC2, 0x43, 0x12, 0xD6, 0x87]) + } + + func testEncodePositiveDecimalPositiveExponent() { + XCTAssertEqual(try encode(Decimal(sign: .plus, exponent: 1, significand: 1234567)), + [0xC4, 0x82, 0x01, 0xC2, 0x43, 0x12, 0xD6, 0x87]) + } + + func testEncodeNegativeDecimalNegativeExponent() throws { + XCTAssertEqual(try encode(Decimal(sign: .minus, exponent: -3, significand: 1234567)), + [0xC4, 0x82, 0x22, 0xC3, 0x43, 0x12, 0xD6, 0x86]) + } + + func testEncodeNegativeDecimalPositiveExponent() { + XCTAssertEqual(try encode(Decimal(sign: .minus, exponent: 1, significand: 1234567)), + [0xC4, 0x82, 0x01, 0xC3, 0x43, 0x12, 0xD6, 0x86]) + } + + func testEncodePositiveBigInt() { + XCTAssertEqual(try encode(BigInt(1234567)), + [0xC2, 0x43, 0x12, 0xD6, 0x87]) + } + + func testEncodeNegativeBigInt() { + XCTAssertEqual(try encode(BigInt(-1234567)), + [0xC3, 0x43, 0x12, 0xD6, 0x86]) + } + + func testEncodePositiveBigUInt() { + XCTAssertEqual(try encode(BigUInt(1234567)), + [0xC2, 0x43, 0x12, 0xD6, 0x87]) + } } extension Array { diff --git a/Tests/CBORReaderTests.swift b/Tests/CBORReaderTests.swift index b31e6c87d..d6c6e4561 100644 --- a/Tests/CBORReaderTests.swift +++ b/Tests/CBORReaderTests.swift @@ -14,18 +14,6 @@ import XCTest class CBORReaderTests: XCTestCase { - static var allTests = [ - ("testDecodeNumbers", testDecodeNumbers), - ("testDecodeByteStrings", testDecodeByteStrings), - ("testDecodeUtf8Strings", testDecodeUtf8Strings), - ("testDecodeArrays", testDecodeArrays), - ("testDecodeMaps", testDecodeMaps), - ("testDecodeTagged", testDecodeTagged), - ("testDecodeSimple", testDecodeSimple), - ("testDecodeFloats", testDecodeFloats), - ("testDecodePerformance", testDecodePerformance), - ("testDecodeMapFromIssue29", testDecodeMapFromIssue29), - ] func decode(_ bytes: UInt8...) throws -> CBOR { return try decode(Data(bytes)) @@ -324,7 +312,7 @@ class CBORReaderTests: XCTestCase { // XCTAssertEqual(try decode(0xc1, 0xfb, 0x41, 0xd4, 0x52, 0xd9, 0xec, 0x20, 0x00, 0x00), .date(dateTwo)) // } - func testDecodePerformance() { + func disabled_testDecodePerformance() { var data = Data([0x9F]) for i in 0 ..< 255 { data.append(contentsOf: [0xBF, 0x63, 0x6B, 0x65, 0x79, 0xA1, 0x63, 0x6B, 0x65, 0x79, 0x18, UInt8(i), 0xFF]) diff --git a/Tests/CBORValueTests.swift b/Tests/CBORValueTests.swift new file mode 100644 index 000000000..1de4a7aa2 --- /dev/null +++ b/Tests/CBORValueTests.swift @@ -0,0 +1,193 @@ +// +// CBORValueTests.swift +// PotentCodables +// +// Copyright © 2021 Outfox, inc. +// +// +// Distributed under the MIT License, See LICENSE for details. +// + +import Foundation +import PotentCBOR +import XCTest + + +class CBORValueTests: XCTestCase { + + func testNilLiteralInitializer() { + let cbor: CBOR = nil + XCTAssertTrue(cbor.isNull) + } + + func testBoolLiteralInitializer() { + let cbor: CBOR = true + XCTAssertEqual(cbor.booleanValue, true) + } + + func testStringLiteralInitializer() { + let cbor: CBOR = "Hello World!" + XCTAssertEqual(cbor.utf8StringValue, "Hello World!") + } + + func testFloatLiteralInitializer() { + let cbor: CBOR = 1.234 + XCTAssertEqual(cbor.doubleValue, 1.234) + } + + func testArrayLiteralInitializer() { + let cbor: CBOR = [1, 2, 3] + XCTAssertEqual(cbor.arrayValue, [1, 2, 3]) + } + + func testDictionaryLiteralInitializer() { + let cbor: CBOR = ["b": 1, "a": 2, "c": 3] + XCTAssertEqual(cbor.mapValue, ["b": 1, "a": 2, "c": 3]) + } + + func testDataInitializer() { + let cbor = CBOR(Data([1, 2, 3, 4])) + XCTAssertEqual(cbor.bytesStringValue, Data([1, 2, 3, 4])) + } + + func testUIntInitializer() { + let cbor = CBOR(UInt(5)) + XCTAssertEqual(cbor.integerValue() as UInt?, 5) + } + + func testHalfInitializer() { + let cbor = CBOR(CBOR.Half(1.5)) + XCTAssertEqual(cbor.floatingPointValue() as CBOR.Half?, 1.5) + } + + func testFloatInitializer() { + let cbor = CBOR(Float(1.5)) + XCTAssertEqual(cbor.floatingPointValue() as Float?, 1.5) + } + + func testCBORNumberFloatLiteralInitializer() { + let number: CBOR = 1.234 + XCTAssertEqual(number.doubleValue, 1.234) + } + + func testDynamicMemberSubscript() { + let map: CBOR = [ + "aaa": 1, + ] + XCTAssertEqual(map.aaa, .unsignedInt(1)) + XCTAssertEqual(map.bbb, nil) + XCTAssertEqual(CBOR.null.aaa, nil) + } + + func testIntSubscript() { + var array: CBOR = [ + "a", "b", "c" + ] + XCTAssertEqual(array[1], .utf8String("b")) + XCTAssertEqual(array[3], nil) + XCTAssertEqual(CBOR.null[1], nil) + + array[1] = .utf8String("d") + XCTAssertEqual(array[1], .utf8String("d")) + } + + func testStringSubscript() { + var map: CBOR = [ + "aaa": 1, + ] + XCTAssertEqual(map["aaa"], .unsignedInt(1)) + XCTAssertEqual(map["bbb"], nil) + XCTAssertEqual(CBOR.null["aaa"], nil) + + map["aaa"] = .unsignedInt(5) + XCTAssertEqual(map["aaa"], .unsignedInt(5)) + } + + func testNullAccessor() { + XCTAssertTrue(CBOR.null.isNull) + XCTAssertFalse(CBOR.unsignedInt(5).isNull) + } + + func testStringAccessor() { + XCTAssertEqual(CBOR.utf8String("Hello World!").utf8StringValue, "Hello World!") + XCTAssertNil(CBOR.unsignedInt(5).utf8StringValue) + } + + func testByteStringAccessor() { + XCTAssertEqual(CBOR.byteString(Data([1, 2, 3, 4])).bytesStringValue, Data([1, 2, 3, 4])) + XCTAssertNil(CBOR.unsignedInt(5).bytesStringValue) + } + + func testBoolAccessor() { + XCTAssertEqual(CBOR.boolean(true).booleanValue, true) + XCTAssertNil(CBOR.unsignedInt(5).booleanValue) + } + + func testIntegerAccessor() { + XCTAssertEqual(CBOR.unsignedInt(5).integerValue() as Int?, 5) + XCTAssertEqual(CBOR.negativeInt(4).integerValue() as Int?, -5) + XCTAssertNil(CBOR.double(5.3).integerValue() as Int?) + XCTAssertNil(CBOR.boolean(false).integerValue() as Int?) + } + + func testSimpleAccessor() { + XCTAssertEqual(CBOR.simple(5).simpleValue, 5) + XCTAssertNil(CBOR.boolean(false).simpleValue) + } + + func testHalfAccessor() { + XCTAssertEqual(CBOR.half(5.3).halfValue, 5.3) + XCTAssertNil(CBOR.boolean(false).halfValue) + } + + func testFloatAccessor() { + XCTAssertEqual(CBOR.float(5.3).floatValue, 5.3) + XCTAssertNil(CBOR.boolean(false).floatValue) + } + + func testDoubleAccessor() { + XCTAssertEqual(CBOR.double(5.3).doubleValue, 5.3) + XCTAssertNil(CBOR.boolean(false).doubleValue) + } + + func testFloatingAccessor() { + XCTAssertEqual(CBOR.unsignedInt(5).floatingPointValue() as Float?, 5.0) + XCTAssertEqual(CBOR.negativeInt(4).floatingPointValue() as Float?, -5.0) + XCTAssertEqual(CBOR.half(5.3).floatingPointValue() as CBOR.Half?, 5.3) + XCTAssertEqual(CBOR.float(5.3).floatingPointValue() as Float?, 5.3) + XCTAssertEqual(CBOR.double(5.3).floatingPointValue() as Double?, 5.3) + XCTAssertNil(CBOR.boolean(false).floatingPointValue() as Double?) + } + + func testArrayAccessor() { + XCTAssertEqual(CBOR.array([1, 2, 3]).arrayValue, [1, 2, 3] as CBOR.Array) + XCTAssertNil(CBOR.boolean(false).arrayValue) + } + + func testObjectAccessor() { + XCTAssertEqual(CBOR.map(["a": 1, "b": 2, "c": 3]).mapValue, ["a": 1, "b": 2, "c": 3] as CBOR.Map) + XCTAssertNil(CBOR.boolean(false).mapValue) + } + + func testUnwrap() { + XCTAssertNil(CBOR.null.unwrapped) + XCTAssertNil(CBOR.undefined.unwrapped) + XCTAssertEqual(CBOR.boolean(true).unwrapped as? Bool, true) + XCTAssertEqual(CBOR.byteString(Data([1, 2, 3, 4])).unwrapped as? Data, Data([1, 2, 3, 4])) + XCTAssertEqual(CBOR.simple(5).unwrapped as? UInt8, 5) + XCTAssertEqual(CBOR.utf8String("Hello World!").unwrapped as? String, "Hello World!") + XCTAssertEqual(CBOR.unsignedInt(5).unwrapped as? UInt64, 5) + XCTAssertEqual(CBOR.negativeInt(4).unwrapped as? Int64, -5) + XCTAssertEqual(CBOR.half(1.5).unwrapped as? CBOR.Half, 1.5) + XCTAssertEqual(CBOR.float(1.5).unwrapped as? Float, 1.5) + XCTAssertEqual(CBOR.double(1.5).unwrapped as? Double, 1.5) + XCTAssertEqual(CBOR.array([1, 2, 3]).unwrapped as? [UInt64], [1, 2, 3]) + XCTAssertEqual(CBOR.map(["a": 1, "b": 2, "c": 3]).unwrapped as? [String: UInt64], ["a": 1, "b": 2, "c": 3]) + XCTAssertEqual(CBOR.tagged(CBOR.Tag(rawValue: 5), CBOR.unsignedInt(5)).unwrapped as? UInt64, 5) + } + + func testUntagged() { + XCTAssertEqual(CBOR.tagged(CBOR.Tag(rawValue: 5), CBOR.unsignedInt(5)).untagged, CBOR.unsignedInt(5)) + } + +} diff --git a/Tests/CBORWriterTests.swift b/Tests/CBORWriterTests.swift index d2d8d6fdd..53161dea7 100644 --- a/Tests/CBORWriterTests.swift +++ b/Tests/CBORWriterTests.swift @@ -51,6 +51,9 @@ class CBORWriterTests: XCTestCase { XCTAssertEqual(try encode { try $0.encodeInt(i) }, [UInt8(i)]) XCTAssertEqual(try encode { try $0.encodeInt(-i) }.count, 1) } + for i in 24 ... UInt8.max { + XCTAssertEqual(try encode { try $0.encodeUInt(i) }, [0x18, UInt8(i)]) + } XCTAssertEqual(try encode { try $0.encodeInt(-1) }, [0x20]) XCTAssertEqual(try encode { try $0.encodeInt(-10) }, [0x29]) XCTAssertEqual(try encode { try $0.encodeInt(-24) }, [0x37]) diff --git a/Tests/Datas.swift b/Tests/Datas.swift new file mode 100644 index 000000000..185a2727a --- /dev/null +++ b/Tests/Datas.swift @@ -0,0 +1,42 @@ +// +// Datas.swift +// PotentCodables +// +// Copyright © 2021 Outfox, inc. +// +// +// Distributed under the MIT License, See LICENSE for details. +// + +import Foundation + +extension Data { + + func hexEncodedString() -> String { + return reduce("") {$0 + String(format: "%02x", $1)} + } + + init(hexEncoded: String) { + + let chars = Array(hexEncoded.utf8) + + var bytes = [UInt8]() + bytes.reserveCapacity(chars.count / 2) + + let map: [UInt8] = [ + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, // 01234567 + 0x08, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 89:;<=>? + 0x00, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x00, // @ABCDEFG + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // HIJKLMNO + ] + + for i in stride(from: 0, to: chars.count, by: 2) { + let index1 = Int(chars[i] & 0x1F ^ 0x10) + let index2 = Int(chars[i + 1] & 0x1F ^ 0x10) + bytes.append(map[index1] << 4 | map[index2]) + } + + self.init(bytes) + } + +} diff --git a/Tests/Dates.swift b/Tests/Dates.swift new file mode 100644 index 000000000..1aac4c6a2 --- /dev/null +++ b/Tests/Dates.swift @@ -0,0 +1,27 @@ +// +// Dates.swift +// PotentCodables +// +// Copyright © 2021 Outfox, inc. +// +// +// Distributed under the MIT License, See LICENSE for details. +// + +import Foundation + +extension Date { + + var truncatedToSecs: Date { + Date(timeIntervalSinceReferenceDate: timeIntervalSinceReferenceDate.rounded()) + } + + var truncatedToMillisecs: Date { + Date(timeIntervalSinceReferenceDate: millisecondsFromReferenceDate) + } + + var millisecondsFromReferenceDate: TimeInterval { + return (timeIntervalSinceReferenceDate * 1000.0).rounded() / 1000.0 + } + +} diff --git a/Tests/JSONAnyValueTests.swift b/Tests/JSONAnyValueTests.swift new file mode 100644 index 000000000..f62e81dd3 --- /dev/null +++ b/Tests/JSONAnyValueTests.swift @@ -0,0 +1,207 @@ +// +// JSONAnyValueTests.swift +// PotentCodables +// +// Copyright © 2021 Outfox, inc. +// +// +// Distributed under the MIT License, See LICENSE for details. +// + +import BigInt +import Foundation +import PotentCodables +import PotentJSON +import XCTest + + +class JSONAnyValueTests: XCTestCase { + + let prettyEncoder = { + let enc = JSON.Encoder() + enc.outputFormatting = .prettyPrinted + return enc + }() + + func testDecode() throws { + + struct TestValue: Codable { + var null: AnyValue + var bool: AnyValue + var number: AnyValue + var string: AnyValue + var array: AnyValue + var object: AnyValue + } + + let json = """ + { + "null": null, + "bool": true, + "number": 123.456, + "string": "Hello World!", + "array": [null, false, 456, "a"], + "object": { + "c": 1, + "a": 2, + "d": 3, + "b": 4 + } + } + """ + + let value = try JSON.Decoder.default.decode(TestValue.self, from: json) + XCTAssertEqual(value.null, AnyValue.nil) + XCTAssertEqual(value.bool, AnyValue.bool(true)) + XCTAssertEqual(value.number, AnyValue.double(123.456)) + XCTAssertEqual(value.string, AnyValue.string("Hello World!")) + XCTAssertEqual(value.array, AnyValue.array([.nil, .bool(false), .int64(456), .string("a")])) + XCTAssertEqual(value.object, AnyValue.dictionary(["c": 1, "a": 2, "d": 3, "b": 4])) + } + + func testEncode() throws { + + struct TestValue: Codable { + var `nil`: AnyValue = .nil + var bool: AnyValue = .bool(true) + var string: AnyValue = .string("Hello World!") + var pi8: AnyValue = .int8(2) + var pi16: AnyValue = .int16(500) + var pi32: AnyValue = .int32(70_000) + var pi64: AnyValue = .int64(5_000_000_000) + var ni8: AnyValue = .int8(-2) + var ni16: AnyValue = .int16(-500) + var ni32: AnyValue = .int32(-70_000) + var ni64: AnyValue = .int64(-5_000_000_000) + var u8: AnyValue = .uint8(UInt8.max) + var u16: AnyValue = .uint16(UInt16.max) + var u32: AnyValue = .uint32(UInt32.max) + var u64: AnyValue = .uint64(UInt64.max) + var nint: AnyValue = .integer(BigInt("-999000000000000000000000000000")) + var pint: AnyValue = .integer(BigInt("999000000000000000000000000000")) + var uint: AnyValue = .unsignedInteger(BigUInt("999000000000000000000000000000")) + var f16: AnyValue = .float16(1.5) + var f32: AnyValue = .float(12.34567) + var f64: AnyValue = .double(123.4567) + var pdec: AnyValue = .decimal(Decimal(sign: .plus, exponent: -3, significand: 1234567)) + var ndec: AnyValue = .decimal(Decimal(sign: .minus, exponent: -3, significand: 1234567)) + var data: AnyValue = .data("Binary Data".data(using: .utf8)!) + var url: AnyValue = .url(URL(string: "https://example.com/some/thing")!) + var uuid: AnyValue = .uuid(UUID(uuidString: "46076D06-86E8-4B3B-80EF-B24115D4C609")!) + var date: AnyValue = .date(Date(timeIntervalSinceReferenceDate: 1234567.89)) + var array: AnyValue = .array([nil, false, 456, "a"]) + var object: AnyValue = .dictionary([ + "c": 1, + "a": 2, + "d": 3, + "b": 4, + ]) + var intObject: AnyValue = .dictionary([ + 2: "a", + 1: "b", + 4: "c", + 3: "d", + ]) + } + let srcValue = TestValue() + + let json = + """ + { + "nil" : null, + "bool" : true, + "string" : "Hello World!", + "pi8" : 2, + "pi16" : 500, + "pi32" : 70000, + "pi64" : 5000000000, + "ni8" : -2, + "ni16" : -500, + "ni32" : -70000, + "ni64" : -5000000000, + "u8" : 255, + "u16" : 65535, + "u32" : 4294967295, + "u64" : 18446744073709551615, + "nint" : -999000000000000000000000000000, + "pint" : 999000000000000000000000000000, + "uint" : 999000000000000000000000000000, + "f16" : 1.5, + "f32" : 12.34567, + "f64" : 123.4567, + "pdec" : 1234.567, + "ndec" : -1234.567, + "data" : "QmluYXJ5IERhdGE=", + "url" : "https://example.com/some/thing", + "uuid" : "46076D06-86E8-4B3B-80EF-B24115D4C609", + "date" : "2001-01-15T06:56:07.890Z", + "array" : [ + null, + false, + 456, + "a" + ], + "object" : { + "c" : 1, + "a" : 2, + "d" : 3, + "b" : 4 + }, + "intObject" : { + "2" : "a", + "1" : "b", + "4" : "c", + "3" : "d" + } + } + """ + + XCTAssertEqual(try prettyEncoder.encodeString(TestValue()), json) + + let dstValue = try JSON.Decoder.default.decode(TestValue.self, from: json) + XCTAssertEqual(dstValue.nil, srcValue.nil) + XCTAssertEqual(dstValue.bool, srcValue.bool) + XCTAssertEqual(dstValue.string, srcValue.string) + XCTAssertEqual(dstValue.pi8, .int64(2)) + XCTAssertEqual(dstValue.pi16, .int64(500)) + XCTAssertEqual(dstValue.pi32, .int64(70_000)) + XCTAssertEqual(dstValue.pi64, srcValue.pi64) + XCTAssertEqual(dstValue.ni8, .int64(-2)) + XCTAssertEqual(dstValue.ni16, .int64(-500)) + XCTAssertEqual(dstValue.ni32, .int64(-70_000)) + XCTAssertEqual(dstValue.ni64, srcValue.ni64) + XCTAssertEqual(dstValue.u8, .int64(Int64(UInt8.max))) + XCTAssertEqual(dstValue.u16, .int64(Int64(UInt16.max))) + XCTAssertEqual(dstValue.u32, .int64(Int64(UInt32.max))) + XCTAssertEqual(dstValue.u64, .uint64(UInt64.max)) + XCTAssertEqual(dstValue.pint, srcValue.pint) + XCTAssertEqual(dstValue.nint, srcValue.nint) + XCTAssertEqual(dstValue.uint, .integer(BigInt("999000000000000000000000000000"))) + XCTAssertEqual(dstValue.f16, .double(1.5)) + XCTAssertEqual(dstValue.f32, .double(12.34567)) + XCTAssertEqual(dstValue.f64, srcValue.f64) + XCTAssertEqual(dstValue.pdec, .double(1234.567)) + XCTAssertEqual(dstValue.ndec, .double(-1234.567)) + XCTAssertEqual(dstValue.data, .string(srcValue.data.dataValue!.base64EncodedString())) + XCTAssertEqual(dstValue.url, .string(srcValue.url.urlValue!.absoluteString)) + XCTAssertEqual(dstValue.uuid, .string(srcValue.uuid.uuidValue!.uuidString)) + XCTAssertEqual(dstValue.date.stringValue, ZonedDate(date: srcValue.date.dateValue!, timeZone: .utc).iso8601EncodedString()) + XCTAssertEqual(dstValue.array, .array([nil, false, .int64(456), "a"])) + XCTAssertEqual(dstValue.object, .dictionary(["c": .int64(1), "a": .int64(2), "d": .int64(3), "b": .int64(4)])) + XCTAssertEqual(dstValue.intObject, .dictionary([.string("2"): "a", .string("1"): "b", .string("4"): "c", .string("3"): "d"])) + } + + func testEncodeInvalidDictionaryThrowsError() { + + struct TestValue: Codable { + var invalid: AnyValue = .dictionary([ + .data("Bad Key".data(using: .utf8)!): "Shouldn't Work!" + ]) + } + + XCTAssertThrowsError(try JSON.Encoder.default.encode(TestValue())) { error in + AssertEncodingInvalidValue(error) + } + } + +} diff --git a/Tests/JSONDecoderTests.swift b/Tests/JSONDecoderTests.swift new file mode 100644 index 000000000..b1eed663a --- /dev/null +++ b/Tests/JSONDecoderTests.swift @@ -0,0 +1,2284 @@ +// +// JSONDecoderTests.swift +// PotentCodables +// +// Copyright © 2021 Outfox, inc. +// +// +// Distributed under the MIT License, See LICENSE for details. +// + +import BigInt +import Foundation +import PotentCodables +import PotentJSON +import XCTest + + +class JSONDecoderTests: XCTestCase { + + func testDecodeFromUTF8Data() { + + struct TestValue: Codable { + var test: String + } + + let json = #"{"test":"Hello World!"}"# + + let decoder = JSON.Decoder() + decoder.keyDecodingStrategy = .useDefaultKeys + + XCTAssertNoThrow(try decoder.decode(TestValue.self, from: json.data(using: .utf8)!)) + } + + func testDecodeWithDefaultKeyStrategy() { + + struct TestValue: Codable { + var camelCased: String + } + + let json = #"{"camelCased":"Hello World!"}"# + + let decoder = JSON.Decoder() + decoder.keyDecodingStrategy = .useDefaultKeys + + XCTAssertNoThrow(try decoder.decode(TestValue.self, from: json)) + } + + func testDecodeWithSnakeCaseKeyStrategy() { + + struct TestValue: Codable { + var snakeCased: String + } + + let json = #"{"snake_cased":"Hello World!"}"# + + let decoder = JSON.Decoder() + decoder.keyDecodingStrategy = .convertFromSnakeCase + + XCTAssertNoThrow(try decoder.decode(TestValue.self, from: json)) + } + + func testDecodeWithCustomKeyStrategy() { + + struct TestValue: Codable { + var kebabCased: String + } + + let json = #"{"kebab-cased":"Hello World!"}"# + + let decoder = JSON.Decoder() + decoder.keyDecodingStrategy = .custom { _ in + return AnyCodingKey(stringValue: "kebabCased") + } + + XCTAssertNoThrow(try decoder.decode(TestValue.self, from: json)) + } + + func testDecodeISO8601Date() throws { + + let date = ZonedDate(date: Date(), timeZone: .utc) + let parsedDate = ZonedDate(iso8601Encoded: date.iso8601EncodedString())! + + struct TestValue: Codable { + var date: Date + } + + let json = #"{"date":"\#(parsedDate.iso8601EncodedString())"}"# + + let decoder = JSON.Decoder() + decoder.dateDecodingStrategy = .iso8601 + + let testValue = try decoder.decode(TestValue.self, from: json) + XCTAssertEqual(testValue.date, parsedDate.utcDate) + } + + func testDecodeEpochSecondsDate() throws { + + let date = Date() + + struct TestValue: Codable { + var date: Date + } + + let json = #"{"date":\#(date.timeIntervalSince1970)}"# + + let decoder = JSON.Decoder() + decoder.dateDecodingStrategy = .secondsSince1970 + + let testValue = try decoder.decode(TestValue.self, from: json) + XCTAssertEqual(testValue.date.timeIntervalSince1970, date.timeIntervalSince1970) + } + + func testDecodeEpochMillisecondsDate() throws { + + let date = Date() + + struct TestValue: Codable { + var date: Date + } + + let json = #"{"date":\#(date.timeIntervalSince1970 * 1000)}"# + + let decoder = JSON.Decoder() + decoder.dateDecodingStrategy = .millisecondsSince1970 + + let testValue = try decoder.decode(TestValue.self, from: json) + XCTAssertEqual((testValue.date.timeIntervalSince1970 * 1000.0).rounded(), + (date.timeIntervalSince1970 * 1000.0).rounded()) + } + + func testDecodeDeferredToDate() throws { + + let date = Date() + + struct TestValue: Codable { + var date: Date + } + + let json = #"{"date":\#(date.timeIntervalSinceReferenceDate)}"# + + let decoder = JSON.Decoder() + decoder.dateDecodingStrategy = .deferredToDate + + let testValue = try decoder.decode(TestValue.self, from: json) + XCTAssertEqual(testValue.date.timeIntervalSince1970, date.timeIntervalSince1970) + } + + func testDecodeWithCustomDecoder() throws { + + let date = Date() + + struct TestValue: Codable { + var date: Date + } + + let json = #"{"date":\#(date.timeIntervalSince1970)}"# + + let decoder = JSON.Decoder() + decoder.dateDecodingStrategy = .custom { + Date(timeIntervalSince1970: try $0.singleValueContainer().decode(Double.self)) + } + + let testValue = try decoder.decode(TestValue.self, from: json) + XCTAssertEqual(testValue.date.timeIntervalSince1970, date.timeIntervalSince1970) + } + + func testDecodeWithFormatter() throws { + + let formatter = DateFormatter() + formatter.dateFormat = "yyyy-MM-dd HH:mm:ss.SSS XXXX" + + let date = Date().truncatedToMillisecs + + struct TestValue: Codable { + var date: Date + } + + let json = #"{"date":"\#(formatter.string(from: date))"}"# + + let decoder = JSON.Decoder() + decoder.dateDecodingStrategy = .formatted(formatter) + + let testValue = try decoder.decode(TestValue.self, from: json) + XCTAssertEqual((testValue.date.timeIntervalSince1970 * 1000.0).rounded(), + (date.timeIntervalSince1970 * 1000.0).rounded()) + } + + func testDecodeBadFormattedDate() throws { + + let formatter = DateFormatter() + formatter.dateFormat = "yyyy-MM-dd HH:mm:ss.SSS XXXX" + + let date = Date().truncatedToMillisecs + + struct TestValue: Codable { + var date: Date + } + + let json = #"{"date":"\#(date)"}"# + + let decoder = JSON.Decoder() + decoder.dateDecodingStrategy = .formatted(formatter) + + XCTAssertThrowsError(try decoder.decode(TestValue.self, from: json)) { error in + AssertDecodingDataCorrupted(error) + } + } + + func testDecodeBadISO8601Date() throws { + + struct TestValue: Codable { + var date: Date + } + + let json = #"{"date":"2020 Wed 09"}"# + + let decoder = JSON.Decoder() + decoder.dateDecodingStrategy = .iso8601 + + XCTAssertThrowsError(try decoder.decode(TestValue.self, from: json)) { error in + AssertDecodingDataCorrupted(error) + } + } + + func testDecodeDateFromNull() throws { + + struct TestValue: Codable { + var date: Date + } + + let json = #"{"date":null}"# + + XCTAssertThrowsError(try JSON.Decoder.default.decode(TestValue.self, from: json)) { error in + AssertDecodingValueNotFound(error) + } + } + + func testDecodeDateFromNullItem() throws { + + struct TestValue: Codable { + init(from decoder: Decoder) throws { + var container = try decoder.unkeyedContainer() + _ = try container.decode(Date.self) + } + func encode(to encoder: Encoder) throws { + } + } + + XCTAssertThrowsError(try JSON.Decoder.default.decodeTree(TestValue.self, from: .array([nil]))) { error in + AssertDecodingValueNotFound(error) + } + } + + func testDecodeBase64Data() throws { + + let data = "Hello World!".data(using: .utf8)! + + struct TestValue: Codable { + var data: Data + } + + let json = #"{"data":"\#(data.base64EncodedString())"}"# + + let decoder = JSON.Decoder() + decoder.dataDecodingStrategy = .base64 + + let testValue = try decoder.decode(TestValue.self, from: json) + XCTAssertEqual(testValue.data, data) + } + + func testDecodeDeferredToData() throws { + + let data = "Hello World!".data(using: .utf8)! + + struct TestValue: Codable { + var data: Data + } + + let json = #"{"data":[\#(data.map { String($0) }.joined(separator: ", "))]}"# + + let decoder = JSON.Decoder() + decoder.dataDecodingStrategy = .deferredToData + + let testValue = try decoder.decode(TestValue.self, from: json) + XCTAssertEqual(testValue.data, data) + } + + func testDecodeCustomData() throws { + + let data = "Hello World!".data(using: .utf8)! + + struct TestValue: Codable { + var data: Data + } + + let json = #"{"data":"\#(data.hexEncodedString())"}"# + + let decoder = JSON.Decoder() + decoder.dataDecodingStrategy = .custom { decoder in + return Data(hexEncoded: try decoder.singleValueContainer().decode(String.self)) + } + + let testValue = try decoder.decode(TestValue.self, from: json) + XCTAssertEqual(testValue.data, data) + } + + func testDecodeBadBase64Data() throws { + + struct TestValue: Codable { + var data: Data + } + + let json = #"{"data":"This is not base64"}"# + + let decoder = JSON.Decoder() + decoder.dataDecodingStrategy = .base64 + + XCTAssertThrowsError(try decoder.decode(TestValue.self, from: json)) { error in + AssertDecodingDataCorrupted(error) + } + } + + func testDecodeIncorrectBase64Data() throws { + + struct TestValue: Codable { + var data: Data + } + + let json = #"{"data":[1, 2, 3, 4, 5]}"# + + let decoder = JSON.Decoder() + decoder.dataDecodingStrategy = .base64 + + XCTAssertThrowsError(try decoder.decode(TestValue.self, from: json)) { error in + AssertDecodingTypeMismatch(error) + } + } + + func testDecodeDataFromNull() throws { + + struct TestValue: Codable { + var data: Data + } + + let json = #"{"data":null}"# + + XCTAssertThrowsError(try JSON.Decoder.default.decode(TestValue.self, from: json)) { error in + AssertDecodingValueNotFound(error) + } + } + + func testDecodeDataFromNullItem() throws { + + struct TestValue: Codable { + init(from decoder: Decoder) throws { + var container = try decoder.unkeyedContainer() + _ = try container.decode(Data.self) + } + func encode(to encoder: Encoder) throws { + } + } + + XCTAssertThrowsError(try JSON.Decoder.default.decodeTree(TestValue.self, from: .array([nil]))) { error in + AssertDecodingValueNotFound(error) + } + } + + func testDecodeURL() throws { + + struct TestValue: Codable { + var url: URL + } + + let json = #"{"url":"https://example.com/some/thing"}"# + + let testValue = try JSON.Decoder.default.decode(TestValue.self, from: json) + XCTAssertEqual(testValue.url, URL(string: "https://example.com/some/thing")) + } + + func testDecodeBadURL() throws { + + struct TestValue: Codable { + var url: URL + } + + let json = #"{"url":"Not a URL"}"# + + XCTAssertThrowsError(try JSON.Decoder.default.decode(TestValue.self, from: json)) { error in + AssertDecodingDataCorrupted(error) + } + } + + func testDecodeURLFromNonString() throws { + + struct TestValue: Codable { + var url: URL + } + + let json = #"{"url":12345}"# + + XCTAssertThrowsError(try JSON.Decoder.default.decode(TestValue.self, from: json)) { error in + AssertDecodingTypeMismatch(error) + } + } + + func testDecodeURLFromNull() throws { + + struct TestValue: Codable { + var url: URL + } + + let json = #"{"url":null}"# + + XCTAssertThrowsError(try JSON.Decoder.default.decode(TestValue.self, from: json)) { error in + AssertDecodingValueNotFound(error) + } + } + + func testDecodeURLFromNullItem() throws { + + struct TestValue: Codable { + init(from decoder: Decoder) throws { + var container = try decoder.unkeyedContainer() + _ = try container.decode(URL.self) + } + func encode(to encoder: Encoder) throws { + } + } + + XCTAssertThrowsError(try JSON.Decoder.default.decodeTree(TestValue.self, from: .array([nil]))) { error in + AssertDecodingValueNotFound(error) + } + } + + func testDecodeUUID() throws { + + let uuid = UUID() + + struct TestValue: Codable { + var uuid: UUID + } + + let json = #"{"uuid":"\#(uuid.uuidString)"}"# + + let testValue = try JSON.Decoder.default.decode(TestValue.self, from: json) + XCTAssertEqual(testValue.uuid, uuid) + } + + func testDecodeBadUUID() throws { + + struct TestValue: Codable { + var uuid: UUID + } + + let json = #"{"uuid":"Not a UUID"}"# + + XCTAssertThrowsError(try JSON.Decoder.default.decode(TestValue.self, from: json)) { error in + AssertDecodingDataCorrupted(error) + } + } + + func testDecodeUUIDFromNonString() throws { + + struct TestValue: Codable { + var uuid: UUID + } + + let json = #"{"uuid":12345}"# + + XCTAssertThrowsError(try JSON.Decoder.default.decode(TestValue.self, from: json)) { error in + AssertDecodingTypeMismatch(error) + } + } + + func testDecodeUUIDFromNull() throws { + + struct TestValue: Codable { + var uuid: UUID + } + + let json = #"{"uuid":null}"# + + XCTAssertThrowsError(try JSON.Decoder.default.decode(TestValue.self, from: json)) { error in + AssertDecodingValueNotFound(error) + } + } + + func testDecodeUUIDFromNullItem() throws { + + struct TestValue: Codable { + init(from decoder: Decoder) throws { + var container = try decoder.unkeyedContainer() + _ = try container.decode(UUID.self) + } + func encode(to encoder: Encoder) throws { + } + } + + XCTAssertThrowsError(try JSON.Decoder.default.decodeTree(TestValue.self, from: .array([nil]))) { error in + AssertDecodingValueNotFound(error) + } + } + + func testDecodeString() throws { + + let string = "Hello World!" + + struct TestValue: Codable { + var string: String + } + + let json = #"{"string":"\#(string)"}"# + + let testValue = try JSON.Decoder.default.decode(TestValue.self, from: json) + XCTAssertEqual(testValue.string, string) + } + + func testDecodeStringFromNonString() throws { + + struct TestValue: Codable { + var string: String + } + + let json = #"{"string":12345}"# + + XCTAssertThrowsError(try JSON.Decoder.default.decode(TestValue.self, from: json)) { error in + AssertDecodingTypeMismatch(error) + } + } + + func testDecodeStringFromNull() throws { + + struct TestValue: Codable { + var string: String + } + + let json = #"{"string":null}"# + + XCTAssertThrowsError(try JSON.Decoder.default.decode(TestValue.self, from: json)) { error in + AssertDecodingValueNotFound(error) + } + } + + func testDecodeStringFromNullItem() throws { + + struct TestValue: Codable { + init(from decoder: Decoder) throws { + var container = try decoder.unkeyedContainer() + _ = try container.decode(String.self) + } + func encode(to encoder: Encoder) throws { + } + } + + XCTAssertThrowsError(try JSON.Decoder.default.decodeTree(TestValue.self, from: .array([nil]))) { error in + AssertDecodingValueNotFound(error) + } + } + + func testDecodeDecimalFromPositiveDecimal() throws { + + let decimal = Decimal(sign: .plus, exponent: -3, significand: 1234567) + + struct TestValue: Codable { + var decimal: Decimal + } + + let json = #"{"decimal":\#(decimal.description)}"# + + let testValue = try JSON.Decoder.default.decode(TestValue.self, from: json) + XCTAssertEqual(testValue.decimal, decimal) + } + + func testDecodeDecimalFromNegativeDecimal() throws { + + let decimal = Decimal(sign: .minus, exponent: -3, significand: 1234567) + + struct TestValue: Codable { + var decimal: Decimal + } + + let json = #"{"decimal":\#(decimal.description)}"# + + let testValue = try JSON.Decoder.default.decode(TestValue.self, from: json) + XCTAssertEqual(testValue.decimal, decimal) + } + + func testDecodeDecimalFromPositiveInteger() throws { + + let integer = 1234567 + + struct TestValue: Codable { + var decimal: Decimal + } + + let json = #"{"decimal":\#(integer)}"# + + let testValue = try JSON.Decoder.default.decode(TestValue.self, from: json) + XCTAssertEqual(testValue.decimal, Decimal(sign: .plus, exponent: 0, significand: Decimal(integer))) + } + + func testDecodeDecimalFromNegativeInteger() throws { + + let integer = -1234567 + + struct TestValue: Codable { + var decimal: Decimal + } + + let json = #"{"decimal":\#(integer)}"# + + let testValue = try JSON.Decoder.default.decode(TestValue.self, from: json) + XCTAssertEqual(testValue.decimal, Decimal(sign: .minus, exponent: 0, significand: Decimal(integer.magnitude))) + } + + func testDecodeDecimalFromNaNWithStrategy() throws { + + struct TestValue: Codable { + var decimal: Decimal + } + + let json = #"{"decimal":"nan"}"# + + let decoder = JSON.Decoder() + decoder.nonConformingFloatDecodingStrategy = + .convertFromString(positiveInfinity: "+inf", negativeInfinity: "-inf", nan: "nan") + + let testValue = try decoder.decode(TestValue.self, from: json) + XCTAssertEqual(testValue.decimal, Decimal.nan) + } + + func testDecodeNonConformingDecimalThrowsError() throws { + + struct TestValue: Codable { + var decimal: Decimal + } + + let json = #"{"decimal":"nan"}"# + + let decoder = JSON.Decoder() + decoder.nonConformingFloatDecodingStrategy = .throw + + XCTAssertThrowsError(try decoder.decode(TestValue.self, from: json)) { error in + AssertDecodingTypeMismatch(error) + } + } + + func testDecodeDecimalFromNonNumber() throws { + + struct TestValue: Codable { + var decimal: Decimal + } + + let json = #"{"decimal":"Not a Number"}"# + + XCTAssertThrowsError(try JSON.Decoder.default.decode(TestValue.self, from: json)) { error in + AssertDecodingTypeMismatch(error) + } + } + + func testDecodeDecimalFromNull() throws { + + struct TestValue: Codable { + var decimal: Decimal + } + + let json = #"{"decimal":null}"# + + XCTAssertThrowsError(try JSON.Decoder.default.decode(TestValue.self, from: json)) { error in + AssertDecodingValueNotFound(error) + } + } + + func testDecodeDecimalFromNullItem() throws { + + struct TestValue: Codable { + init(from decoder: Decoder) throws { + var container = try decoder.unkeyedContainer() + _ = try container.decode(Decimal.self) + } + func encode(to encoder: Encoder) throws { + } + } + + XCTAssertThrowsError(try JSON.Decoder.default.decodeTree(TestValue.self, from: .array([nil]))) { error in + AssertDecodingValueNotFound(error) + } + } + + func testDecodeDoubleFromPositiveDecimal() throws { + + let double = Double(sign: .plus, exponent: -3, significand: 1234567) + + struct TestValue: Codable { + var double: Double + } + + let json = #"{"double":\#(double.description)}"# + + let testValue = try JSON.Decoder.default.decode(TestValue.self, from: json) + XCTAssertEqual(testValue.double, double) + } + + func testDecodeDoubleFromNegativeDecimal() throws { + + let double = Double(sign: .minus, exponent: -3, significand: 1234567) + + struct TestValue: Codable { + var double: Double + } + + let json = #"{"double":\#(double.description)}"# + + let testValue = try JSON.Decoder.default.decode(TestValue.self, from: json) + XCTAssertEqual(testValue.double, double) + } + + func testDecodeDoubleFromPositiveInteger() throws { + + let integer = 1234567 + + struct TestValue: Codable { + var double: Double + } + + let json = #"{"double":\#(integer)}"# + + let testValue = try JSON.Decoder.default.decode(TestValue.self, from: json) + XCTAssertEqual(testValue.double, Double(sign: .plus, exponent: 0, significand: Double(integer))) + } + + func testDecodeDoubleFromNegativeInteger() throws { + + let integer = -1234567 + + struct TestValue: Codable { + var double: Double + } + + let json = #"{"double":\#(integer)}"# + + let testValue = try JSON.Decoder.default.decode(TestValue.self, from: json) + XCTAssertEqual(testValue.double, Double(sign: .minus, exponent: 0, significand: Double(integer.magnitude))) + } + + func testDecodeDoubleFromNaNWithStrategy() throws { + + struct TestValue: Codable { + var double: Double + } + + let json = #"{"double":"nan"}"# + + let decoder = JSON.Decoder() + decoder.nonConformingFloatDecodingStrategy = + .convertFromString(positiveInfinity: "+inf", negativeInfinity: "-inf", nan: "nan") + + let testValue = try decoder.decode(TestValue.self, from: json) + XCTAssertTrue(testValue.double.isNaN) + } + + func testDecodeDoubleFromPositiveInfinityWithStrategy() throws { + + struct TestValue: Codable { + var double: Double + } + + let json = #"{"double":"+inf"}"# + + let decoder = JSON.Decoder() + decoder.nonConformingFloatDecodingStrategy = + .convertFromString(positiveInfinity: "+inf", negativeInfinity: "-inf", nan: "nan") + + let testValue = try decoder.decode(TestValue.self, from: json) + XCTAssertEqual(testValue.double, Double.infinity) + } + + func testDecodeDoubleFromNegativeInfinityWithStrategy() throws { + + struct TestValue: Codable { + var double: Double + } + + let json = #"{"double":"-inf"}"# + + let decoder = JSON.Decoder() + decoder.nonConformingFloatDecodingStrategy = + .convertFromString(positiveInfinity: "+inf", negativeInfinity: "-inf", nan: "nan") + + let testValue = try decoder.decode(TestValue.self, from: json) + XCTAssertEqual(testValue.double, -Double.infinity) + } + + func testDecodeNonConformingDoubleThrowsError() throws { + + struct TestValue: Codable { + var double: Double + } + + let json = #"{"double":"nan"}"# + + let decoder = JSON.Decoder() + decoder.nonConformingFloatDecodingStrategy = .throw + + XCTAssertThrowsError(try decoder.decode(TestValue.self, from: json)) { error in + AssertDecodingTypeMismatch(error) + } + } + + func testDecodeDoubleFromNonNumber() throws { + + struct TestValue: Codable { + var double: Double + } + + let json = #"{"double":"Not a Number"}"# + + XCTAssertThrowsError(try JSON.Decoder.default.decode(TestValue.self, from: json)) { error in + AssertDecodingTypeMismatch(error) + } + } + + func testDecodeDoubleFromNull() throws { + + struct TestValue: Codable { + var double: Double + } + + let json = #"{"double":null}"# + + XCTAssertThrowsError(try JSON.Decoder.default.decode(TestValue.self, from: json)) { error in + AssertDecodingValueNotFound(error) + } + } + + func testDecodeDoubleFromNullItem() throws { + + struct TestValue: Codable { + init(from decoder: Decoder) throws { + var container = try decoder.unkeyedContainer() + _ = try container.decode(Double.self) + } + func encode(to encoder: Encoder) throws { + } + } + + XCTAssertThrowsError(try JSON.Decoder.default.decodeTree(TestValue.self, from: .array([nil]))) { error in + AssertDecodingValueNotFound(error) + } + } + + func testDecodeFloatFromPositiveDecimal() throws { + + let float = Float(sign: .plus, exponent: -3, significand: 1234567) + + struct TestValue: Codable { + var float: Float + } + + let json = #"{"float":\#(float.description)}"# + + let testValue = try JSON.Decoder.default.decode(TestValue.self, from: json) + XCTAssertEqual(testValue.float, float) + } + + func testDecodeFloatFromNegativeDecimal() throws { + + let float = Float(sign: .minus, exponent: -3, significand: 1234567) + + struct TestValue: Codable { + var float: Float + } + + let json = #"{"float":\#(float.description)}"# + + let testValue = try JSON.Decoder.default.decode(TestValue.self, from: json) + XCTAssertEqual(testValue.float, float) + } + + func testDecodeFloatFromPositiveInteger() throws { + + let integer = 1234567 + + struct TestValue: Codable { + var float: Float + } + + let json = #"{"float":\#(integer)}"# + + let testValue = try JSON.Decoder.default.decode(TestValue.self, from: json) + XCTAssertEqual(testValue.float, Float(sign: .plus, exponent: 0, significand: Float(integer))) + } + + func testDecodeFloatFromNegativeInteger() throws { + + let integer = -1234567 + + struct TestValue: Codable { + var float: Float + } + + let json = #"{"float":\#(integer)}"# + + let testValue = try JSON.Decoder.default.decode(TestValue.self, from: json) + XCTAssertEqual(testValue.float, Float(sign: .minus, exponent: 0, significand: Float(integer.magnitude))) + } + + func testDecodeFloatFromNaNWithStrategy() throws { + + struct TestValue: Codable { + var float: Float + } + + let json = #"{"float":"nan"}"# + + let decoder = JSON.Decoder() + decoder.nonConformingFloatDecodingStrategy = + .convertFromString(positiveInfinity: "+inf", negativeInfinity: "-inf", nan: "nan") + + let testValue = try decoder.decode(TestValue.self, from: json) + XCTAssertTrue(testValue.float.isNaN) + } + + func testDecodeFloatFromPositiveInfinityWithStrategy() throws { + + struct TestValue: Codable { + var float: Float + } + + let json = #"{"float":"+inf"}"# + + let decoder = JSON.Decoder() + decoder.nonConformingFloatDecodingStrategy = + .convertFromString(positiveInfinity: "+inf", negativeInfinity: "-inf", nan: "nan") + + let testValue = try decoder.decode(TestValue.self, from: json) + XCTAssertEqual(testValue.float, Float.infinity) + } + + func testDecodeFloatFromNegativeInfinityWithStrategy() throws { + + struct TestValue: Codable { + var float: Float + } + + let json = #"{"float":"-inf"}"# + + let decoder = JSON.Decoder() + decoder.nonConformingFloatDecodingStrategy = + .convertFromString(positiveInfinity: "+inf", negativeInfinity: "-inf", nan: "nan") + + let testValue = try decoder.decode(TestValue.self, from: json) + XCTAssertEqual(testValue.float, -Float.infinity) + } + + func testDecodeNonConformingFloatThrowsError() throws { + + struct TestValue: Codable { + var float: Float + } + + let json = #"{"float":"nan"}"# + + let decoder = JSON.Decoder() + decoder.nonConformingFloatDecodingStrategy = .throw + + XCTAssertThrowsError(try decoder.decode(TestValue.self, from: json)) { error in + AssertDecodingTypeMismatch(error) + } + } + + func testDecodeFloatFromNonNumber() throws { + + struct TestValue: Codable { + var float: Float + } + + let json = #"{"float":"Not a Number"}"# + + XCTAssertThrowsError(try JSON.Decoder.default.decode(TestValue.self, from: json)) { error in + AssertDecodingTypeMismatch(error) + } + } + + func testDecodeFloatFromNull() throws { + + struct TestValue: Codable { + var float: Float + } + + let json = #"{"float":null}"# + + XCTAssertThrowsError(try JSON.Decoder.default.decode(TestValue.self, from: json)) { error in + AssertDecodingValueNotFound(error) + } + } + + func testDecodeFloatFromNullItem() throws { + + struct TestValue: Codable { + init(from decoder: Decoder) throws { + var container = try decoder.unkeyedContainer() + _ = try container.decode(Float.self) + } + func encode(to encoder: Encoder) throws { + } + } + + XCTAssertThrowsError(try JSON.Decoder.default.decodeTree(TestValue.self, from: .array([nil]))) { error in + AssertDecodingValueNotFound(error) + } + } + + func testDecodeFloat16FromPositiveDecimal() throws { + + let float = Float16(sign: .plus, exponent: -3, significand: 1234) + + struct TestValue: Codable { + var float: Float16 + } + + let json = #"{"float":\#(float)}"# + + let testValue = try JSON.Decoder.default.decode(TestValue.self, from: json) + XCTAssertEqual(testValue.float, float) + } + + func testDecodeFloat16FromNegativeDecimal() throws { + + let float = Float16(sign: .minus, exponent: -3, significand: 1234) + + struct TestValue: Codable { + var float: Float16 + } + + let json = #"{"float":\#(float)}"# + + let testValue = try JSON.Decoder.default.decode(TestValue.self, from: json) + XCTAssertEqual(testValue.float, float) + } + + func testDecodeFloat16FromPositiveInteger() throws { + + let integer = 1234 + + struct TestValue: Codable { + var float: Float16 + } + + let json = #"{"float":\#(integer)}"# + + let testValue = try JSON.Decoder.default.decode(TestValue.self, from: json) + XCTAssertEqual(testValue.float, Float16(sign: .plus, exponent: 0, significand: Float16(integer))) + } + + func testDecodeFloat16FromNegativeInteger() throws { + + let integer = -1234 + + struct TestValue: Codable { + var float: Float16 + } + + let json = #"{"float":\#(integer)}"# + + let testValue = try JSON.Decoder.default.decode(TestValue.self, from: json) + XCTAssertEqual(testValue.float, Float16(sign: .minus, exponent: 0, significand: Float16(integer.magnitude))) + } + + func testDecodeFloat16FromNaNWithStrategy() throws { + + struct TestValue: Codable { + var float: Float16 + } + + let json = #"{"float":"nan"}"# + + let decoder = JSON.Decoder() + decoder.nonConformingFloatDecodingStrategy = + .convertFromString(positiveInfinity: "+inf", negativeInfinity: "-inf", nan: "nan") + + let testValue = try decoder.decode(TestValue.self, from: json) + XCTAssertTrue(testValue.float.isNaN) + } + + func testDecodeFloat16FromPositiveInfinityWithStrategy() throws { + + struct TestValue: Codable { + var float: Float16 + } + + let json = #"{"float":"+inf"}"# + + let decoder = JSON.Decoder() + decoder.nonConformingFloatDecodingStrategy = + .convertFromString(positiveInfinity: "+inf", negativeInfinity: "-inf", nan: "nan") + + let testValue = try decoder.decode(TestValue.self, from: json) + XCTAssertEqual(testValue.float, Float16.infinity) + } + + func testDecodeFloat16FromNegativeInfinityWithStrategy() throws { + + struct TestValue: Codable { + var float: Float16 + } + + let json = #"{"float":"-inf"}"# + + let decoder = JSON.Decoder() + decoder.nonConformingFloatDecodingStrategy = + .convertFromString(positiveInfinity: "+inf", negativeInfinity: "-inf", nan: "nan") + + let testValue = try decoder.decode(TestValue.self, from: json) + XCTAssertEqual(testValue.float, -Float16.infinity) + } + + func testDecodeNonConformingFloat16ThrowsError() throws { + + struct TestValue: Codable { + var float: Float16 + } + + let json = #"{"float":"nan"}"# + + let decoder = JSON.Decoder() + decoder.nonConformingFloatDecodingStrategy = .throw + + XCTAssertThrowsError(try decoder.decode(TestValue.self, from: json)) { error in + AssertDecodingTypeMismatch(error) + } + } + + func testDecodeFloat16FromNonNumber() throws { + + struct TestValue: Codable { + var float: Float16 + } + + let json = #"{"float":"Not a Number"}"# + + XCTAssertThrowsError(try JSON.Decoder.default.decode(TestValue.self, from: json)) { error in + AssertDecodingTypeMismatch(error) + } + } + + func testDecodeFloat16FromNull() throws { + + struct TestValue: Codable { + var float: Float16 + } + + let json = #"{"float":null}"# + + XCTAssertThrowsError(try JSON.Decoder.default.decode(TestValue.self, from: json)) { error in + AssertDecodingValueNotFound(error) + } + } + + func testDecodeFloat16FromNullItem() throws { + + struct TestValue: Codable { + init(from decoder: Decoder) throws { + var container = try decoder.unkeyedContainer() + _ = try container.decode(Float16.self) + } + func encode(to encoder: Encoder) throws { + } + } + + XCTAssertThrowsError(try JSON.Decoder.default.decodeTree(TestValue.self, from: .array([nil]))) { error in + AssertDecodingValueNotFound(error) + } + } + + func testDecodeBigIntFromPositiveInteger() throws { + + let integer = 1234567 + + struct TestValue: Codable { + var bigInt: BigInt + } + + let json = #"{"bigInt":\#(integer)}"# + + let testValue = try JSON.Decoder.default.decode(TestValue.self, from: json) + XCTAssertEqual(testValue.bigInt, BigInt(integer)) + } + + func testDecodeBigIntFromNegativeInteger() throws { + + let integer = -1234567 + + struct TestValue: Codable { + var bigInt: BigInt + } + + let json = #"{"bigInt":\#(integer)}"# + + let testValue = try JSON.Decoder.default.decode(TestValue.self, from: json) + XCTAssertEqual(testValue.bigInt, BigInt(integer)) + } + + func testDecodeBigIntFromDecimalFails() throws { + + struct TestValue: Codable { + var bigInt: BigInt + } + + let json = #"{"bigInt":123.456}"# + + XCTAssertThrowsError(try JSON.Decoder.default.decode(TestValue.self, from: json)) { error in + AssertDecodingDataCorrupted(error) + } + } + + func testDecodeBigIntFromNonNumber() throws { + + struct TestValue: Codable { + var bigInt: BigInt + } + + let json = #"{"bigInt":"Not a Number"}"# + + XCTAssertThrowsError(try JSON.Decoder.default.decode(TestValue.self, from: json)) { error in + AssertDecodingTypeMismatch(error) + } + } + + func testDecodeBigIntFromNull() throws { + + struct TestValue: Codable { + var bigInt: BigInt + } + + let json = #"{"bigInt":null}"# + + XCTAssertThrowsError(try JSON.Decoder.default.decode(TestValue.self, from: json)) { error in + AssertDecodingValueNotFound(error) + } + } + + func testDecodeBigIntFromNullItem() throws { + + struct TestValue: Codable { + init(from decoder: Decoder) throws { + var container = try decoder.unkeyedContainer() + _ = try container.decode(BigInt.self) + } + func encode(to encoder: Encoder) throws { + } + } + + XCTAssertThrowsError(try JSON.Decoder.default.decodeTree(TestValue.self, from: .array([nil]))) { error in + AssertDecodingValueNotFound(error) + } + } + + func testDecodeBigUIntFromInteger() throws { + + let integer = 1234567 + + struct TestValue: Codable { + var bigUInt: BigUInt + } + + let json = #"{"bigUInt":\#(integer)}"# + + let testValue = try JSON.Decoder.default.decode(TestValue.self, from: json) + XCTAssertEqual(testValue.bigUInt, BigUInt(integer)) + } + + func testDecodeBigUIntFromNegativeIntegerFails() throws { + + let integer = -1234567 + + struct TestValue: Codable { + var bigUInt: BigUInt + } + + let json = #"{"bigUInt":\#(integer)}"# + + XCTAssertThrowsError(try JSON.Decoder.default.decode(TestValue.self, from: json)) { error in + AssertDecodingDataCorrupted(error) + } + } + + func testDecodeBigUIntFromNonNumber() throws { + + struct TestValue: Codable { + var bigUInt: BigUInt + } + + let json = #"{"bigUInt":"Not a Number"}"# + + XCTAssertThrowsError(try JSON.Decoder.default.decode(TestValue.self, from: json)) { error in + AssertDecodingTypeMismatch(error) + } + } + + func testDecodeBigUIntFromNull() throws { + + struct TestValue: Codable { + var bigUInt: BigUInt + } + + let json = #"{"bigUInt":null}"# + + XCTAssertThrowsError(try JSON.Decoder.default.decode(TestValue.self, from: json)) { error in + AssertDecodingValueNotFound(error) + } + } + + func testDecodeBigUIntFromNullItem() throws { + + struct TestValue: Codable { + init(from decoder: Decoder) throws { + var container = try decoder.unkeyedContainer() + _ = try container.decode(BigUInt.self) + } + func encode(to encoder: Encoder) throws { + } + } + + XCTAssertThrowsError(try JSON.Decoder.default.decodeTree(TestValue.self, from: .array([nil]))) { error in + AssertDecodingValueNotFound(error) + } + } + + func testDecodeUIntFromPositiveInteger() throws { + + let integer = 1234567 + + struct TestValue: Codable { + var int: UInt + } + + let json = #"{"int":\#(integer)}"# + + let testValue = try JSON.Decoder.default.decode(TestValue.self, from: json) + XCTAssertEqual(testValue.int, UInt(integer)) + } + + func testDecodeUIntFromNegativeIntegerFails() throws { + + let integer = -1234567 + + struct TestValue: Codable { + var int: UInt + } + + let json = #"{"int":\#(integer)}"# + + XCTAssertThrowsError(try JSON.Decoder.default.decode(TestValue.self, from: json)) { error in + AssertDecodingTypeMismatch(error) + } + } + + func testDecodeUIntFromDecimalFails() throws { + + struct TestValue: Codable { + var int: UInt + } + + let json = #"{"int":123.456}"# + + XCTAssertThrowsError(try JSON.Decoder.default.decode(TestValue.self, from: json)) { error in + AssertDecodingTypeMismatch(error) + } + } + + func testDecodeUIntFromNonNumber() throws { + + struct TestValue: Codable { + var int: UInt + } + + let json = #"{"int":"Not a Number"}"# + + XCTAssertThrowsError(try JSON.Decoder.default.decode(TestValue.self, from: json)) { error in + AssertDecodingTypeMismatch(error) + } + } + + func testDecodeUIntFromNull() throws { + + struct TestValue: Codable { + var int: UInt + } + + let json = #"{"int":null}"# + + XCTAssertThrowsError(try JSON.Decoder.default.decode(TestValue.self, from: json)) { error in + AssertDecodingValueNotFound(error) + } + } + + func testDecodeUIntFromNullItem() throws { + + struct TestValue: Codable { + init(from decoder: Decoder) throws { + var container = try decoder.unkeyedContainer() + _ = try container.decode(UInt.self) + } + func encode(to encoder: Encoder) throws { + } + } + + XCTAssertThrowsError(try JSON.Decoder.default.decodeTree(TestValue.self, from: .array([nil]))) { error in + AssertDecodingValueNotFound(error) + } + } + + func testDecodeUInt64FromPositiveInteger() throws { + + let integer = 1234567 + + struct TestValue: Codable { + var int: UInt64 + } + + let json = #"{"int":\#(integer)}"# + + let testValue = try JSON.Decoder.default.decode(TestValue.self, from: json) + XCTAssertEqual(testValue.int, UInt64(integer)) + } + + func testDecodeUInt64FromNegativeIntegerFails() throws { + + let integer = -1234567 + + struct TestValue: Codable { + var int: UInt64 + } + + let json = #"{"int":\#(integer)}"# + + XCTAssertThrowsError(try JSON.Decoder.default.decode(TestValue.self, from: json)) { error in + AssertDecodingTypeMismatch(error) + } + } + + func testDecodeUInt64FromDecimalFails() throws { + + struct TestValue: Codable { + var int: UInt64 + } + + let json = #"{"int":123.456}"# + + XCTAssertThrowsError(try JSON.Decoder.default.decode(TestValue.self, from: json)) { error in + AssertDecodingTypeMismatch(error) + } + } + + func testDecodeUInt64FromNonNumber() throws { + + struct TestValue: Codable { + var int: UInt64 + } + + let json = #"{"int":"Not a Number"}"# + + XCTAssertThrowsError(try JSON.Decoder.default.decode(TestValue.self, from: json)) { error in + AssertDecodingTypeMismatch(error) + } + } + + func testDecodeUInt64FromNull() throws { + + struct TestValue: Codable { + var int: UInt64 + } + + let json = #"{"int":null}"# + + XCTAssertThrowsError(try JSON.Decoder.default.decode(TestValue.self, from: json)) { error in + AssertDecodingValueNotFound(error) + } + } + + func testDecodeUInt64FromNullItem() throws { + + struct TestValue: Codable { + init(from decoder: Decoder) throws { + var container = try decoder.unkeyedContainer() + _ = try container.decode(UInt64.self) + } + func encode(to encoder: Encoder) throws { + } + } + + XCTAssertThrowsError(try JSON.Decoder.default.decodeTree(TestValue.self, from: .array([nil]))) { error in + AssertDecodingValueNotFound(error) + } + } + + func testDecodeUInt32FromPositiveInteger() throws { + + let integer = 1234567 + + struct TestValue: Codable { + var int: UInt32 + } + + let json = #"{"int":\#(integer)}"# + + let testValue = try JSON.Decoder.default.decode(TestValue.self, from: json) + XCTAssertEqual(testValue.int, UInt32(integer)) + } + + func testDecodeUInt32FromNegativeIntegerFails() throws { + + let integer = -1234567 + + struct TestValue: Codable { + var int: UInt32 + } + + let json = #"{"int":\#(integer)}"# + + XCTAssertThrowsError(try JSON.Decoder.default.decode(TestValue.self, from: json)) { error in + AssertDecodingTypeMismatch(error) + } + } + + func testDecodeUInt32FromDecimalFails() throws { + + struct TestValue: Codable { + var int: UInt32 + } + + let json = #"{"int":123.456}"# + + XCTAssertThrowsError(try JSON.Decoder.default.decode(TestValue.self, from: json)) { error in + AssertDecodingTypeMismatch(error) + } + } + + func testDecodeUInt32FromNonNumber() throws { + + struct TestValue: Codable { + var int: UInt32 + } + + let json = #"{"int":"Not a Number"}"# + + XCTAssertThrowsError(try JSON.Decoder.default.decode(TestValue.self, from: json)) { error in + AssertDecodingTypeMismatch(error) + } + } + + func testDecodeUInt32FromNull() throws { + + struct TestValue: Codable { + var int: UInt32 + } + + let json = #"{"int":null}"# + + XCTAssertThrowsError(try JSON.Decoder.default.decode(TestValue.self, from: json)) { error in + AssertDecodingValueNotFound(error) + } + } + + func testDecodeUInt32FromNullItem() throws { + + struct TestValue: Codable { + init(from decoder: Decoder) throws { + var container = try decoder.unkeyedContainer() + _ = try container.decode(UInt32.self) + } + func encode(to encoder: Encoder) throws { + } + } + + XCTAssertThrowsError(try JSON.Decoder.default.decodeTree(TestValue.self, from: .array([nil]))) { error in + AssertDecodingValueNotFound(error) + } + } + + func testDecodeUInt16FromPositiveInteger() throws { + + let integer = 65432 + + struct TestValue: Codable { + var int: UInt16 + } + + let json = #"{"int":\#(integer)}"# + + let testValue = try JSON.Decoder.default.decode(TestValue.self, from: json) + XCTAssertEqual(testValue.int, UInt16(integer)) + } + + func testDecodeUInt16FromNegativeIntegerFails() throws { + + let integer = -65432 + + struct TestValue: Codable { + var int: UInt16 + } + + let json = #"{"int":\#(integer)}"# + + XCTAssertThrowsError(try JSON.Decoder.default.decode(TestValue.self, from: json)) { error in + AssertDecodingTypeMismatch(error) + } + } + + func testDecodeUInt16FromDecimalFails() throws { + + struct TestValue: Codable { + var int: UInt16 + } + + let json = #"{"int":123.456}"# + + XCTAssertThrowsError(try JSON.Decoder.default.decode(TestValue.self, from: json)) { error in + AssertDecodingTypeMismatch(error) + } + } + + func testDecodeUInt16FromNonNumber() throws { + + struct TestValue: Codable { + var int: UInt16 + } + + let json = #"{"int":"Not a Number"}"# + + XCTAssertThrowsError(try JSON.Decoder.default.decode(TestValue.self, from: json)) { error in + AssertDecodingTypeMismatch(error) + } + } + + func testDecodeUInt16FromNull() throws { + + struct TestValue: Codable { + var int: UInt16 + } + + let json = #"{"int":null}"# + + XCTAssertThrowsError(try JSON.Decoder.default.decode(TestValue.self, from: json)) { error in + AssertDecodingValueNotFound(error) + } + } + + func testDecodeUInt16FromNullItem() throws { + + struct TestValue: Codable { + init(from decoder: Decoder) throws { + var container = try decoder.unkeyedContainer() + _ = try container.decode(UInt16.self) + } + func encode(to encoder: Encoder) throws { + } + } + + XCTAssertThrowsError(try JSON.Decoder.default.decodeTree(TestValue.self, from: .array([nil]))) { error in + AssertDecodingValueNotFound(error) + } + } + + func testDecodeUInt8FromPositiveInteger() throws { + + let integer = 234 + + struct TestValue: Codable { + var int: UInt8 + } + + let json = #"{"int":\#(integer)}"# + + let testValue = try JSON.Decoder.default.decode(TestValue.self, from: json) + XCTAssertEqual(testValue.int, UInt8(integer)) + } + + func testDecodeUInt8FromNegativeIntegerFails() throws { + + let integer = -234 + + struct TestValue: Codable { + var int: UInt8 + } + + let json = #"{"int":\#(integer)}"# + + XCTAssertThrowsError(try JSON.Decoder.default.decode(TestValue.self, from: json)) { error in + AssertDecodingTypeMismatch(error) + } + } + + func testDecodeUInt8FromDecimalFails() throws { + + struct TestValue: Codable { + var int: UInt8 + } + + let json = #"{"int":123.456}"# + + XCTAssertThrowsError(try JSON.Decoder.default.decode(TestValue.self, from: json)) { error in + AssertDecodingTypeMismatch(error) + } + } + + func testDecodeUInt8FromNonNumber() throws { + + struct TestValue: Codable { + var int: UInt8 + } + + let json = #"{"int":"Not a Number"}"# + + XCTAssertThrowsError(try JSON.Decoder.default.decode(TestValue.self, from: json)) { error in + AssertDecodingTypeMismatch(error) + } + } + + func testDecodeUInt8FromNull() throws { + + struct TestValue: Codable { + var int: UInt8 + } + + let json = #"{"int":null}"# + + XCTAssertThrowsError(try JSON.Decoder.default.decode(TestValue.self, from: json)) { error in + AssertDecodingValueNotFound(error) + } + } + + func testDecodeUInt8FromNullItem() throws { + + struct TestValue: Codable { + init(from decoder: Decoder) throws { + var container = try decoder.unkeyedContainer() + _ = try container.decode(UInt8.self) + } + func encode(to encoder: Encoder) throws { + } + } + + XCTAssertThrowsError(try JSON.Decoder.default.decodeTree(TestValue.self, from: .array([nil]))) { error in + AssertDecodingValueNotFound(error) + } + } + + func testDecodeIntFromPositiveInteger() throws { + + let integer = 1234567 + + struct TestValue: Codable { + var int: Int + } + + let json = #"{"int":\#(integer)}"# + + let testValue = try JSON.Decoder.default.decode(TestValue.self, from: json) + XCTAssertEqual(testValue.int, Int(integer)) + } + + func testDecodeIntFromNegativeInteger() throws { + + let integer = -1234567 + + struct TestValue: Codable { + var int: Int + } + + let json = #"{"int":\#(integer)}"# + + let testValue = try JSON.Decoder.default.decode(TestValue.self, from: json) + XCTAssertEqual(testValue.int, Int(integer)) + } + + func testDecodeIntFromOverflowFails() throws { + + struct TestValue: Codable { + var int: Int + } + + let json = #"{"int":\#(UInt(Int.max) + 1)}"# + + XCTAssertThrowsError(try JSON.Decoder.default.decode(TestValue.self, from: json)) { error in + AssertDecodingTypeMismatch(error) + } + } + + func testDecodeIntFromDecimalFails() throws { + + struct TestValue: Codable { + var int: Int + } + + let json = #"{"int":123.456}"# + + XCTAssertThrowsError(try JSON.Decoder.default.decode(TestValue.self, from: json)) { error in + AssertDecodingTypeMismatch(error) + } + } + + func testDecodeIntFromNonNumber() throws { + + struct TestValue: Codable { + var int: Int + } + + let json = #"{"int":"Not a Number"}"# + + XCTAssertThrowsError(try JSON.Decoder.default.decode(TestValue.self, from: json)) { error in + AssertDecodingTypeMismatch(error) + } + } + + func testDecodeIntFromNull() throws { + + struct TestValue: Codable { + var int: Int + } + + let json = #"{"int":null}"# + + XCTAssertThrowsError(try JSON.Decoder.default.decode(TestValue.self, from: json)) { error in + AssertDecodingValueNotFound(error) + } + } + + func testDecodeIntFromNullItem() throws { + + struct TestValue: Codable { + init(from decoder: Decoder) throws { + var container = try decoder.unkeyedContainer() + _ = try container.decode(Int.self) + } + func encode(to encoder: Encoder) throws { + } + } + + XCTAssertThrowsError(try JSON.Decoder.default.decodeTree(TestValue.self, from: .array([nil]))) { error in + AssertDecodingValueNotFound(error) + } + } + + func testDecodeInt64FromPositiveInteger() throws { + + let integer = 1234567 + + struct TestValue: Codable { + var int: Int64 + } + + let json = #"{"int":\#(integer)}"# + + let testValue = try JSON.Decoder.default.decode(TestValue.self, from: json) + XCTAssertEqual(testValue.int, Int64(integer)) + } + + func testDecodeInt64FromNegativeIntegerFails() throws { + + let integer = -1234567 + + struct TestValue: Codable { + var int: Int64 + } + + let json = #"{"int":\#(integer)}"# + + let testValue = try JSON.Decoder.default.decode(TestValue.self, from: json) + XCTAssertEqual(testValue.int, Int64(integer)) + } + + func testDecodeInt64FromOverflowFails() throws { + + struct TestValue: Codable { + var int: Int64 + } + + let json = #"{"int":\#(UInt64(Int64.max) + 1)}"# + + XCTAssertThrowsError(try JSON.Decoder.default.decode(TestValue.self, from: json)) { error in + AssertDecodingTypeMismatch(error) + } + } + + func testDecodeInt64FromDecimalFails() throws { + + struct TestValue: Codable { + var int: Int64 + } + + let json = #"{"int":123.456}"# + + XCTAssertThrowsError(try JSON.Decoder.default.decode(TestValue.self, from: json)) { error in + AssertDecodingTypeMismatch(error) + } + } + + func testDecodeInt64FromNonNumber() throws { + + struct TestValue: Codable { + var int: Int64 + } + + let json = #"{"int":"Not a Number"}"# + + XCTAssertThrowsError(try JSON.Decoder.default.decode(TestValue.self, from: json)) { error in + AssertDecodingTypeMismatch(error) + } + } + + func testDecodeInt64FromNull() throws { + + struct TestValue: Codable { + var int: Int64 + } + + let json = #"{"int":null}"# + + XCTAssertThrowsError(try JSON.Decoder.default.decode(TestValue.self, from: json)) { error in + AssertDecodingValueNotFound(error) + } + } + + func testDecodeInt64FromNullItem() throws { + + struct TestValue: Codable { + init(from decoder: Decoder) throws { + var container = try decoder.unkeyedContainer() + _ = try container.decode(Int64.self) + } + func encode(to encoder: Encoder) throws { + } + } + + XCTAssertThrowsError(try JSON.Decoder.default.decodeTree(TestValue.self, from: .array([nil]))) { error in + AssertDecodingValueNotFound(error) + } + } + + func testDecodeInt32FromPositiveInteger() throws { + + let integer = 1234567 + + struct TestValue: Codable { + var int: Int32 + } + + let json = #"{"int":\#(integer)}"# + + let testValue = try JSON.Decoder.default.decode(TestValue.self, from: json) + XCTAssertEqual(testValue.int, Int32(integer)) + } + + func testDecodeInt32FromNegativeInteger() throws { + + let integer = -1234567 + + struct TestValue: Codable { + var int: Int32 + } + + let json = #"{"int":\#(integer)}"# + + let testValue = try JSON.Decoder.default.decode(TestValue.self, from: json) + XCTAssertEqual(testValue.int, Int32(integer)) + } + + func testDecodeInt32FromOverflowFails() throws { + + struct TestValue: Codable { + var int: Int32 + } + + let json = #"{"int":\#(UInt32(Int32.max) + 1)}"# + + XCTAssertThrowsError(try JSON.Decoder.default.decode(TestValue.self, from: json)) { error in + AssertDecodingTypeMismatch(error) + } + } + + func testDecodeInt32FromDecimalFails() throws { + + struct TestValue: Codable { + var int: Int32 + } + + let json = #"{"int":123.456}"# + + XCTAssertThrowsError(try JSON.Decoder.default.decode(TestValue.self, from: json)) { error in + AssertDecodingTypeMismatch(error) + } + } + + func testDecodeInt32FromNonNumber() throws { + + struct TestValue: Codable { + var int: Int32 + } + + let json = #"{"int":"Not a Number"}"# + + XCTAssertThrowsError(try JSON.Decoder.default.decode(TestValue.self, from: json)) { error in + AssertDecodingTypeMismatch(error) + } + } + + func testDecodeInt32FromNull() throws { + + struct TestValue: Codable { + var int: Int32 + } + + let json = #"{"int":null}"# + + XCTAssertThrowsError(try JSON.Decoder.default.decode(TestValue.self, from: json)) { error in + AssertDecodingValueNotFound(error) + } + } + + func testDecodeInt32FromNullItem() throws { + + struct TestValue: Codable { + init(from decoder: Decoder) throws { + var container = try decoder.unkeyedContainer() + _ = try container.decode(Int32.self) + } + func encode(to encoder: Encoder) throws { + } + } + + XCTAssertThrowsError(try JSON.Decoder.default.decodeTree(TestValue.self, from: .array([nil]))) { error in + AssertDecodingValueNotFound(error) + } + } + + func testDecodeInt16FromPositiveInteger() throws { + + let integer = 32101 + + struct TestValue: Codable { + var int: Int16 + } + + let json = #"{"int":\#(integer)}"# + + let testValue = try JSON.Decoder.default.decode(TestValue.self, from: json) + XCTAssertEqual(testValue.int, Int16(integer)) + } + + func testDecodeInt16FromNegativeInteger() throws { + + let integer = -32101 + + struct TestValue: Codable { + var int: Int16 + } + + let json = #"{"int":\#(integer)}"# + + let testValue = try JSON.Decoder.default.decode(TestValue.self, from: json) + XCTAssertEqual(testValue.int, Int16(integer)) + } + + func testDecodeInt16FromOverflowFails() throws { + + struct TestValue: Codable { + var int: Int16 + } + + let json = #"{"int":\#(UInt16(Int16.max) + 1)}"# + + XCTAssertThrowsError(try JSON.Decoder.default.decode(TestValue.self, from: json)) { error in + AssertDecodingTypeMismatch(error) + } + } + + func testDecodeInt16FromDecimalFails() throws { + + struct TestValue: Codable { + var int: Int16 + } + + let json = #"{"int":123.456}"# + + XCTAssertThrowsError(try JSON.Decoder.default.decode(TestValue.self, from: json)) { error in + AssertDecodingTypeMismatch(error) + } + } + + func testDecodeInt16FromNonNumber() throws { + + struct TestValue: Codable { + var int: Int16 + } + + let json = #"{"int":"Not a Number"}"# + + XCTAssertThrowsError(try JSON.Decoder.default.decode(TestValue.self, from: json)) { error in + AssertDecodingTypeMismatch(error) + } + } + + func testDecodeInt16FromNull() throws { + + struct TestValue: Codable { + var int: Int16 + } + + let json = #"{"int":null}"# + + XCTAssertThrowsError(try JSON.Decoder.default.decode(TestValue.self, from: json)) { error in + AssertDecodingValueNotFound(error) + } + } + + func testDecodeInt16FromNullItem() throws { + + struct TestValue: Codable { + init(from decoder: Decoder) throws { + var container = try decoder.unkeyedContainer() + _ = try container.decode(Int16.self) + } + func encode(to encoder: Encoder) throws { + } + } + + XCTAssertThrowsError(try JSON.Decoder.default.decodeTree(TestValue.self, from: .array([nil]))) { error in + AssertDecodingValueNotFound(error) + } + } + + func testDecodeInt8FromPositiveInteger() throws { + + let integer = 123 + + struct TestValue: Codable { + var int: Int8 + } + + let json = #"{"int":\#(integer)}"# + + let testValue = try JSON.Decoder.default.decode(TestValue.self, from: json) + XCTAssertEqual(testValue.int, Int8(integer)) + } + + func testDecodeInt8FromNegativeInteger() throws { + + let integer = -123 + + struct TestValue: Codable { + var int: Int8 + } + + let json = #"{"int":\#(integer)}"# + + let testValue = try JSON.Decoder.default.decode(TestValue.self, from: json) + XCTAssertEqual(testValue.int, Int8(integer)) + } + + func testDecodeInt8FromOverflowFails() throws { + + struct TestValue: Codable { + var int: Int8 + } + + let json = #"{"int":\#(UInt8(Int8.max) + 1)}"# + + XCTAssertThrowsError(try JSON.Decoder.default.decode(TestValue.self, from: json)) { error in + AssertDecodingTypeMismatch(error) + } + } + + func testDecodeInt8FromDecimalFails() throws { + + struct TestValue: Codable { + var int: Int8 + } + + let json = #"{"int":123.456}"# + + XCTAssertThrowsError(try JSON.Decoder.default.decode(TestValue.self, from: json)) { error in + AssertDecodingTypeMismatch(error) + } + } + + func testDecodeInt8FromNonNumber() throws { + + struct TestValue: Codable { + var int: Int8 + } + + let json = #"{"int":"Not a Number"}"# + + XCTAssertThrowsError(try JSON.Decoder.default.decode(TestValue.self, from: json)) { error in + AssertDecodingTypeMismatch(error) + } + } + + func testDecodeInt8FromNull() throws { + + struct TestValue: Codable { + var int: Int8 + } + + let json = #"{"int":null}"# + + XCTAssertThrowsError(try JSON.Decoder.default.decode(TestValue.self, from: json)) { error in + AssertDecodingValueNotFound(error) + } + } + + func testDecodeInt8FromNullItem() throws { + + struct TestValue: Codable { + init(from decoder: Decoder) throws { + var container = try decoder.unkeyedContainer() + _ = try container.decode(Int8.self) + } + func encode(to encoder: Encoder) throws { + } + } + + XCTAssertThrowsError(try JSON.Decoder.default.decodeTree(TestValue.self, from: .array([nil]))) { error in + AssertDecodingValueNotFound(error) + } + } + + func testDecodeBool() throws { + + struct TestValue: Codable { + var bool: Bool + } + + let json = #"{"bool":true}"# + + let testValue = try JSON.Decoder.default.decode(TestValue.self, from: json) + XCTAssertEqual(testValue.bool, true) + } + + func testDecodeBoolFromNonBoolean() throws { + + struct TestValue: Codable { + var bool: Bool + } + + let json = #"{"bool":1}"# + + XCTAssertThrowsError(try JSON.Decoder.default.decode(TestValue.self, from: json)) { error in + AssertDecodingTypeMismatch(error) + } + } + + func testDecodeBoolFromNull() throws { + + struct TestValue: Codable { + var bool: Bool + } + + let json = #"{"bool":null}"# + + XCTAssertThrowsError(try JSON.Decoder.default.decode(TestValue.self, from: json)) { error in + AssertDecodingValueNotFound(error) + } + } + + func testDecodeBoolFromNullItem() throws { + + struct TestValue: Codable { + init(from decoder: Decoder) throws { + var container = try decoder.unkeyedContainer() + _ = try container.decode(Bool.self) + } + func encode(to encoder: Encoder) throws { + } + } + + XCTAssertThrowsError(try JSON.Decoder.default.decodeTree(TestValue.self, from: .array([nil]))) { error in + AssertDecodingValueNotFound(error) + } + } + +} diff --git a/Tests/JSONEncoderTests.swift b/Tests/JSONEncoderTests.swift new file mode 100644 index 000000000..55e8eb352 --- /dev/null +++ b/Tests/JSONEncoderTests.swift @@ -0,0 +1,1138 @@ +// +// JSONEncoderTests.swift +// PotentCodables +// +// Copyright © 2021 Outfox, inc. +// +// +// Distributed under the MIT License, See LICENSE for details. +// + +import BigInt +import Foundation +import PotentCodables +import PotentJSON +import XCTest + + +class JSONEncoderTests: XCTestCase { + + func testEncodeWithDefaultKeyStrategy() { + + struct TestValue: Codable { + var camelCased: String + } + + let json = #"{"camelCased":"Hello World!"}"# + + let encoder = JSON.Encoder() + encoder.keyEncodingStrategy = .useDefaultKeys + + XCTAssertEqual(try encoder.encodeString(TestValue(camelCased: "Hello World!")), json) + } + + func testEncodeWithSnakeCaseKeyStrategy() { + + struct TestValue: Codable { + var snakeCased: String + } + + let json = #"{"snake_cased":"Hello World!"}"# + + let encoder = JSON.Encoder() + encoder.keyEncodingStrategy = .convertToSnakeCase + + XCTAssertEqual(try encoder.encodeString(TestValue(snakeCased: "Hello World!")), json) + } + + func testEncodeWithCustomKeyStrategy() { + + struct TestValue: Codable { + var kebabCased: String + } + + let json = #"{"kebab-cased":"Hello World!"}"# + + let encoder = JSON.Encoder() + encoder.keyEncodingStrategy = .custom { _ in + return AnyCodingKey(stringValue: "kebab-cased") + } + + XCTAssertEqual(try encoder.encodeString(TestValue(kebabCased: "Hello World!")), json) + } + + func testEncodeToString() throws { + + struct TestValue: Codable { + var int = 5 + var string = "Hello World!" + } + + let json = #"{"int":5,"string":"Hello World!"}"# + + let testJSON = try JSON.Encoder.default.encodeString(TestValue()) + XCTAssertEqual(testJSON, json) + } + + func testEncodeToStringWithPrettyOption() throws { + + struct TestValue: Codable { + var int = 5 + var string = "Hello World!" + var array = [1, 2, 3] + var object = [ + "b": 2, + "a": 1, + ] + } + + let json = + """ + { + "array" : [ + 1, + 2, + 3 + ], + "int" : 5, + "object" : { + "a" : 1, + "b" : 2 + }, + "string" : "Hello World!" + } + """ + + let encoder = JSON.Encoder() + encoder.outputFormatting = [.prettyPrinted, .sortedKeys] + + let testJSON = try encoder.encodeString(TestValue()) + XCTAssertEqual(testJSON, json) + } + + func testEncodeToStringWithEscapedStringsOption() throws { + + struct TestValue: Codable { + var url = "http://example.com/some/thing" + } + + let json = #"{"url":"http:\/\/example.com\/some\/thing"}"# + + let encoder = JSON.Encoder() + encoder.outputFormatting = .escapeSlashes + + let testJSON = try encoder.encodeString(TestValue()) + XCTAssertEqual(testJSON, json) + } + + func testEncodeToStringWithSortedKeysOption() throws { + + struct TestValue: Codable { + var c = 1 + var a = 2 + var b = 3 + } + + let json = #"{"a":2,"b":3,"c":1}"# + + let encoder = JSON.Encoder() + encoder.outputFormatting = .sortedKeys + + let testJSON = try encoder.encodeString(TestValue()) + XCTAssertEqual(testJSON, json) + } + + func testEncodeToData() throws { + + struct TestValue: Codable { + var int = 5 + var string = "Hello World!" + } + + let json = #"{"int":5,"string":"Hello World!"}"# + + let testJSONData = try JSON.Encoder.default.encode(TestValue()) + XCTAssertEqual(testJSONData, json.data(using: .utf8)!) + } + + func testEncodeToDataWithPrettyOption() throws { + + struct TestValue: Codable { + var int = 5 + var string = "Hello World!" + var array = [1, 2, 3] + var object = [ + "b": 2, + "a": 1, + ] + } + + let json = + """ + { + "array" : [ + 1, + 2, + 3 + ], + "int" : 5, + "object" : { + "a" : 1, + "b" : 2 + }, + "string" : "Hello World!" + } + """ + + let encoder = JSON.Encoder() + encoder.outputFormatting = [.prettyPrinted, .sortedKeys] + + let testJSONData = try encoder.encode(TestValue()) + XCTAssertEqual(testJSONData, json.data(using: .utf8)) + } + + func testEncodeToDataWithEscapedStringsOption() throws { + + struct TestValue: Codable { + var url = "http://example.com/some/thing" + } + + let json = #"{"url":"http:\/\/example.com\/some\/thing"}"# + + let encoder = JSON.Encoder() + encoder.outputFormatting = .escapeSlashes + + let testJSONData = try encoder.encode(TestValue()) + XCTAssertEqual(testJSONData, json.data(using: .utf8)) + } + + func testEncodeToDataWithSortedKeysOption() throws { + + struct TestValue: Codable { + var c = 1 + var a = 2 + var b = 3 + } + + let json = #"{"a":2,"b":3,"c":1}"# + + let encoder = JSON.Encoder() + encoder.outputFormatting = .sortedKeys + + let testJSONData = try encoder.encode(TestValue()) + XCTAssertEqual(testJSONData, json.data(using: .utf8)) + } + + func testEncodingChildContainers() throws { + + class TestValue: Codable { + var b: Int = 5 + var d: String = "Hello World!" + var a: Bool = true + var c: TestValue? + + init(c: TestValue? = nil) { + self.c = c + } + } + + let json = + """ + { + "b" : 5, + "d" : "Hello World!", + "a" : true, + "c" : { + "b" : 5, + "d" : "Hello World!", + "a" : true, + "c" : { + "b" : 5, + "d" : "Hello World!", + "a" : true + } + } + } + """ + + let testValue = TestValue(c: TestValue(c: TestValue())) + + let testJSON = try prettyEncoder.encodeString(testValue) + XCTAssertEqual(testJSON, json) + } + + func testEncodeDateAsISO8601() throws { + + let date = ZonedDate(date: Date(), timeZone: .utc) + let parsedDate = ZonedDate(iso8601Encoded: date.iso8601EncodedString())! + + struct TestValue: Codable { + var date: Date + } + + let json = #"{"date":"\#(parsedDate.iso8601EncodedString())"}"# + + let encoder = JSON.Encoder() + encoder.dateEncodingStrategy = .iso8601 + + let testJSON = try encoder.encodeString(TestValue(date: parsedDate.utcDate)) + XCTAssertEqual(testJSON, json) + } + + func testEncodeDateAsEpochSeconds() throws { + + let date = Date() + + struct TestValue: Codable { + var date: Date + } + + let json = #"{"date":\#(date.timeIntervalSince1970)}"# + + let encoder = JSON.Encoder() + encoder.dateEncodingStrategy = .secondsSince1970 + + let testJSON = try encoder.encodeString(TestValue(date: date)) + XCTAssertEqual(testJSON, json) + } + + func testEncodeDateAsEpochMilliseconds() throws { + + let date = Date() + + struct TestValue: Codable { + var date: Date + } + + let json = #"{"date":\#(date.timeIntervalSince1970 * 1000.0)}"# + + let encoder = JSON.Encoder() + encoder.dateEncodingStrategy = .millisecondsSince1970 + + let testJSON = try encoder.encodeString(TestValue(date: date)) + XCTAssertEqual(testJSON, json) + } + + func testEncodeDateWithDeferredToDate() throws { + + let date = Date() + + struct TestValue: Codable { + var date: Date + } + + let json = #"{"date":\#(date.timeIntervalSinceReferenceDate)}"# + + let encoder = JSON.Encoder() + encoder.dateEncodingStrategy = .deferredToDate + + let testJSON = try encoder.encodeString(TestValue(date: date)) + XCTAssertEqual(testJSON, json) + } + + func testEncodeDateWithFormatter() throws { + + let formatter = DateFormatter() + formatter.dateFormat = "yyyy-MM-dd HH:mm:ss.SSS XXXX" + + let date = Date() + + struct TestValue: Codable { + var date: Date + } + + let json = #"{"date":"\#(formatter.string(from: date))"}"# + + let encoder = JSON.Encoder() + encoder.dateEncodingStrategy = .formatted(formatter) + + let testJSON = try encoder.encodeString(TestValue(date: date)) + XCTAssertEqual(testJSON, json) + } + + func testEncodeDateWithCustomEncoder() throws { + + let formatter = DateFormatter() + formatter.dateFormat = "yyyy-MM-dd HH:mm:ss.SSS XXXX" + + let date = Date() + + struct TestValue: Codable { + var date: Date + } + + let json = #"{"date":\#(date.timeIntervalSinceReferenceDate)}"# + + let encoder = JSON.Encoder() + encoder.dateEncodingStrategy = .custom { date, encoder in + var container = encoder.singleValueContainer() + try container.encode(date.timeIntervalSinceReferenceDate) + } + + let testJSON = try encoder.encodeString(TestValue(date: date)) + XCTAssertEqual(testJSON, json) + } + + func testEncodeDateWithNothingCustomEncoder() throws { + + let formatter = DateFormatter() + formatter.dateFormat = "yyyy-MM-dd HH:mm:ss.SSS XXXX" + + let date = Date() + + struct TestValue: Codable { + var date: Date + } + + let json = #"{"date":null}"# + + let encoder = JSON.Encoder() + encoder.dateEncodingStrategy = .custom { _, _ in + // do nothing + } + + let testJSON = try encoder.encodeString(TestValue(date: date)) + XCTAssertEqual(testJSON, json) + } + + func testEncodeDataAsBase64() throws { + + let data = Data([1, 2, 3, 4, 5]) + + struct TestValue: Codable { + var data: Data + } + + let json = #"{"data":"\#(data.base64EncodedString())"}"# + + let encoder = JSON.Encoder() + encoder.dataEncodingStrategy = .base64 + + let testJSON = try encoder.encodeString(TestValue(data: data)) + XCTAssertEqual(testJSON, json) + } + + func testEncodeDataWithDeferredToData() throws { + + let data = Data([1, 2, 3, 4, 5]) + + struct TestValue: Codable { + var data: Data + } + + let json = #"{"data":[\#(data.map { String($0) }.joined(separator: ","))]}"# + + let encoder = JSON.Encoder() + encoder.dataEncodingStrategy = .deferredToData + + let testJSON = try encoder.encodeString(TestValue(data: data)) + XCTAssertEqual(testJSON, json) + } + + func testEncodeDataWithCustomEncoder() throws { + + let data = Data([1, 2, 3, 4, 5]) + + struct TestValue: Codable { + var data: Data + } + + let json = #"{"data":"\#(data.hexEncodedString())"}"# + + let encoder = JSON.Encoder() + encoder.dataEncodingStrategy = .custom { data, encoder in + var container = encoder.singleValueContainer() + try container.encode(data.hexEncodedString()) + } + + let testJSON = try encoder.encodeString(TestValue(data: data)) + XCTAssertEqual(testJSON, json) + } + + func testEncodeDataWithNothingCustomEncoder() throws { + + let data = Data([1, 2, 3, 4, 5]) + + struct TestValue: Codable { + var data: Data + } + + let json = #"{"data":null}"# + + let encoder = JSON.Encoder() + encoder.dataEncodingStrategy = .custom { _, _ in + } + + let testJSON = try encoder.encodeString(TestValue(data: data)) + XCTAssertEqual(testJSON, json) + } + + func testEncodePositiveBigInt() throws { + + let bigInt = BigInt(1234567) + + struct TestValue: Codable { + var bigInt: BigInt + } + + let json = #"{"bigInt":\#(bigInt)}"# + + let testJSON = try JSON.Encoder.default.encodeString(TestValue(bigInt: bigInt)) + XCTAssertEqual(testJSON, json) + } + + func testEncodeNegativeBigInt() throws { + + let bigInt = BigInt(-1234567) + + struct TestValue: Codable { + var bigInt: BigInt + } + + let json = #"{"bigInt":\#(bigInt)}"# + + let testJSON = try JSON.Encoder.default.encodeString(TestValue(bigInt: bigInt)) + XCTAssertEqual(testJSON, json) + } + + func testEncodeBigUInt() throws { + + let bigUInt = BigUInt(1234567) + + struct TestValue: Codable { + var bigUInt: BigUInt + } + + let json = #"{"bigUInt":\#(bigUInt)}"# + + let testJSON = try JSON.Encoder.default.encodeString(TestValue(bigUInt: bigUInt)) + XCTAssertEqual(testJSON, json) + } + + func testEncodePostiveDecimal() throws { + + let dec = Decimal(sign: .plus, exponent: -3, significand: 1234567) + + struct TestValue: Codable { + var dec: Decimal + } + + let json = #"{"dec":\#(dec)}"# + + let testJSON = try JSON.Encoder.default.encodeString(TestValue(dec: dec)) + XCTAssertEqual(testJSON, json) + } + + func testEncodeNegativeDecimal() throws { + + let dec = Decimal(sign: .minus, exponent: -3, significand: 1234567) + + struct TestValue: Codable { + var dec: Decimal + } + + let json = #"{"dec":\#(dec)}"# + + let testJSON = try JSON.Encoder.default.encodeString(TestValue(dec: dec)) + XCTAssertEqual(testJSON, json) + } + + func testEncodeDecimalNaNWithStrategy() throws { + + let dec = Decimal.nan + + struct TestValue: Codable { + var dec: Decimal + } + + let json = #"{"dec":"nan"}"# + + let encoder = JSON.Encoder() + encoder.nonConformingFloatEncodingStrategy = + .convertToString(positiveInfinity: "+inf", negativeInfinity: "-inf", nan: "nan") + + let testJSON = try encoder.encodeString(TestValue(dec: dec)) + XCTAssertEqual(testJSON, json) + } + + func testEncodeNonConformingDecimalThrowsError() throws { + + let dec = Decimal.nan + + struct TestValue: Codable { + var dec: Decimal + } + + let encoder = JSON.Encoder() + encoder.nonConformingFloatEncodingStrategy = .throw + + XCTAssertThrowsError(try JSON.Encoder.default.encodeString(TestValue(dec: dec))) { error in + AssertEncodingInvalidValue(error) + } + } + + func testEncodePostiveDouble() throws { + + let double = Double(sign: .plus, exponent: -3, significand: 1234567) + + struct TestValue: Codable { + var double: Double + } + + let json = #"{"double":\#(double)}"# + + let testJSON = try JSON.Encoder.default.encodeString(TestValue(double: double)) + XCTAssertEqual(testJSON, json) + } + + func testEncodeNegativeDouble() throws { + + let double = Double(sign: .minus, exponent: -3, significand: 1234567) + + struct TestValue: Codable { + var double: Double + } + + let json = #"{"double":\#(double)}"# + + let testJSON = try JSON.Encoder.default.encodeString(TestValue(double: double)) + XCTAssertEqual(testJSON, json) + } + + func testEncodeDoubleNaNWithStrategy() throws { + + let double = Double.nan + + struct TestValue: Codable { + var double: Double + } + + let json = #"{"double":"nan"}"# + + let encoder = JSON.Encoder() + encoder.nonConformingFloatEncodingStrategy = + .convertToString(positiveInfinity: "+inf", negativeInfinity: "-inf", nan: "nan") + + let testJSON = try encoder.encodeString(TestValue(double: double)) + XCTAssertEqual(testJSON, json) + } + + func testEncodeDoublePositiveInfinityWithStrategy() throws { + + let double = Double.infinity + + struct TestValue: Codable { + var double: Double + } + + let json = #"{"double":"+inf"}"# + + let encoder = JSON.Encoder() + encoder.nonConformingFloatEncodingStrategy = + .convertToString(positiveInfinity: "+inf", negativeInfinity: "-inf", nan: "nan") + + let testJSON = try encoder.encodeString(TestValue(double: double)) + XCTAssertEqual(testJSON, json) + } + + func testEncodeDoubleNegativeInfinityWithStrategy() throws { + + let double = -Double.infinity + + struct TestValue: Codable { + var double: Double + } + + let json = #"{"double":"-inf"}"# + + let encoder = JSON.Encoder() + encoder.nonConformingFloatEncodingStrategy = + .convertToString(positiveInfinity: "+inf", negativeInfinity: "-inf", nan: "nan") + + let testJSON = try encoder.encodeString(TestValue(double: double)) + XCTAssertEqual(testJSON, json) + } + + func testEncodeNonConformingDoubleThrowsError() throws { + + let double = Double.nan + + struct TestValue: Codable { + var double: Double + } + + let encoder = JSON.Encoder() + encoder.nonConformingFloatEncodingStrategy = .throw + + XCTAssertThrowsError(try JSON.Encoder.default.encodeString(TestValue(double: double))) { error in + AssertEncodingInvalidValue(error) + } + } + + func testEncodePostiveFloat() throws { + + let float = Float(sign: .plus, exponent: -3, significand: 1234567) + + struct TestValue: Codable { + var float: Float + } + + let json = #"{"float":\#(float)}"# + + let testJSON = try JSON.Encoder.default.encodeString(TestValue(float: float)) + XCTAssertEqual(testJSON, json) + } + + func testEncodeNegativeFloat() throws { + + let float = Float(sign: .minus, exponent: -3, significand: 1234567) + + struct TestValue: Codable { + var float: Float + } + + let json = #"{"float":\#(float)}"# + + let testJSON = try JSON.Encoder.default.encodeString(TestValue(float: float)) + XCTAssertEqual(testJSON, json) + } + + func testEncodeFloatNaNWithStrategy() throws { + + let float = Float.nan + + struct TestValue: Codable { + var float: Float + } + + let json = #"{"float":"nan"}"# + + let encoder = JSON.Encoder() + encoder.nonConformingFloatEncodingStrategy = + .convertToString(positiveInfinity: "+inf", negativeInfinity: "-inf", nan: "nan") + + let testJSON = try encoder.encodeString(TestValue(float: float)) + XCTAssertEqual(testJSON, json) + } + + func testEncodeFloatPositiveInfinityWithStrategy() throws { + + let float = Float.infinity + + struct TestValue: Codable { + var float: Float + } + + let json = #"{"float":"+inf"}"# + + let encoder = JSON.Encoder() + encoder.nonConformingFloatEncodingStrategy = + .convertToString(positiveInfinity: "+inf", negativeInfinity: "-inf", nan: "nan") + + let testJSON = try encoder.encodeString(TestValue(float: float)) + XCTAssertEqual(testJSON, json) + } + + func testEncodeFloatNegativeInfinityWithStrategy() throws { + + let float = -Float.infinity + + struct TestValue: Codable { + var float: Float + } + + let json = #"{"float":"-inf"}"# + + let encoder = JSON.Encoder() + encoder.nonConformingFloatEncodingStrategy = + .convertToString(positiveInfinity: "+inf", negativeInfinity: "-inf", nan: "nan") + + let testJSON = try encoder.encodeString(TestValue(float: float)) + XCTAssertEqual(testJSON, json) + } + + func testEncodeNonConformingFloatThrowsError() throws { + + let float = Float.nan + + struct TestValue: Codable { + var float: Float + } + + let encoder = JSON.Encoder() + encoder.nonConformingFloatEncodingStrategy = .throw + + XCTAssertThrowsError(try JSON.Encoder.default.encodeString(TestValue(float: float))) { error in + AssertEncodingInvalidValue(error) + } + } + + func testEncodePostiveFloat16() throws { + + let float = Float16(sign: .plus, exponent: -3, significand: 1234) + + struct TestValue: Codable { + var float: Float16 + } + + let json = #"{"float":\#(float)}"# + + let testJSON = try JSON.Encoder.default.encodeString(TestValue(float: float)) + XCTAssertEqual(testJSON, json) + } + + func testEncodeNegativeFloat16() throws { + + let float = Float16(sign: .minus, exponent: -3, significand: 1234) + + struct TestValue: Codable { + var float: Float16 + } + + let json = #"{"float":\#(float)}"# + + let testJSON = try JSON.Encoder.default.encodeString(TestValue(float: float)) + XCTAssertEqual(testJSON, json) + } + + func testEncodeFloat16NaNWithStrategy() throws { + + let float = Float16.nan + + struct TestValue: Codable { + var float: Float16 + } + + let json = #"{"float":"nan"}"# + + let encoder = JSON.Encoder() + encoder.nonConformingFloatEncodingStrategy = + .convertToString(positiveInfinity: "+inf", negativeInfinity: "-inf", nan: "nan") + + let testJSON = try encoder.encodeString(TestValue(float: float)) + XCTAssertEqual(testJSON, json) + } + + func testEncodeFloat16PositiveInfinityWithStrategy() throws { + + let float = Float16.infinity + + struct TestValue: Codable { + var float: Float16 + } + + let json = #"{"float":"+inf"}"# + + let encoder = JSON.Encoder() + encoder.nonConformingFloatEncodingStrategy = + .convertToString(positiveInfinity: "+inf", negativeInfinity: "-inf", nan: "nan") + + let testJSON = try encoder.encodeString(TestValue(float: float)) + XCTAssertEqual(testJSON, json) + } + + func testEncodeFloat16NegativeInfinityWithStrategy() throws { + + let float = -Float16.infinity + + struct TestValue: Codable { + var float: Float16 + } + + let json = #"{"float":"-inf"}"# + + let encoder = JSON.Encoder() + encoder.nonConformingFloatEncodingStrategy = + .convertToString(positiveInfinity: "+inf", negativeInfinity: "-inf", nan: "nan") + + let testJSON = try encoder.encodeString(TestValue(float: float)) + XCTAssertEqual(testJSON, json) + } + + func testEncodeNonConformingFloat16ThrowsError() throws { + + let float = Float16.nan + + struct TestValue: Codable { + var float: Float16 + } + + let encoder = JSON.Encoder() + encoder.nonConformingFloatEncodingStrategy = .throw + + XCTAssertThrowsError(try JSON.Encoder.default.encodeString(TestValue(float: float))) { error in + AssertEncodingInvalidValue(error) + } + } + + func testEncodePositiveInt8() throws { + + let int = Int8.max + + struct TestValue: Codable { + var int: Int8 + } + + let json = #"{"int":\#(int)}"# + + let testJSON = try JSON.Encoder.default.encodeString(TestValue(int: int)) + XCTAssertEqual(testJSON, json) + } + + func testEncodeNegativeInt8() throws { + + let int = Int8.min + + struct TestValue: Codable { + var int: Int8 + } + + let json = #"{"int":\#(int)}"# + + let testJSON = try JSON.Encoder.default.encodeString(TestValue(int: int)) + XCTAssertEqual(testJSON, json) + } + + func testEncodePositiveInt16() throws { + + let int = Int16.max + + struct TestValue: Codable { + var int: Int16 + } + + let json = #"{"int":\#(int)}"# + + let testJSON = try JSON.Encoder.default.encodeString(TestValue(int: int)) + XCTAssertEqual(testJSON, json) + } + + func testEncodeNegativeInt16() throws { + + let int = Int16.min + + struct TestValue: Codable { + var int: Int16 + } + + let json = #"{"int":\#(int)}"# + + let testJSON = try JSON.Encoder.default.encodeString(TestValue(int: int)) + XCTAssertEqual(testJSON, json) + } + + func testEncodePositiveInt32() throws { + + let int = Int32.max + + struct TestValue: Codable { + var int: Int32 + } + + let json = #"{"int":\#(int)}"# + + let testJSON = try JSON.Encoder.default.encodeString(TestValue(int: int)) + XCTAssertEqual(testJSON, json) + } + + func testEncodeNegativeInt32() throws { + + let int = Int32.min + + struct TestValue: Codable { + var int: Int32 + } + + let json = #"{"int":\#(int)}"# + + let testJSON = try JSON.Encoder.default.encodeString(TestValue(int: int)) + XCTAssertEqual(testJSON, json) + } + + func testEncodePositiveInt64() throws { + + let int = Int64.max + + struct TestValue: Codable { + var int: Int64 + } + + let json = #"{"int":\#(int)}"# + + let testJSON = try JSON.Encoder.default.encodeString(TestValue(int: int)) + XCTAssertEqual(testJSON, json) + } + + func testEncodeNegativeInt64() throws { + + let int = Int64.min + + struct TestValue: Codable { + var int: Int64 + } + + let json = #"{"int":\#(int)}"# + + let testJSON = try JSON.Encoder.default.encodeString(TestValue(int: int)) + XCTAssertEqual(testJSON, json) + } + + func testEncodePositiveInt() throws { + + let int = Int.max + + struct TestValue: Codable { + var int: Int + } + + let json = #"{"int":\#(int)}"# + + let testJSON = try JSON.Encoder.default.encodeString(TestValue(int: int)) + XCTAssertEqual(testJSON, json) + } + + func testEncodeNegativeInt() throws { + + let int = Int.min + + struct TestValue: Codable { + var int: Int + } + + let json = #"{"int":\#(int)}"# + + let testJSON = try JSON.Encoder.default.encodeString(TestValue(int: int)) + XCTAssertEqual(testJSON, json) + } + + func testEncodeUInt8() throws { + + let uint = UInt8.max + + struct TestValue: Codable { + var uint: UInt8 + } + + let json = #"{"uint":\#(uint)}"# + + let testJSON = try JSON.Encoder.default.encodeString(TestValue(uint: uint)) + XCTAssertEqual(testJSON, json) + } + + func testEncodeUInt16() throws { + + let uint = UInt16.max + + struct TestValue: Codable { + var uint: UInt16 + } + + let json = #"{"uint":\#(uint)}"# + + let testJSON = try JSON.Encoder.default.encodeString(TestValue(uint: uint)) + XCTAssertEqual(testJSON, json) + } + + func testEncodeUInt32() throws { + + let uint = UInt32.max + + struct TestValue: Codable { + var uint: UInt32 + } + + let json = #"{"uint":\#(uint)}"# + + let testJSON = try JSON.Encoder.default.encodeString(TestValue(uint: uint)) + XCTAssertEqual(testJSON, json) + } + + func testEncodeUInt64() throws { + + let uint = UInt64.max + + struct TestValue: Codable { + var uint: UInt64 + } + + let json = #"{"uint":\#(uint)}"# + + let testJSON = try JSON.Encoder.default.encodeString(TestValue(uint: uint)) + XCTAssertEqual(testJSON, json) + } + + func testEncodeUInt() throws { + + let uint = UInt.max + + struct TestValue: Codable { + var uint: UInt + } + + let json = #"{"uint":\#(uint)}"# + + let testJSON = try JSON.Encoder.default.encodeString(TestValue(uint: uint)) + XCTAssertEqual(testJSON, json) + } + + func testEncodeURL() throws { + + let url = URL(string: "https://example.com/some/thing")! + + struct TestValue: Codable { + var url: URL + } + + let json = #"{"url":"\#(url.absoluteString)"}"# + + let testJSON = try JSON.Encoder.default.encodeString(TestValue(url: url)) + XCTAssertEqual(testJSON, json) + } + + func testEncodeUUID() throws { + + let uuid = UUID() + + struct TestValue: Codable { + var uuid: UUID + } + + let json = #"{"uuid":"\#(uuid.uuidString)"}"# + + let testJSON = try JSON.Encoder.default.encodeString(TestValue(uuid: uuid)) + XCTAssertEqual(testJSON, json) + } + + func testEncodeNil() throws { + + struct TestValue: Codable { + var null: Int? = .none + + func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encodeNil(forKey: .null) + } + } + + let json = #"{"null":null}"# + + let testJSON = try JSON.Encoder.default.encodeString(TestValue(null: nil)) + XCTAssertEqual(testJSON, json) + } + + func testEncodeNonStringKeyedDictionary() throws { + + struct TestValue: Codable { + var dict: [Int: Int] + } + + XCTAssertNoThrow(try JSON.Encoder.default.encodeString(TestValue(dict: [1: 1]))) + } + + let prettyEncoder = { + let enc = JSON.Encoder() + enc.outputFormatting = .prettyPrinted + return enc + }() + +} diff --git a/Tests/JSONOrderTests.swift b/Tests/JSONOrderTests.swift new file mode 100644 index 000000000..1f593be51 --- /dev/null +++ b/Tests/JSONOrderTests.swift @@ -0,0 +1,73 @@ +// +// JSONOrderTests.swift +// PotentCodables +// +// Copyright © 2021 Outfox, inc. +// +// +// Distributed under the MIT License, See LICENSE for details. +// + +import BigInt +import Foundation +import PotentCodables +import PotentJSON +import XCTest + + +class JSONOrderTests: XCTestCase { + + func testObjectKeySerializationExplicitOrder() throws { + + let json = try JSONSerialization.string(from: [ + "c": 1, + "a": 2, + "b": 3, + ]) + + XCTAssertEqual(json, #"{"c":1,"a":2,"b":3}"#) + } + + func testObjectKeySerializationSortedOrder() throws { + + let json = try JSONSerialization.string(from: [ + "c": 1, + "a": 2, + "b": 3, + ], options: .sortedKeys) + + XCTAssertEqual(json, #"{"a":2,"b":3,"c":1}"#) + } + + func testObjectKeyDeserializationOrder() throws { + + let object: JSON = [ + "c": 1, + "a": 2, + "b": 3, + ] + + XCTAssertEqual(object, try JSONSerialization.json(from: #"{"c":1,"a":2,"b":3}"#)) + } + + func testDescriptionOrder() throws { + + let json: JSON = [ + "c": 1, + "a": 2, + "b": 3, + ] + + XCTAssertEqual( + json.description, + """ + { + "c" : 1, + "a" : 2, + "b" : 3 + } + """ + ) + } + +} diff --git a/Tests/JSONReaderTests.swift b/Tests/JSONReaderTests.swift new file mode 100644 index 000000000..e4d442eed --- /dev/null +++ b/Tests/JSONReaderTests.swift @@ -0,0 +1,212 @@ +// +// JSONReaderTests.swift +// PotentCodables +// +// Copyright © 2021 Outfox, inc. +// +// +// Distributed under the MIT License, See LICENSE for details. +// + +import Foundation +@testable import PotentJSON +import XCTest + + +class JSONReaderTests: XCTestCase { + + func testParsesEmptyObjects() throws { + + let json = "{}" + + XCTAssertEqual(try JSONSerialization.json(from: json.data(using: .utf8)!), .object([:])) + } + + func testParsesEmptyArrays() throws { + + let json = "[]" + + XCTAssertEqual(try JSONSerialization.json(from: json.data(using: .utf8)!), .array([])) + } + + func testReportsGoodPositionForObjectsMissingComma() throws { + + let json = + """ + { + "a": 1, + "b": 2, + "c": 3, + "d": 4 + "e": 5 + } + """ + + XCTAssertThrowsError(try JSONSerialization.json(from: json)) { error in + guard case JSONReader.Error.invalidData(let exp, let pos) = error else { + return XCTFail("expected invalidData error") + } + XCTAssertEqual(exp, .expectedObjectSeparator) + XCTAssertEqual(pos, 40) + } + } + + func testReportsGoodPositionForArraysMissingComma() throws { + + let json = + """ + [ + 1, + 2, + 3, + 4 + 5 + ] + """ + + XCTAssertThrowsError(try JSONSerialization.json(from: json)) { error in + guard case JSONReader.Error.invalidData(let exp, let pos) = error else { + return XCTFail("expected invalidData error") + } + XCTAssertEqual(exp, .expectedArraySeparator) + XCTAssertEqual(pos, 20) + } + } + + func testParsesScientificFloats() throws { + + let json = #"{"float":1.0e+3}"# + + let value = try JSONSerialization.json(from: json.data(using: .utf8)!) + XCTAssertEqual(value["float"]?.floatValue, 1000.0) + } + + func testParsingTooLargeScientificFloatsThrowsError() throws { + + let json = #"{"float":1.0e+400}"# + + XCTAssertThrowsError(try JSONSerialization.json(from: json.data(using: .utf8)!)) { error in + AssertJSONErrorInvalidData(error) + } + } + + func testParsingScientificFloatsWithInvalidCharsThrowsError() throws { + + let json = #"{"float":1.0e++4}"# + + XCTAssertThrowsError(try JSONSerialization.json(from: json.data(using: .utf8)!)) { error in + AssertJSONErrorInvalidData(error) + } + } + + func testUnescapesQuotes() throws { + + let json = #"{"bs":"\""}"# + + let value = try JSONSerialization.json(from: json.data(using: .utf8)!) + XCTAssertEqual(value["bs"]?.stringValue, #"""#) + } + + func testUnescapesForwardSlashes() throws { + + let json = #"{"url":"https:\/\/example.com\/some\/thing"}"# + + let value = try JSONSerialization.json(from: json.data(using: .utf8)!) + XCTAssertEqual(value["url"]?.stringValue, "https://example.com/some/thing") + } + + func testUnescapesBackwardSlashes() throws { + + let json = #"{"path":"c:\\a\\windows\\file"}"# + + let value = try JSONSerialization.json(from: json.data(using: .utf8)!) + XCTAssertEqual(value["path"]?.stringValue, #"c:\a\windows\file"#) + } + + func testUnescapesBackspace() throws { + + let json = #"{"bs":"\b"}"# + + let value = try JSONSerialization.json(from: json.data(using: .utf8)!) + XCTAssertEqual(value["bs"]?.stringValue, "\u{08}") + } + + func testUnescapesFormFeed() throws { + + let json = #"{"ff":"\f"}"# + + let value = try JSONSerialization.json(from: json.data(using: .utf8)!) + XCTAssertEqual(value["ff"]?.stringValue, "\u{0C}") + } + + func testUnescapesNewLine() throws { + + let json = #"{"nl":"\n"}"# + + let value = try JSONSerialization.json(from: json.data(using: .utf8)!) + XCTAssertEqual(value["nl"]?.stringValue, "\u{0A}") + } + + func testUnescapesCarriageReturn() throws { + + let json = #"{"cr":"\r"}"# + + let value = try JSONSerialization.json(from: json.data(using: .utf8)!) + XCTAssertEqual(value["cr"]?.stringValue, "\u{0D}") + } + + func testUnescapesTab() throws { + + let json = #"{"tab":"\t"}"# + + let value = try JSONSerialization.json(from: json.data(using: .utf8)!) + XCTAssertEqual(value["tab"]?.stringValue, "\u{09}") + } + + func testUnescapingInvalidCodeThrowsError() throws { + + let json = #"{"invalid":"\s"}"# + + XCTAssertThrowsError(try JSONSerialization.json(from: json.data(using: .utf8)!)) { error in + AssertJSONErrorInvalidData(error) + } + } + + func testUnescapesUnicode() throws { + + let json = #"{"uni":"15\u00B0C"}"# + + let value = try JSONSerialization.json(from: json.data(using: .utf8)!) + XCTAssertEqual(value["uni"]?.stringValue, "15\u{00B0}C") + } + + func testUnescapesUnicodeSurrogatePairs() throws { + + let json = #"{"uni":"\ud83c\udf09"}"# + + let value = try JSONSerialization.json(from: json.data(using: .utf8)!) + XCTAssertEqual(value["uni"]?.stringValue, "🌉") + } + + func testUnescapingInvalidUnicodeSurrogatePairsThrowsError() throws { + + let json = #"{"uni":"\ud83c\u00B0"}"# + + XCTAssertThrowsError(try JSONSerialization.json(from: json.data(using: .utf8)!)) { error in + AssertJSONErrorInvalidData(error) + } + + let json2 = #"{"uni":"\ud83c"}"# + + XCTAssertThrowsError(try JSONSerialization.json(from: json2.data(using: .utf8)!)) { error in + AssertJSONErrorInvalidData(error) + } + + let json3 = #"{"uni":"\ud"}"# + + XCTAssertThrowsError(try JSONSerialization.json(from: json3.data(using: .utf8)!)) { error in + AssertJSONErrorInvalidData(error) + } + } + +} diff --git a/Tests/JSONTests.swift b/Tests/JSONTests.swift deleted file mode 100644 index b6a5cbe17..000000000 --- a/Tests/JSONTests.swift +++ /dev/null @@ -1,184 +0,0 @@ -// -// JSONTests.swift -// PotentCodables -// -// Copyright © 2021 Outfox, inc. -// -// -// Distributed under the MIT License, See LICENSE for details. -// - -@testable import PotentCodables -@testable import PotentJSON -import XCTest - - -class JSONTests: XCTestCase { - - let json = """ - { - "a": 1, - "b": "2", - "c": [true, false, true], - "d": { - "a": 1, - "b": "2", - "c": [false, false, true], - "d": { - "a": 1, - "b": "2", - "c": [true, true, false] - } - }, - "e": [ - { - "a": 5, - "b": "6", - "c": [true] - }, - { - "a": 5, - "b": "6", - "c": [true] - } - ] - } - """ - - fileprivate let objects = TestValue( - a: 1, - b: "2", - c: [true, false, true], - d: TestValue( - a: 1, - b: "2", - c: [false, false, true], - d: TestValue( - a: 1, - b: "2", - c: [true, true, false] - ) - ), - e: [ - TestValue( - a: 5, - b: "6", - c: [true] - ), - TestValue( - a: 5, - b: "6", - c: [true] - ), - ] - ) - - let values: JSON = [ - "a": 1, - "b": "2", - "c": [true, false, true], - "d": [ - "a": 1, - "b": "2", - "c": [false, false, true], - "d": [ - "a": 1, - "b": "2", - "c": [true, true, false], - ], - ], - "e": [ - [ - "a": 5, - "b": "6", - "c": [true], - ], - [ - "a": 5, - "b": "6", - "c": [true], - ], - ], - ] - - func testDecodingChildContainers() throws { - _ = try JSONDecoder().decode(TestValue.self, from: json) - } - - func testEncodingChildContainers() throws { - let json = try JSONEncoder().encodeTree(objects).stableText - XCTAssertEqual(json, values.stableText) - } - - func testObjectKeySerializationExplicitOrder() throws { - - let json = try JSONSerialization.string(from: [ - "c": 1, - "a": 2, - "b": 3, - ]) - - XCTAssertEqual(json, #"{"c":1,"a":2,"b":3}"#) - } - - func testObjectKeySerializationSortedOrder() throws { - - let json = try JSONSerialization.string(from: [ - "c": 1, - "a": 2, - "b": 3, - ], options: .sortedKeys) - - XCTAssertEqual(json, #"{"a":2,"b":3,"c":1}"#) - } - - func testObjectKeyDeserializationOrder() throws { - - let object: JSON = [ - "c": 1, - "a": 2, - "b": 3, - ] - - XCTAssertEqual(object, try JSONSerialization.json(from: #"{"c":1,"a":2,"b":3}"#)) - } - - func testDescriptionOrder() throws { - - let json: JSON = [ - "c": 1, - "a": 2, - "b": 3, - ] - - XCTAssertEqual( - json.description, - """ - { - "c" : 1, - "a" : 2, - "b" : 3 - } - """ - ) - } - -} - - -private class TestValue: Codable { - let a: Int - let b: String - let c: [Bool] - let d: TestValue? - let e: [TestValue]? - - init(a: Int, b: String, c: [Bool], d: TestValue? = nil, e: [TestValue]? = nil) { - self.a = a - self.b = b - self.c = c - self.d = d - self.e = e - } - -} diff --git a/Tests/JSONValueTests.swift b/Tests/JSONValueTests.swift new file mode 100644 index 000000000..f0fedbfb6 --- /dev/null +++ b/Tests/JSONValueTests.swift @@ -0,0 +1,155 @@ +// +// JSONValueTests.swift +// PotentCodables +// +// Copyright © 2021 Outfox, inc. +// +// +// Distributed under the MIT License, See LICENSE for details. +// + + +import BigInt +import Foundation +import PotentCodables +import PotentJSON +import XCTest + + +class JSONValueTests: XCTestCase { + + func testNilLiteralInitializer() { + let json: JSON = nil + XCTAssertTrue(json.isNull) + } + + func testBoolLiteralInitializer() { + let json: JSON = true + XCTAssertEqual(json.boolValue, true) + } + + func testStringLiteralInitializer() { + let json: JSON = "Hello World!" + XCTAssertEqual(json.stringValue, "Hello World!") + } + + func testFloatLiteralInitializer() { + let json: JSON = 1.234 + XCTAssertEqual(json.doubleValue, 1.234) + } + + func testArrayLiteralInitializer() { + let json: JSON = [1, 2, 3] + XCTAssertEqual(json.arrayValue, [1, 2, 3]) + } + + func testDictionaryLiteralInitializer() { + let json: JSON = ["b": 1, "a": 2, "c": 3] + XCTAssertEqual(json.objectValue, ["b": 1, "a": 2, "c": 3]) + } + + func testJSONNumberStringLiteralInitializer() { + let number: JSON.Number = "1.234" + XCTAssertEqual(number.doubleValue, 1.234) + } + + func testJSONNumberFloatLiteralInitializer() { + let number: JSON.Number = 1.234 + XCTAssertEqual(number.doubleValue, 1.234) + } + + func testJSONNumberIntegerLiteralInitializer() { + let number: JSON.Number = 1234 + XCTAssertEqual(number.integerValue, 1234) + } + + func testDynamicMemberSubscript() { + let object: JSON = [ + "aaa": 1, + ] + XCTAssertEqual(object.aaa, .number(1)) + XCTAssertEqual(object.bbb, nil) + XCTAssertEqual(JSON.null.aaa, nil) + } + + func testIntSubscript() { + let array: JSON = [ + "a", "b", "c" + ] + XCTAssertEqual(array[1], .string("b")) + XCTAssertEqual(array[3], nil) + XCTAssertEqual(JSON.null[1], nil) + } + + func testStringSubscript() { + let object: JSON = [ + "aaa": 1, + ] + XCTAssertEqual(object["aaa"], .number(1)) + XCTAssertEqual(object["bbb"], nil) + XCTAssertEqual(JSON.null["aaa"], nil) + } + + func testNullAccessor() { + XCTAssertTrue(JSON.null.isNull) + XCTAssertFalse(JSON.number(5).isNull) + } + + func testStringAccessor() { + XCTAssertEqual(JSON.string("Hello World!").stringValue, "Hello World!") + XCTAssertNil(JSON.number(5).stringValue) + } + + func testBoolAccessor() { + XCTAssertEqual(JSON.bool(true).boolValue, true) + XCTAssertNil(JSON.number(5).boolValue) + } + + func testNumberAccessor() { + XCTAssertEqual(JSON.number(5).numberValue as? Int, 5) + XCTAssertNil(JSON.bool(false).numberValue) + } + + func testIntegerAccessor() { + XCTAssertEqual(JSON.number(5).integerValue, 5) + XCTAssertEqual(JSON.number(-5).integerValue, -5) + XCTAssertNil(JSON.number(5.3).integerValue) + XCTAssertNil(JSON.bool(false).integerValue) + } + + func testUnsignedIntegerAccessor() { + XCTAssertEqual(JSON.number(5).unsignedIntegerValue, 5) + XCTAssertNil(JSON.number(5.3).unsignedIntegerValue) + XCTAssertNil(JSON.bool(false).unsignedIntegerValue) + } + + func testFloatAccessor() { + XCTAssertEqual(JSON.number(5.3).floatValue, 5.3) + XCTAssertNil(JSON.bool(false).floatValue) + } + + func testDoubleAccessor() { + XCTAssertEqual(JSON.number(5.3).doubleValue, 5.3) + XCTAssertNil(JSON.bool(false).doubleValue) + } + + func testArrayAccessor() { + XCTAssertEqual(JSON.array([1, 2, 3]).arrayValue, [1, 2, 3] as JSON.Array) + XCTAssertNil(JSON.bool(false).arrayValue) + } + + func testObjectAccessor() { + XCTAssertEqual(JSON.object(["a": 1, "b": 2, "c": 3]).objectValue, ["a": 1, "b": 2, "c": 3] as JSON.Object) + XCTAssertNil(JSON.bool(false).objectValue) + } + + func testUnwrap() { + XCTAssertNil(JSON.null.unwrapped) + XCTAssertEqual(JSON.bool(true).unwrapped as? Bool, true) + XCTAssertEqual(JSON.string("Hello World!").unwrapped as? String, "Hello World!") + XCTAssertEqual(JSON.number(5).unwrapped as? Int, 5) + XCTAssertEqual(JSON.array([1, 2, 3]).unwrapped as? [Int], [1, 2, 3]) + XCTAssertEqual(JSON.object(["a": 1, "b": 2, "c": 3]).unwrapped as? [String: Int], ["a": 1, "b": 2, "c": 3]) + } + +} diff --git a/Tests/JSONWriterTests.swift b/Tests/JSONWriterTests.swift new file mode 100644 index 000000000..1b30c6616 --- /dev/null +++ b/Tests/JSONWriterTests.swift @@ -0,0 +1,81 @@ +// +// JSONWriterTests.swift +// PotentCodables +// +// Copyright © 2021 Outfox, inc. +// +// +// Distributed under the MIT License, See LICENSE for details. +// + +import Foundation +@testable import PotentJSON +import XCTest + + +class JSONWriterTests: XCTestCase { + + func testEscapesQuotes() throws { + + let json = try JSONSerialization.string(from: .string(#""test""#), options: [.escapeSlashes]) + XCTAssertEqual(json, #""\"test\"""#) + } + + func testEscapesForwardSlashes() throws { + + let json = try JSONSerialization.string(from: .string("http://example.com/some/thing"), options: [.escapeSlashes]) + XCTAssertEqual(json, #""http:\/\/example.com\/some\/thing""#) + } + + func testEscapesBackwardSlashes() throws { + + let json = try JSONSerialization.string(from: .string(#"c:\a\file"#), options: [.escapeSlashes]) + XCTAssertEqual(json, #""c:\\a\\file""#) + } + + func testEscapesBackspace() throws { + + let json = try JSONSerialization.string(from: .string("\u{08}"), options: [.escapeSlashes]) + XCTAssertEqual(json, #""\b""#) + } + + func testEscapesFormFeed() throws { + + let json = try JSONSerialization.string(from: .string("\u{0c}"), options: [.escapeSlashes]) + XCTAssertEqual(json, #""\f""#) + } + + func testEscapesNewLine() throws { + + let json = try JSONSerialization.string(from: .string("\n"), options: [.escapeSlashes]) + XCTAssertEqual(json, #""\n""#) + } + + func testEscapesCarriageReturn() throws { + + let json = try JSONSerialization.string(from: .string("\r"), options: [.escapeSlashes]) + XCTAssertEqual(json, #""\r""#) + } + + func testEscapesTab() throws { + + let json = try JSONSerialization.string(from: .string("\t"), options: [.escapeSlashes]) + XCTAssertEqual(json, #""\t""#) + } + + func testEscapesUnicodeFormatInLongForm() throws { + + let json = try JSONSerialization.string(from: .string("\u{0}"), options: [.escapeSlashes]) + XCTAssertEqual(json, #""\u0000""#) + + let json2 = try JSONSerialization.string(from: .string("\u{10}"), options: [.escapeSlashes]) + XCTAssertEqual(json2, #""\u0010""#) + } + + func testRewritesIntegerFloats() throws { + + let json = try JSONSerialization.string(from: .number("100.0"), options: [.escapeSlashes]) + XCTAssertEqual(json, #"100"#) + } + +} diff --git a/Tests/RefTests.swift b/Tests/RefTests.swift index 94e5065c6..9501a4eb0 100644 --- a/Tests/RefTests.swift +++ b/Tests/RefTests.swift @@ -31,7 +31,7 @@ struct BValue: RefTestValue, Codable { class RefTests: XCTestCase { override class func setUp() { - DefaultTypeIndex.setAllowedTypes([AValue.self, BValue.self]) + DefaultTypeIndex.addAllowedTypes([AValue.self, BValue.self]) } func testRef() throws { @@ -84,8 +84,6 @@ class RefTests: XCTestCase { func testCustomEmbeddedRef() throws { - - struct MyTypeKey: TypeKeyProvider { static var typeKey: AnyCodingKey = "_type" } @@ -103,4 +101,239 @@ class RefTests: XCTestCase { XCTAssertEqual(src.name, try ref.as(AValue.self).name) } + func testRefUnkeyed() throws { + + let json = try JSON.Encoder.default.encodeTree(Ref.Value([1, 2, 3])) + XCTAssertNotNil(json["@type"]) + XCTAssertEqual(json["value"], .array([1, 2, 3])) + + let ref = try JSONDecoder.default.decodeTree(Ref.self, from: json) + + XCTAssertEqual(try ref.as([Int].self), [1, 2, 3]) + } + + func testRefSingleBool() throws { + + let json = try JSON.Encoder.default.encodeTree(Ref.Value(true)) + XCTAssertNotNil(json["@type"]) + XCTAssertEqual(json["value"], .bool(true)) + + let ref = try JSONDecoder.default.decodeTree(Ref.self, from: json) + + XCTAssertEqual(try ref.as(Bool.self), true) + } + + func testRefSingleInt() throws { + + let json = try JSON.Encoder.default.encodeTree(Ref.Value(1)) + XCTAssertNotNil(json["@type"]) + XCTAssertEqual(json["value"], .number(1)) + + let ref = try JSONDecoder.default.decodeTree(Ref.self, from: json) + + XCTAssertEqual(try ref.as(Int.self), 1) + } + + func testRefSingleInt8() throws { + + let json = try JSON.Encoder.default.encodeTree(Ref.Value(Int8(1))) + XCTAssertNotNil(json["@type"]) + XCTAssertEqual(json["value"], .number(1)) + + let ref = try JSONDecoder.default.decodeTree(Ref.self, from: json) + + XCTAssertEqual(try ref.as(Int8.self), 1) + } + + func testRefSingleInt16() throws { + + let json = try JSON.Encoder.default.encodeTree(Ref.Value(Int16(1))) + XCTAssertNotNil(json["@type"]) + XCTAssertEqual(json["value"], .number(1)) + + let ref = try JSONDecoder.default.decodeTree(Ref.self, from: json) + + XCTAssertEqual(try ref.as(Int16.self), 1) + } + + func testRefSingleInt32() throws { + + let json = try JSON.Encoder.default.encodeTree(Ref.Value(Int32(1))) + XCTAssertNotNil(json["@type"]) + XCTAssertEqual(json["value"], .number(1)) + + let ref = try JSONDecoder.default.decodeTree(Ref.self, from: json) + + XCTAssertEqual(try ref.as(Int32.self), 1) + } + + func testRefSingleInt64() throws { + + let json = try JSON.Encoder.default.encodeTree(Ref.Value(Int64(1))) + XCTAssertNotNil(json["@type"]) + XCTAssertEqual(json["value"], .number(1)) + + let ref = try JSONDecoder.default.decodeTree(Ref.self, from: json) + + XCTAssertEqual(try ref.as(Int64.self), 1) + } + + func testRefSingleUInt() throws { + + let json = try JSON.Encoder.default.encodeTree(Ref.Value(UInt(1))) + XCTAssertNotNil(json["@type"]) + XCTAssertEqual(json["value"], .number(1)) + + let ref = try JSONDecoder.default.decodeTree(Ref.self, from: json) + + XCTAssertEqual(try ref.as(UInt.self), 1) + } + + func testRefSingleUInt8() throws { + + let json = try JSON.Encoder.default.encodeTree(Ref.Value(UInt8(1))) + XCTAssertNotNil(json["@type"]) + XCTAssertEqual(json["value"], .number(1)) + + let ref = try JSONDecoder.default.decodeTree(Ref.self, from: json) + + XCTAssertEqual(try ref.as(UInt8.self), 1) + } + + func testRefSingleUInt16() throws { + + let json = try JSON.Encoder.default.encodeTree(Ref.Value(UInt16(1))) + XCTAssertNotNil(json["@type"]) + XCTAssertEqual(json["value"], .number(1)) + + let ref = try JSONDecoder.default.decodeTree(Ref.self, from: json) + + XCTAssertEqual(try ref.as(UInt16.self), 1) + } + + func testRefSingleUInt32() throws { + + let json = try JSON.Encoder.default.encodeTree(Ref.Value(UInt32(1))) + XCTAssertNotNil(json["@type"]) + XCTAssertEqual(json["value"], .number(1)) + + let ref = try JSONDecoder.default.decodeTree(Ref.self, from: json) + + XCTAssertEqual(try ref.as(UInt32.self), 1) + } + + func testRefSingleUInt64() throws { + + let json = try JSON.Encoder.default.encodeTree(Ref.Value(UInt64(1))) + XCTAssertNotNil(json["@type"]) + XCTAssertEqual(json["value"], .number(1)) + + let ref = try JSONDecoder.default.decodeTree(Ref.self, from: json) + + XCTAssertEqual(try ref.as(UInt64.self), 1) + } + + func testRefSingleString() throws { + + let json = try JSON.Encoder.default.encodeTree(Ref.Value("test")) + XCTAssertNotNil(json["@type"]) + XCTAssertEqual(json["value"], .string("test")) + + let ref = try JSONDecoder.default.decodeTree(Ref.self, from: json) + + XCTAssertEqual(try ref.as(String.self), "test") + } + + func testRefSingleFloat16() throws { + + let json = try JSON.Encoder.default.encodeTree(Ref.Value(Float16(1.0))) + XCTAssertNotNil(json["@type"]) + XCTAssertEqual(json["value"], .number(1.0)) + + let ref = try JSONDecoder.default.decodeTree(Ref.self, from: json) + + XCTAssertEqual(try ref.as(Float16.self), 1) + } + + func testRefSingleFloat() throws { + + let json = try JSON.Encoder.default.encodeTree(Ref.Value(Float(1.0))) + XCTAssertNotNil(json["@type"]) + XCTAssertEqual(json["value"], .number(1.0)) + + let ref = try JSONDecoder.default.decodeTree(Ref.self, from: json) + + XCTAssertEqual(try ref.as(Float.self), 1) + } + + func testRefSingleDouble() throws { + + let json = try JSON.Encoder.default.encodeTree(Ref.Value(Double(1.0))) + XCTAssertNotNil(json["@type"]) + XCTAssertEqual(json["value"], .number(1.0)) + + let ref = try JSONDecoder.default.decodeTree(Ref.self, from: json) + + XCTAssertEqual(try ref.as(Double.self), 1) + } + + func testRefSingleNil() throws { + + let json = try JSON.Encoder.default.encodeTree(Ref.Value(nil as Bool?)) + XCTAssertNotNil(json["@type"]) + XCTAssertEqual(json["value"], .null) + + let ref = try JSONDecoder.default.decodeTree(Ref.self, from: json) + + XCTAssertEqual(try ref.as(Bool?.self), nil) + } + + func testRefSingleBoolArray() throws { + + let json = try JSON.Encoder.default.encodeTree(Ref.Value([true, false])) + XCTAssertNotNil(json["@type"]) + XCTAssertEqual(json["value"], .array([true, false])) + + let ref = try JSONDecoder.default.decodeTree(Ref.self, from: json) + + XCTAssertEqual(try ref.as([Bool].self), [true, false]) + } + + func testRefSingleIntArray() throws { + + let json = try JSON.Encoder.default.encodeTree(Ref.Value([1, 0])) + XCTAssertNotNil(json["@type"]) + XCTAssertEqual(json["value"], .array([1, 0])) + + let ref = try JSONDecoder.default.decodeTree(Ref.self, from: json) + + XCTAssertEqual(try ref.as([Int].self), [1, 0]) + } + + func testRefDecodeFailsWhenTypeNotAuthorized() throws { + + struct TestValue: Codable { + var value: Bool + } + + let json = try JSON.Encoder.default.encodeTree(Ref.Value(TestValue(value: true))) + XCTAssertNotNil(json["@type"]) + XCTAssertEqual(json["value"], .object(["value": true])) + + XCTAssertThrowsError(try JSONDecoder.default.decodeTree(Ref.self, from: json)) + } + + func testEmbeddedRefDecodeFailsWhenTypeNotAuthorized() throws { + + struct TestValue: Codable { + var value: Bool + } + + let json = try JSON.Encoder.default.encodeTree(EmbeddedRef.Value(TestValue(value: true))) + XCTAssertNotNil(json["@type"]) + XCTAssertEqual(json["value"], .bool(true)) + + XCTAssertThrowsError(try JSONDecoder.default.decodeTree(EmbeddedRef.self, from: json)) + } + } diff --git a/Tests/ValueCoderTests.swift b/Tests/ValueCoderTests.swift new file mode 100644 index 000000000..99a48089f --- /dev/null +++ b/Tests/ValueCoderTests.swift @@ -0,0 +1,1135 @@ +// +// ValueCoderTests.swift +// PotentCodables +// +// Copyright © 2021 Outfox, inc. +// +// +// Distributed under the MIT License, See LICENSE for details. +// + +import Foundation +import PotentCodables +import PotentJSON +import XCTest + + +class ValueCoderTests: XCTestCase { + + func testUnkeyedContainerDecodeFromEmptyNil() throws { + + struct TestValue: Codable { + init(from decoder: Decoder) throws { + var decoder = try decoder.unkeyedContainer() + _ = try decoder.decodeNil() + } + + func encode(to encoder: Encoder) throws { + } + } + + XCTAssertThrowsError(try JSON.Decoder.default.decodeTree(TestValue.self, from: .array([]))) { error in + AssertDecodingValueNotFound(error) + } + } + + func testUnkeyedContainerDecodeFromEmptyBool() throws { + + struct TestValue: Codable { + init(from decoder: Decoder) throws { + var decoder = try decoder.unkeyedContainer() + _ = try decoder.decode(Bool.self) + } + + func encode(to encoder: Encoder) throws { + } + } + + XCTAssertThrowsError(try JSON.Decoder.default.decodeTree(TestValue.self, from: .array([]))) { error in + AssertDecodingValueNotFound(error) + } + } + + func testUnkeyedContainerDecoderNilAsBool() throws { + + struct TestValue: Codable { + init(from decoder: Decoder) throws { + var decoder = try decoder.unkeyedContainer() + _ = try decoder.decode(Bool.self) + } + + func encode(to encoder: Encoder) throws { + } + } + + XCTAssertThrowsError(try JSON.Decoder.default.decodeTree(TestValue.self, from: .array([nil]))) { error in + AssertDecodingValueNotFound(error) + } + } + + func testUnkeyedContainerDecodeFromEmptyInt() throws { + + struct TestValue: Codable { + init(from decoder: Decoder) throws { + var decoder = try decoder.unkeyedContainer() + _ = try decoder.decode(Int.self) + } + + func encode(to encoder: Encoder) throws { + } + } + + XCTAssertThrowsError(try JSON.Decoder.default.decodeTree(TestValue.self, from: .array([]))) { error in + AssertDecodingValueNotFound(error) + } + } + + func testUnkeyedContainerDecoderNilAsInt() throws { + + struct TestValue: Codable { + init(from decoder: Decoder) throws { + var decoder = try decoder.unkeyedContainer() + _ = try decoder.decode(Int.self) + } + + func encode(to encoder: Encoder) throws { + } + } + + XCTAssertThrowsError(try JSON.Decoder.default.decodeTree(TestValue.self, from: .array([nil]))) { error in + AssertDecodingValueNotFound(error) + } + } + + func testUnkeyedContainerDecodeFromEmptyInt8() throws { + + struct TestValue: Codable { + init(from decoder: Decoder) throws { + var decoder = try decoder.unkeyedContainer() + _ = try decoder.decode(Int8.self) + } + + func encode(to encoder: Encoder) throws { + } + } + + XCTAssertThrowsError(try JSON.Decoder.default.decodeTree(TestValue.self, from: .array([]))) { error in + AssertDecodingValueNotFound(error) + } + } + + func testUnkeyedContainerDecoderNilAsInt8() throws { + + struct TestValue: Codable { + init(from decoder: Decoder) throws { + var decoder = try decoder.unkeyedContainer() + _ = try decoder.decode(Int8.self) + } + + func encode(to encoder: Encoder) throws { + } + } + + XCTAssertThrowsError(try JSON.Decoder.default.decodeTree(TestValue.self, from: .array([nil]))) { error in + AssertDecodingValueNotFound(error) + } + } + + func testUnkeyedContainerDecodeFromEmptyInt16() throws { + + struct TestValue: Codable { + init(from decoder: Decoder) throws { + var decoder = try decoder.unkeyedContainer() + _ = try decoder.decode(Int16.self) + } + + func encode(to encoder: Encoder) throws { + } + } + + XCTAssertThrowsError(try JSON.Decoder.default.decodeTree(TestValue.self, from: .array([]))) { error in + AssertDecodingValueNotFound(error) + } + } + + func testUnkeyedContainerDecoderNilAsInt16() throws { + + struct TestValue: Codable { + init(from decoder: Decoder) throws { + var decoder = try decoder.unkeyedContainer() + _ = try decoder.decode(Int16.self) + } + + func encode(to encoder: Encoder) throws { + } + } + + XCTAssertThrowsError(try JSON.Decoder.default.decodeTree(TestValue.self, from: .array([nil]))) { error in + AssertDecodingValueNotFound(error) + } + } + + func testUnkeyedContainerDecodeFromEmptyInt32() throws { + + struct TestValue: Codable { + init(from decoder: Decoder) throws { + var decoder = try decoder.unkeyedContainer() + _ = try decoder.decode(Int32.self) + } + + func encode(to encoder: Encoder) throws { + } + } + + XCTAssertThrowsError(try JSON.Decoder.default.decodeTree(TestValue.self, from: .array([]))) { error in + AssertDecodingValueNotFound(error) + } + } + + func testUnkeyedContainerDecoderNilAsInt32() throws { + + struct TestValue: Codable { + init(from decoder: Decoder) throws { + var decoder = try decoder.unkeyedContainer() + _ = try decoder.decode(Int32.self) + } + + func encode(to encoder: Encoder) throws { + } + } + + XCTAssertThrowsError(try JSON.Decoder.default.decodeTree(TestValue.self, from: .array([nil]))) { error in + AssertDecodingValueNotFound(error) + } + } + + func testUnkeyedContainerDecodeFromEmptyInt64() throws { + + struct TestValue: Codable { + init(from decoder: Decoder) throws { + var decoder = try decoder.unkeyedContainer() + _ = try decoder.decode(Int64.self) + } + + func encode(to encoder: Encoder) throws { + } + } + + XCTAssertThrowsError(try JSON.Decoder.default.decodeTree(TestValue.self, from: .array([]))) { error in + AssertDecodingValueNotFound(error) + } + } + + func testUnkeyedContainerDecoderNilAsInt64() throws { + + struct TestValue: Codable { + init(from decoder: Decoder) throws { + var decoder = try decoder.unkeyedContainer() + _ = try decoder.decode(Int64.self) + } + + func encode(to encoder: Encoder) throws { + } + } + + XCTAssertThrowsError(try JSON.Decoder.default.decodeTree(TestValue.self, from: .array([nil]))) { error in + AssertDecodingValueNotFound(error) + } + } + + func testUnkeyedContainerDecodeFromEmptyUInt() throws { + + struct TestValue: Codable { + init(from decoder: Decoder) throws { + var decoder = try decoder.unkeyedContainer() + _ = try decoder.decode(UInt.self) + } + + func encode(to encoder: Encoder) throws { + } + } + + XCTAssertThrowsError(try JSON.Decoder.default.decodeTree(TestValue.self, from: .array([]))) { error in + AssertDecodingValueNotFound(error) + } + } + + func testUnkeyedContainerDecoderNilAsUInt() throws { + + struct TestValue: Codable { + init(from decoder: Decoder) throws { + var decoder = try decoder.unkeyedContainer() + _ = try decoder.decode(UInt.self) + } + + func encode(to encoder: Encoder) throws { + } + } + + XCTAssertThrowsError(try JSON.Decoder.default.decodeTree(TestValue.self, from: .array([nil]))) { error in + AssertDecodingValueNotFound(error) + } + } + + func testUnkeyedContainerDecodeFromEmptyUInt8() throws { + + struct TestValue: Codable { + init(from decoder: Decoder) throws { + var decoder = try decoder.unkeyedContainer() + _ = try decoder.decode(UInt8.self) + } + + func encode(to encoder: Encoder) throws { + } + } + + XCTAssertThrowsError(try JSON.Decoder.default.decodeTree(TestValue.self, from: .array([]))) { error in + AssertDecodingValueNotFound(error) + } + } + + func testUnkeyedContainerDecoderNilAsUInt8() throws { + + struct TestValue: Codable { + init(from decoder: Decoder) throws { + var decoder = try decoder.unkeyedContainer() + _ = try decoder.decode(UInt8.self) + } + + func encode(to encoder: Encoder) throws { + } + } + + XCTAssertThrowsError(try JSON.Decoder.default.decodeTree(TestValue.self, from: .array([nil]))) { error in + AssertDecodingValueNotFound(error) + } + } + + func testUnkeyedContainerDecodeFromEmptyUInt16() throws { + + struct TestValue: Codable { + init(from decoder: Decoder) throws { + var decoder = try decoder.unkeyedContainer() + _ = try decoder.decode(UInt16.self) + } + + func encode(to encoder: Encoder) throws { + } + } + + XCTAssertThrowsError(try JSON.Decoder.default.decodeTree(TestValue.self, from: .array([]))) { error in + AssertDecodingValueNotFound(error) + } + } + + func testUnkeyedContainerDecoderNilAsUInt16() throws { + + struct TestValue: Codable { + init(from decoder: Decoder) throws { + var decoder = try decoder.unkeyedContainer() + _ = try decoder.decode(UInt16.self) + } + + func encode(to encoder: Encoder) throws { + } + } + + XCTAssertThrowsError(try JSON.Decoder.default.decodeTree(TestValue.self, from: .array([nil]))) { error in + AssertDecodingValueNotFound(error) + } + } + + func testUnkeyedContainerDecodeFromEmptyUInt32() throws { + + struct TestValue: Codable { + init(from decoder: Decoder) throws { + var decoder = try decoder.unkeyedContainer() + _ = try decoder.decode(UInt32.self) + } + + func encode(to encoder: Encoder) throws { + } + } + + XCTAssertThrowsError(try JSON.Decoder.default.decodeTree(TestValue.self, from: .array([]))) { error in + AssertDecodingValueNotFound(error) + } + } + + func testUnkeyedContainerDecoderNilAsUInt32() throws { + + struct TestValue: Codable { + init(from decoder: Decoder) throws { + var decoder = try decoder.unkeyedContainer() + _ = try decoder.decode(UInt32.self) + } + + func encode(to encoder: Encoder) throws { + } + } + + XCTAssertThrowsError(try JSON.Decoder.default.decodeTree(TestValue.self, from: .array([nil]))) { error in + AssertDecodingValueNotFound(error) + } + } + + func testUnkeyedContainerDecodeFromEmptyUInt64() throws { + + struct TestValue: Codable { + init(from decoder: Decoder) throws { + var decoder = try decoder.unkeyedContainer() + _ = try decoder.decode(UInt64.self) + } + + func encode(to encoder: Encoder) throws { + } + } + + XCTAssertThrowsError(try JSON.Decoder.default.decodeTree(TestValue.self, from: .array([]))) { error in + AssertDecodingValueNotFound(error) + } + } + + func testUnkeyedContainerDecoderNilAsUInt64() throws { + + struct TestValue: Codable { + init(from decoder: Decoder) throws { + var decoder = try decoder.unkeyedContainer() + _ = try decoder.decode(UInt64.self) + } + + func encode(to encoder: Encoder) throws { + } + } + + XCTAssertThrowsError(try JSON.Decoder.default.decodeTree(TestValue.self, from: .array([nil]))) { error in + AssertDecodingValueNotFound(error) + } + } + + func testUnkeyedContainerDecodeFromEmptyFloat() throws { + + struct TestValue: Codable { + init(from decoder: Decoder) throws { + var decoder = try decoder.unkeyedContainer() + _ = try decoder.decode(Float.self) + } + + func encode(to encoder: Encoder) throws { + } + } + + XCTAssertThrowsError(try JSON.Decoder.default.decodeTree(TestValue.self, from: .array([]))) { error in + AssertDecodingValueNotFound(error) + } + } + + func testUnkeyedContainerDecoderNilAsFloat() throws { + + struct TestValue: Codable { + init(from decoder: Decoder) throws { + var decoder = try decoder.unkeyedContainer() + _ = try decoder.decode(Float.self) + } + + func encode(to encoder: Encoder) throws { + } + } + + XCTAssertThrowsError(try JSON.Decoder.default.decodeTree(TestValue.self, from: .array([nil]))) { error in + AssertDecodingValueNotFound(error) + } + } + + func testUnkeyedContainerDecodeFromEmptyDouble() throws { + + struct TestValue: Codable { + init(from decoder: Decoder) throws { + var decoder = try decoder.unkeyedContainer() + _ = try decoder.decode(Double.self) + } + + func encode(to encoder: Encoder) throws { + } + } + + XCTAssertThrowsError(try JSON.Decoder.default.decodeTree(TestValue.self, from: .array([]))) { error in + AssertDecodingValueNotFound(error) + } + } + + func testUnkeyedContainerDecoderNilAsDouble() throws { + + struct TestValue: Codable { + init(from decoder: Decoder) throws { + var decoder = try decoder.unkeyedContainer() + _ = try decoder.decode(Double.self) + } + + func encode(to encoder: Encoder) throws { + } + } + + XCTAssertThrowsError(try JSON.Decoder.default.decodeTree(TestValue.self, from: .array([nil]))) { error in + AssertDecodingValueNotFound(error) + } + } + + func testUnkeyedContainerDecodeFromEmptyString() throws { + + struct TestValue: Codable { + init(from decoder: Decoder) throws { + var decoder = try decoder.unkeyedContainer() + _ = try decoder.decode(String.self) + } + + func encode(to encoder: Encoder) throws { + } + } + + XCTAssertThrowsError(try JSON.Decoder.default.decodeTree(TestValue.self, from: .array([]))) { error in + AssertDecodingValueNotFound(error) + } + } + + func testUnkeyedContainerDecoderNilAsString() throws { + + struct TestValue: Codable { + init(from decoder: Decoder) throws { + var decoder = try decoder.unkeyedContainer() + _ = try decoder.decode(String.self) + } + + func encode(to encoder: Encoder) throws { + } + } + + XCTAssertThrowsError(try JSON.Decoder.default.decodeTree(TestValue.self, from: .array([nil]))) { error in + AssertDecodingValueNotFound(error) + } + } + + func testUnkeyedContainerDecodeFromEmptyCodable() throws { + + struct Sub: Codable { + var bool: Bool + } + + struct TestValue: Codable { + init(from decoder: Decoder) throws { + var decoder = try decoder.unkeyedContainer() + _ = try decoder.decode(Sub.self) + } + + func encode(to encoder: Encoder) throws { + } + } + + XCTAssertThrowsError(try JSON.Decoder.default.decodeTree(TestValue.self, from: .array([]))) { error in + AssertDecodingValueNotFound(error) + } + } + + func testUnkeyedContainerDecoderNilAsCodable() throws { + + struct Sub: Codable { + var bool: Bool + } + + struct TestValue: Codable { + init(from decoder: Decoder) throws { + var decoder = try decoder.unkeyedContainer() + _ = try decoder.decode(Sub.self) + } + + func encode(to encoder: Encoder) throws { + } + } + + XCTAssertThrowsError(try JSON.Decoder.default.decodeTree(TestValue.self, from: .array([nil]))) { error in + AssertDecodingValueNotFound(error) + } + } + + func testUnkeyedContainerDecodeFromEmptyNestedKeyed() throws { + + struct TestValue: Codable { + init(from decoder: Decoder) throws { + var decoder = try decoder.unkeyedContainer() + _ = try decoder.nestedContainer(keyedBy: AnyCodingKey.self) + } + + func encode(to encoder: Encoder) throws { + } + } + + XCTAssertThrowsError(try JSON.Decoder.default.decodeTree(TestValue.self, from: .array([]))) { error in + AssertDecodingValueNotFound(error) + } + } + + func testUnkeyedContainerDecoderNilAsNestedKeyed() throws { + + struct TestValue: Codable { + init(from decoder: Decoder) throws { + var decoder = try decoder.unkeyedContainer() + _ = try decoder.nestedContainer(keyedBy: AnyCodingKey.self) + } + + func encode(to encoder: Encoder) throws { + } + } + + XCTAssertThrowsError(try JSON.Decoder.default.decodeTree(TestValue.self, from: .array([nil]))) { error in + AssertDecodingValueNotFound(error) + } + } + + func testUnkeyedContainerDecodeFromEmptyNestedUnkeyed() throws { + + struct TestValue: Codable { + init(from decoder: Decoder) throws { + var decoder = try decoder.unkeyedContainer() + _ = try decoder.nestedUnkeyedContainer() + } + + func encode(to encoder: Encoder) throws { + } + } + + XCTAssertThrowsError(try JSON.Decoder.default.decodeTree(TestValue.self, from: .array([]))) { error in + AssertDecodingValueNotFound(error) + } + } + + func testUnkeyedContainerDecoderNilAsNestedUnkeyed() throws { + + struct TestValue: Codable { + init(from decoder: Decoder) throws { + var decoder = try decoder.unkeyedContainer() + _ = try decoder.nestedUnkeyedContainer() + } + + func encode(to encoder: Encoder) throws { + } + } + + XCTAssertThrowsError(try JSON.Decoder.default.decodeTree(TestValue.self, from: .array([nil]))) { error in + AssertDecodingValueNotFound(error) + } + } + + func testUnkeyedContainerDecodeFromEmptyNestedSuper() throws { + + struct TestValue: Codable { + init(from decoder: Decoder) throws { + var decoder = try decoder.unkeyedContainer() + _ = try decoder.superDecoder() + } + + func encode(to encoder: Encoder) throws { + } + } + + XCTAssertThrowsError(try JSON.Decoder.default.decodeTree(TestValue.self, from: .array([]))) { error in + AssertDecodingValueNotFound(error) + } + } + + func testKeyedContainerDecoderNilAsNestedKeyed() throws { + + struct TestValue: Codable { + enum CodingKeys: CodingKey { + case nested + } + init(from decoder: Decoder) throws { + let decoder = try decoder.container(keyedBy: CodingKeys.self) + _ = try decoder.nestedContainer(keyedBy: AnyCodingKey.self, forKey: .nested) + } + func encode(to encoder: Encoder) throws { + } + } + + XCTAssertThrowsError(try JSON.Decoder.default.decodeTree(TestValue.self, from: .object([:]))) { error in + AssertDecodingKeyNotFound(error) + } + } + + func testKeyedContainerDecoderNilAsNestedUnkeyed() throws { + + struct TestValue: Codable { + enum CodingKeys: CodingKey { + case nested + } + init(from decoder: Decoder) throws { + let decoder = try decoder.container(keyedBy: CodingKeys.self) + _ = try decoder.nestedUnkeyedContainer(forKey: .nested) + } + func encode(to encoder: Encoder) throws { + } + } + + XCTAssertThrowsError(try JSON.Decoder.default.decodeTree(TestValue.self, from: .object([:]))) { error in + AssertDecodingKeyNotFound(error) + } + } + + func testUnkeyedContainer() throws { + + struct TestValue: Codable, Equatable { + var bool: Bool + var string: String + var int: Int + var int8: Int8 + var int16: Int16 + var int32: Int32 + var int64: Int64 + var uint: UInt + var uint8: UInt8 + var uint16: UInt16 + var uint32: UInt32 + var uint64: UInt64 + var float: Float + var double: Double + + + init() { + self.bool = true + self.string = "test" + self.int = 1 + self.int8 = 2 + self.int16 = 3 + self.int32 = 4 + self.int64 = 5 + self.uint = 6 + self.uint8 = 7 + self.uint16 = 8 + self.uint32 = 9 + self.uint64 = 10 + self.float = 11.0 + self.double = 12.0 + } + + init(from decoder: Decoder) throws { + var container = try decoder.unkeyedContainer() + _ = try container.decodeNil() + bool = try container.decode(Bool.self) + string = try container.decode(String.self) + int = try container.decode(Int.self) + int8 = try container.decode(Int8.self) + int16 = try container.decode(Int16.self) + int32 = try container.decode(Int32.self) + int64 = try container.decode(Int64.self) + uint = try container.decode(UInt.self) + uint8 = try container.decode(UInt8.self) + uint16 = try container.decode(UInt16.self) + uint32 = try container.decode(UInt32.self) + uint64 = try container.decode(UInt64.self) + float = try container.decode(Float.self) + double = try container.decode(Double.self) + } + + func encode(to encoder: Encoder) throws { + var container = encoder.unkeyedContainer() + try container.encodeNil() + try container.encode(bool) + try container.encode(string) + try container.encode(int) + try container.encode(int8) + try container.encode(int16) + try container.encode(int32) + try container.encode(int64) + try container.encode(uint) + try container.encode(uint8) + try container.encode(uint16) + try container.encode(uint32) + try container.encode(uint64) + try container.encode(float) + try container.encode(double) + } + } + + let value = TestValue() + let tree: JSON = [ + nil, + true, + "test", + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8, + 9, + 10, + 11.0, + 12.0, + ] + + XCTAssertEqual(try JSON.Encoder.default.encodeTree(value), tree) + XCTAssertEqual(try JSON.Decoder.default.decodeTree(TestValue.self, from: tree), value) + } + + func testSuperContainerForKeyedContainer() throws { + + class BaseValue: Codable { + + var id: String + + init(id: String) { + self.id = id + } + + enum CodingKeys: CodingKey { + case id + } + + required init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + self.id = try container.decode(String.self, forKey: .id) + } + + func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(self.id, forKey: .id) + } + + } + + class TestValue: BaseValue { + + var firstName: String + var lastName: String + + init(id: String, firstName: String, lastName: String) { + self.firstName = firstName + self.lastName = lastName + super.init(id: id) + } + + enum CodingKeys: CodingKey { + case firstName + case lastName + } + + required init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + firstName = try container.decode(String.self, forKey: .firstName) + lastName = try container.decode(String.self, forKey: .lastName) + try super.init(from: try container.superDecoder()) + } + + override func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(firstName, forKey: .firstName) + try container.encode(lastName, forKey: .lastName) + try super.encode(to: container.superEncoder()) + } + + } + + let value = TestValue(id: "1", firstName: "Some", lastName: "Guy") + let tree = JSON.object([ + "firstName": "Some", + "lastName": "Guy", + "super": [ + "id": "1", + ] + ]) + + XCTAssertEqual(try JSON.Encoder.default.encodeTree(value), tree) + + let testValue = try JSON.Decoder.default.decodeTree(TestValue.self, from: tree) + XCTAssertEqual(testValue.id, value.id) + XCTAssertEqual(testValue.firstName, value.firstName) + XCTAssertEqual(testValue.lastName, value.lastName) + } + + func testSuperContainerForKeyedContainerWithCustomKey() throws { + + class BaseValue: Codable { + + var id: String + + init(id: String) { + self.id = id + } + + enum CodingKeys: CodingKey { + case id + } + + required init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + self.id = try container.decode(String.self, forKey: .id) + } + + func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(self.id, forKey: .id) + } + + } + + class TestValue: BaseValue { + + var firstName: String + var lastName: String + + init(id: String, firstName: String, lastName: String) { + self.firstName = firstName + self.lastName = lastName + super.init(id: id) + } + + enum CodingKeys: CodingKey { + case firstName + case lastName + case base + } + + required init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + firstName = try container.decode(String.self, forKey: .firstName) + lastName = try container.decode(String.self, forKey: .lastName) + try super.init(from: try container.superDecoder(forKey: .base)) + } + + override func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(firstName, forKey: .firstName) + try container.encode(lastName, forKey: .lastName) + try super.encode(to: container.superEncoder(forKey: .base)) + } + + } + + let value = TestValue(id: "1", firstName: "Some", lastName: "Guy") + let tree = JSON.object([ + "firstName": "Some", + "lastName": "Guy", + "base": [ + "id": "1", + ] + ]) + + XCTAssertEqual(try JSON.Encoder.default.encodeTree(value), tree) + + let testValue = try JSON.Decoder.default.decodeTree(TestValue.self, from: tree) + XCTAssertEqual(testValue.id, value.id) + XCTAssertEqual(testValue.firstName, value.firstName) + XCTAssertEqual(testValue.lastName, value.lastName) + } + + func testSuperContainerForUnkeyedContainer() throws { + + class BaseValue: Codable { + + var id: String + + init(id: String) { + self.id = id + } + + enum CodingKeys: CodingKey { + case id + } + + required init(from decoder: Decoder) throws { + var container = try decoder.unkeyedContainer() + self.id = try container.decode(String.self) + } + + func encode(to encoder: Encoder) throws { + var container = encoder.unkeyedContainer() + try container.encode(self.id) + } + + } + + class TestValue: BaseValue { + + var firstName: String + var lastName: String + + init(id: String, firstName: String, lastName: String) { + self.firstName = firstName + self.lastName = lastName + super.init(id: id) + } + + enum CodingKeys: CodingKey { + case firstName + case lastName + } + + required init(from decoder: Decoder) throws { + var container = try decoder.unkeyedContainer() + firstName = try container.decode(String.self) + lastName = try container.decode(String.self) + try super.init(from: try container.superDecoder()) + } + + override func encode(to encoder: Encoder) throws { + var container = encoder.unkeyedContainer() + try container.encode(firstName) + try container.encode(lastName) + try super.encode(to: container.superEncoder()) + } + + } + + let value = TestValue(id: "1", firstName: "Some", lastName: "Guy") + let tree = JSON.array([ + "Some", + "Guy", + [ + "1", + ] + ]) + + XCTAssertEqual(try JSON.Encoder.default.encodeTree(value), tree) + + let testValue = try JSON.Decoder.default.decodeTree(TestValue.self, from: tree) + XCTAssertEqual(testValue.id, value.id) + XCTAssertEqual(testValue.firstName, value.firstName) + XCTAssertEqual(testValue.lastName, value.lastName) + } + + func testNestedContainersForKeyedContainer() { + + struct TestValue: Codable, Equatable { + + var id: String + var names: [String] + var socialNames: [String] + + init(id: String, names: [String], socialNames: [String]) { + self.id = id + self.names = names + self.socialNames = socialNames + } + + enum CodingKeys: CodingKey { + case id + case name + case socialNames + } + + enum NameCodingKeys: CodingKey { + case first + case last + } + + init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + self.id = try container.decode(String.self, forKey: .id) + + self.names = [] + let nameContainer = try container.nestedContainer(keyedBy: NameCodingKeys.self, forKey: .name) + self.names.append(try nameContainer.decode(String.self, forKey: .first)) + self.names.append(try nameContainer.decode(String.self, forKey: .last)) + + self.socialNames = [] + var socialNamesContainer = try container.nestedUnkeyedContainer(forKey: .socialNames) + while !socialNamesContainer.isAtEnd { + self.socialNames.append(try socialNamesContainer.decode(String.self)) + } + } + + func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(self.id, forKey: .id) + + var nameContainer = container.nestedContainer(keyedBy: NameCodingKeys.self, forKey: .name) + try nameContainer.encode(names.first!, forKey: .first) + try nameContainer.encode(names.last!, forKey: .last) + + var socialNamesContainer = container.nestedUnkeyedContainer(forKey: .socialNames) + try socialNamesContainer.encode(contentsOf: socialNames) + } + } + + let value = TestValue(id: "1", names: ["Some", "Guy"], socialNames: ["@some_guy"]) + let tree = JSON.object([ + "id": "1", + "name": .object([ + "first": "Some", + "last": "Guy", + ]), + "socialNames": .array([ + "@some_guy" + ]), + ]) + + XCTAssertEqual(try JSON.Encoder.default.encodeTree(value), tree) + XCTAssertEqual(try JSON.Decoder.default.decodeTree(TestValue.self, from: tree), value) + } + + func testNestedContainersForUnkeyedContainer() { + + struct TestValue: Codable, Equatable { + + var id: String + var names: [String] + var socialNames: [String] + + init(id: String, names: [String], socialNames: [String]) { + self.id = id + self.names = names + self.socialNames = socialNames + } + + enum NameCodingKeys: CodingKey { + case first + case last + } + + init(from decoder: Decoder) throws { + var container = try decoder.unkeyedContainer() + self.id = try container.decode(String.self) + + self.names = [] + let nameContainer = try container.nestedContainer(keyedBy: NameCodingKeys.self) + self.names.append(try nameContainer.decode(String.self, forKey: .first)) + self.names.append(try nameContainer.decode(String.self, forKey: .last)) + + self.socialNames = [] + var socialNamesContainer = try container.nestedUnkeyedContainer() + while !socialNamesContainer.isAtEnd { + self.socialNames.append(try socialNamesContainer.decode(String.self)) + } + } + + func encode(to encoder: Encoder) throws { + var container = encoder.unkeyedContainer() + try container.encode(self.id) + + var nameContainer = container.nestedContainer(keyedBy: NameCodingKeys.self) + try nameContainer.encode(names.first!, forKey: .first) + try nameContainer.encode(names.last!, forKey: .last) + + var socialNamesContainer = container.nestedUnkeyedContainer() + try socialNamesContainer.encode(contentsOf: socialNames) + } + } + + let value = TestValue(id: "1", names: ["Some", "Guy"], socialNames: ["@some_guy"]) + let tree = JSON.array([ + "1", + .object([ + "first": "Some", + "last": "Guy", + ]), + .array([ + "@some_guy" + ]), + ]) + + XCTAssertEqual(try JSON.Encoder.default.encodeTree(value), tree) + XCTAssertEqual(try JSON.Decoder.default.decodeTree(TestValue.self, from: tree), value) + } + +} diff --git a/Tests/ValueTransformerTests.swift b/Tests/ValueTransformerTests.swift index 3c5a34c2e..35a5739c1 100644 --- a/Tests/ValueTransformerTests.swift +++ b/Tests/ValueTransformerTests.swift @@ -12,54 +12,1545 @@ @testable import PotentJSON import XCTest -struct IntegerBoolTransformer: ValueCodingTransformer { - func encode(_ value: Bool) throws -> Int { - return value ? 1 : 0 +class ValueTransformerTests: XCTestCase { + + func testKeyed() throws { + + let tree: JSON = [ + "trueValueBool": true, + "falseValueBool": false, + "trueValueInt": 1, + "falseValueInt": 0, + "trueValueInt8": 1, + "falseValueInt8": 0, + "trueValueInt16": 1, + "falseValueInt16": 0, + "trueValueInt32": 1, + "falseValueInt32": 0, + "trueValueInt64": 1, + "falseValueInt64": 0, + "trueValueUInt": 1, + "falseValueUInt": 0, + "trueValueUInt8": 1, + "falseValueUInt8": 0, + "trueValueUInt16": 1, + "falseValueUInt16": 0, + "trueValueUInt32": 1, + "falseValueUInt32": 0, + "trueValueUInt64": 1, + "falseValueUInt64": 0, + "trueValueFloat": 1.0, + "falseValueFloat": 0.0, + "trueValueDouble": 1.0, + "falseValueDouble": 0.0, + "trueValueString": "true", + "falseValueString": "false", + "trueValueStruct": true, + "falseValueStruct": false, + "someValueBool": true, + "someValueInt": 1, + "someValueInt8": 1, + "someValueInt16": 1, + "someValueInt32": 1, + "someValueInt64": 1, + "someValueUInt": 1, + "someValueUInt8": 1, + "someValueUInt16": 1, + "someValueUInt32": 1, + "someValueUInt64": 1, + "someValueFloat": 1.0, + "someValueDouble": 1.0, + "someValueString": "true", + "someValueStruct": true, + ] + + let value = TestKeyed() + + XCTAssertEqual( + try JSON.Encoder.default.encodeTree(value), + tree + ) + + XCTAssertEqual( + try JSON.Decoder.default.decodeTree(TestKeyed.self, from: tree), + value + ) } - func decode(_ value: Int) throws -> Bool { - return value != 0 + func testUnkeyed() throws { + + let tree: JSON = [ + true, false, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1.0, 0.0, 1.0, 0.0, "true", "false", + true, nil, 1, nil, 1, nil, 1, nil, 1, nil, 1, nil, 1, nil, 1, nil, 1, nil, 1, nil, 1, nil, 1.0, nil, 1.0, nil, "true", nil, + ] + + let value = TestUnkeyed() + + XCTAssertEqual( + try JSON.Encoder.default.encodeTree(value), + tree + ) + + XCTAssertEqual( + try JSON.Decoder.default.decodeTree(TestUnkeyed.self, from: tree), + value + ) } -} + func testUnkeyedCollectionBoolStoredInt() throws { + + struct TestValue: Codable, Equatable { + var value: [Int] + + init(value: [Int]) { + self.value = value + } + + init(from decoder: Decoder) throws { + var container = try decoder.unkeyedContainer() + value = [] + while !container.isAtEnd { + value.append(try container.decode(using: IntToBool())) + } + } + + func encode(to encoder: Encoder) throws { + var contaier = encoder.unkeyedContainer() + try contaier.encode(contentsOf: value, using: IntToBool()) + } + } + + XCTAssertEqual(try JSON.Encoder.default.encodeTree(TestValue(value: [1, 0])), JSON.array([true, false])) + XCTAssertEqual(try JSON.Decoder.default.decodeTree(TestValue.self, from: JSON.array([true, false])), TestValue(value: [1, 0])) + } + + func testUnkeyedCollectionIntStoredBool() throws { + + struct TestValue: Codable, Equatable { + var value: [Bool] + + init(value: [Bool]) { + self.value = value + } + + init(from decoder: Decoder) throws { + var container = try decoder.unkeyedContainer() + value = [] + while !container.isAtEnd { + value.append(try container.decode(using: BoolToInt())) + } + } + + func encode(to encoder: Encoder) throws { + var contaier = encoder.unkeyedContainer() + try contaier.encode(contentsOf: value, using: BoolToInt()) + } + } + + XCTAssertEqual(try JSON.Encoder.default.encodeTree(TestValue(value: [true, false])), JSON.array([1, 0])) + XCTAssertEqual(try JSON.Decoder.default.decodeTree(TestValue.self, from: JSON.array([1, 0])), TestValue(value: [true, false])) + } + + func testUnkeyedCollectionInt8StoredBool() throws { + + struct TestValue: Codable, Equatable { + var value: [Bool] + + init(value: [Bool]) { + self.value = value + } + + init(from decoder: Decoder) throws { + var container = try decoder.unkeyedContainer() + value = [] + while !container.isAtEnd { + value.append(try container.decode(using: BoolToInt())) + } + } + + func encode(to encoder: Encoder) throws { + var contaier = encoder.unkeyedContainer() + try contaier.encode(contentsOf: value, using: BoolToInt()) + } + } + + XCTAssertEqual(try JSON.Encoder.default.encodeTree(TestValue(value: [true, false])), JSON.array([1, 0])) + XCTAssertEqual(try JSON.Decoder.default.decodeTree(TestValue.self, from: JSON.array([1, 0])), TestValue(value: [true, false])) + } + + func testUnkeyedCollectionInt16StoredBool() throws { + + struct TestValue: Codable, Equatable { + var value: [Bool] + + init(value: [Bool]) { + self.value = value + } + + init(from decoder: Decoder) throws { + var container = try decoder.unkeyedContainer() + value = [] + while !container.isAtEnd { + value.append(try container.decode(using: BoolToInt())) + } + } + + func encode(to encoder: Encoder) throws { + var contaier = encoder.unkeyedContainer() + try contaier.encode(contentsOf: value, using: BoolToInt()) + } + } + + XCTAssertEqual(try JSON.Encoder.default.encodeTree(TestValue(value: [true, false])), JSON.array([1, 0])) + XCTAssertEqual(try JSON.Decoder.default.decodeTree(TestValue.self, from: JSON.array([1, 0])), TestValue(value: [true, false])) + } + + func testUnkeyedCollectionInt32StoredBool() throws { + + struct TestValue: Codable, Equatable { + var value: [Bool] + + init(value: [Bool]) { + self.value = value + } + + init(from decoder: Decoder) throws { + var container = try decoder.unkeyedContainer() + value = [] + while !container.isAtEnd { + value.append(try container.decode(using: BoolToInt())) + } + } + + func encode(to encoder: Encoder) throws { + var contaier = encoder.unkeyedContainer() + try contaier.encode(contentsOf: value, using: BoolToInt()) + } + } + + XCTAssertEqual(try JSON.Encoder.default.encodeTree(TestValue(value: [true, false])), JSON.array([1, 0])) + XCTAssertEqual(try JSON.Decoder.default.decodeTree(TestValue.self, from: JSON.array([1, 0])), TestValue(value: [true, false])) + } + + func testUnkeyedCollectionInt64StoredBool() throws { + + struct TestValue: Codable, Equatable { + var value: [Bool] + + init(value: [Bool]) { + self.value = value + } + + init(from decoder: Decoder) throws { + var container = try decoder.unkeyedContainer() + value = [] + while !container.isAtEnd { + value.append(try container.decode(using: BoolToInt())) + } + } + + func encode(to encoder: Encoder) throws { + var contaier = encoder.unkeyedContainer() + try contaier.encode(contentsOf: value, using: BoolToInt()) + } + } + + XCTAssertEqual(try JSON.Encoder.default.encodeTree(TestValue(value: [true, false])), JSON.array([1, 0])) + XCTAssertEqual(try JSON.Decoder.default.decodeTree(TestValue.self, from: JSON.array([1, 0])), TestValue(value: [true, false])) + } + + func testUnkeyedCollectionUIntStoredBool() throws { + + struct TestValue: Codable, Equatable { + var value: [Bool] + + init(value: [Bool]) { + self.value = value + } + + init(from decoder: Decoder) throws { + var container = try decoder.unkeyedContainer() + value = [] + while !container.isAtEnd { + value.append(try container.decode(using: BoolToInt())) + } + } + + func encode(to encoder: Encoder) throws { + var contaier = encoder.unkeyedContainer() + try contaier.encode(contentsOf: value, using: BoolToInt()) + } + } + + XCTAssertEqual(try JSON.Encoder.default.encodeTree(TestValue(value: [true, false])), JSON.array([1, 0])) + XCTAssertEqual(try JSON.Decoder.default.decodeTree(TestValue.self, from: JSON.array([1, 0])), TestValue(value: [true, false])) + } + + func testUnkeyedCollectionUInt8StoredBool() throws { + + struct TestValue: Codable, Equatable { + var value: [Bool] + + init(value: [Bool]) { + self.value = value + } + + init(from decoder: Decoder) throws { + var container = try decoder.unkeyedContainer() + value = [] + while !container.isAtEnd { + value.append(try container.decode(using: BoolToInt())) + } + } + + func encode(to encoder: Encoder) throws { + var contaier = encoder.unkeyedContainer() + try contaier.encode(contentsOf: value, using: BoolToInt()) + } + } + + XCTAssertEqual(try JSON.Encoder.default.encodeTree(TestValue(value: [true, false])), JSON.array([1, 0])) + XCTAssertEqual(try JSON.Decoder.default.decodeTree(TestValue.self, from: JSON.array([1, 0])), TestValue(value: [true, false])) + } + + func testUnkeyedCollectionUInt16StoredBool() throws { + + struct TestValue: Codable, Equatable { + var value: [Bool] + + init(value: [Bool]) { + self.value = value + } + + init(from decoder: Decoder) throws { + var container = try decoder.unkeyedContainer() + value = [] + while !container.isAtEnd { + value.append(try container.decode(using: BoolToInt())) + } + } + + func encode(to encoder: Encoder) throws { + var contaier = encoder.unkeyedContainer() + try contaier.encode(contentsOf: value, using: BoolToInt()) + } + } + + XCTAssertEqual(try JSON.Encoder.default.encodeTree(TestValue(value: [true, false])), JSON.array([1, 0])) + XCTAssertEqual(try JSON.Decoder.default.decodeTree(TestValue.self, from: JSON.array([1, 0])), TestValue(value: [true, false])) + } + + func testUnkeyedCollectionUInt32StoredBool() throws { + + struct TestValue: Codable, Equatable { + var value: [Bool] + + init(value: [Bool]) { + self.value = value + } + + init(from decoder: Decoder) throws { + var container = try decoder.unkeyedContainer() + value = [] + while !container.isAtEnd { + value.append(try container.decode(using: BoolToInt())) + } + } + + func encode(to encoder: Encoder) throws { + var contaier = encoder.unkeyedContainer() + try contaier.encode(contentsOf: value, using: BoolToInt()) + } + } + + XCTAssertEqual(try JSON.Encoder.default.encodeTree(TestValue(value: [true, false])), JSON.array([1, 0])) + XCTAssertEqual(try JSON.Decoder.default.decodeTree(TestValue.self, from: JSON.array([1, 0])), TestValue(value: [true, false])) + } + + func testUnkeyedCollectionUInt64StoredBool() throws { + + struct TestValue: Codable, Equatable { + var value: [Bool] + + init(value: [Bool]) { + self.value = value + } + + init(from decoder: Decoder) throws { + var container = try decoder.unkeyedContainer() + value = [] + while !container.isAtEnd { + value.append(try container.decode(using: BoolToInt())) + } + } + + func encode(to encoder: Encoder) throws { + var contaier = encoder.unkeyedContainer() + try contaier.encode(contentsOf: value, using: BoolToInt()) + } + } + + XCTAssertEqual(try JSON.Encoder.default.encodeTree(TestValue(value: [true, false])), JSON.array([1, 0])) + XCTAssertEqual(try JSON.Decoder.default.decodeTree(TestValue.self, from: JSON.array([1, 0])), TestValue(value: [true, false])) + } + + func testUnkeyedCollectionFloatStoredBool() throws { + + struct TestValue: Codable, Equatable { + var value: [Bool] + + init(value: [Bool]) { + self.value = value + } + + init(from decoder: Decoder) throws { + var container = try decoder.unkeyedContainer() + value = [] + while !container.isAtEnd { + value.append(try container.decode(using: BoolToFloat())) + } + } + + func encode(to encoder: Encoder) throws { + var contaier = encoder.unkeyedContainer() + try contaier.encode(contentsOf: value, using: BoolToFloat()) + } + } + + XCTAssertEqual(try JSON.Encoder.default.encodeTree(TestValue(value: [true, false])), JSON.array([1.0, 0.0])) + XCTAssertEqual(try JSON.Decoder.default.decodeTree(TestValue.self, from: JSON.array([1.0, 0.0])), TestValue(value: [true, false])) + } + + func testUnkeyedCollectionDoubleStoredBool() throws { + + struct TestValue: Codable, Equatable { + var value: [Bool] + + init(value: [Bool]) { + self.value = value + } + + init(from decoder: Decoder) throws { + var container = try decoder.unkeyedContainer() + value = [] + while !container.isAtEnd { + value.append(try container.decode(using: BoolToFloat())) + } + } + + func encode(to encoder: Encoder) throws { + var contaier = encoder.unkeyedContainer() + try contaier.encode(contentsOf: value, using: BoolToFloat()) + } + } + + XCTAssertEqual(try JSON.Encoder.default.encodeTree(TestValue(value: [true, false])), JSON.array([1.0, 0.0])) + XCTAssertEqual(try JSON.Decoder.default.decodeTree(TestValue.self, from: JSON.array([1.0, 0.0])), TestValue(value: [true, false])) + } + + func testUnkeyedCollectionStringStoredBool() throws { + + struct TestValue: Codable, Equatable { + var value: [Bool] + + init(value: [Bool]) { + self.value = value + } + + init(from decoder: Decoder) throws { + var container = try decoder.unkeyedContainer() + value = [] + while !container.isAtEnd { + value.append(try container.decode(using: BoolToString())) + } + } + + func encode(to encoder: Encoder) throws { + var contaier = encoder.unkeyedContainer() + try contaier.encode(contentsOf: value, using: BoolToString()) + } + } + + XCTAssertEqual(try JSON.Encoder.default.encodeTree(TestValue(value: [true, false])), JSON.array(["true", "false"])) + XCTAssertEqual(try JSON.Decoder.default.decodeTree(TestValue.self, from: JSON.array(["true", "false"])), TestValue(value: [true, false])) + } + + func testUnkeyedCollectionBoolValueStoredBool() throws { + + struct TestValue: Codable, Equatable { + var value: [Bool] + + init(value: [Bool]) { + self.value = value + } + + init(from decoder: Decoder) throws { + var container = try decoder.unkeyedContainer() + value = [] + while !container.isAtEnd { + value.append(try container.decode(using: BoolToBoolValue())) + } + } + + func encode(to encoder: Encoder) throws { + var contaier = encoder.unkeyedContainer() + try contaier.encode(contentsOf: value, using: BoolToBoolValue()) + } + } + + XCTAssertEqual(try JSON.Encoder.default.encodeTree(TestValue(value: [true, false])), JSON.array([true, false])) + XCTAssertEqual(try JSON.Decoder.default.decodeTree(TestValue.self, from: JSON.array([true, false])), TestValue(value: [true, false])) + } + + func testSingleBoolStoredInt() throws { + + struct TestValue: Codable, Equatable { + var value: Int + + init(value: Int) { + self.value = value + } + + init(from decoder: Decoder) throws { + var container = try decoder.singleValueContainer() + value = try container.decode(using: IntToBool()) + } + + func encode(to encoder: Encoder) throws { + var contaier = encoder.singleValueContainer() + try contaier.encode(value, using: IntToBool()) + } + } + + XCTAssertEqual(try JSON.Encoder.default.encodeTree(TestValue(value: 1)), JSON.bool(true)) + XCTAssertEqual(try JSON.Encoder.default.encodeTree(TestValue(value: 0)), JSON.bool(false)) + XCTAssertEqual(try JSON.Decoder.default.decodeTree(TestValue.self, from: JSON.bool(true)), TestValue(value: 1)) + XCTAssertEqual(try JSON.Decoder.default.decodeTree(TestValue.self, from: JSON.bool(false)), TestValue(value: 0)) + } + + func testSingleIntStoredBool() throws { + + struct TestValue: Codable, Equatable { + var value: Bool + + init(value: Bool) { + self.value = value + } + + init(from decoder: Decoder) throws { + var container = try decoder.singleValueContainer() + value = try container.decode(using: BoolToInt()) + } + + func encode(to encoder: Encoder) throws { + var contaier = encoder.singleValueContainer() + try contaier.encode(value, using: BoolToInt()) + } + } + + XCTAssertEqual(try JSON.Encoder.default.encodeTree(TestValue(value: true)), JSON.number(1)) + XCTAssertEqual(try JSON.Encoder.default.encodeTree(TestValue(value: false)), JSON.number(0)) + XCTAssertEqual(try JSON.Decoder.default.decodeTree(TestValue.self, from: JSON.number(1)), TestValue(value: true)) + XCTAssertEqual(try JSON.Decoder.default.decodeTree(TestValue.self, from: JSON.number(0)), TestValue(value: false)) + } + + func testSingleInt8StoredBool() throws { + + struct TestValue: Codable, Equatable { + var value: Bool + + init(value: Bool) { + self.value = value + } + + init(from decoder: Decoder) throws { + var container = try decoder.singleValueContainer() + value = try container.decode(using: BoolToInt()) + } + + func encode(to encoder: Encoder) throws { + var contaier = encoder.singleValueContainer() + try contaier.encode(value, using: BoolToInt()) + } + } + + XCTAssertEqual(try JSON.Encoder.default.encodeTree(TestValue(value: true)), JSON.number(1)) + XCTAssertEqual(try JSON.Encoder.default.encodeTree(TestValue(value: false)), JSON.number(0)) + XCTAssertEqual(try JSON.Decoder.default.decodeTree(TestValue.self, from: JSON.number(1)), TestValue(value: true)) + XCTAssertEqual(try JSON.Decoder.default.decodeTree(TestValue.self, from: JSON.number(0)), TestValue(value: false)) + } + + func testSingleInt16StoredBool() throws { + + struct TestValue: Codable, Equatable { + var value: Bool + + init(value: Bool) { + self.value = value + } + + init(from decoder: Decoder) throws { + var container = try decoder.singleValueContainer() + value = try container.decode(using: BoolToInt()) + } + + func encode(to encoder: Encoder) throws { + var contaier = encoder.singleValueContainer() + try contaier.encode(value, using: BoolToInt()) + } + } + + XCTAssertEqual(try JSON.Encoder.default.encodeTree(TestValue(value: true)), JSON.number(1)) + XCTAssertEqual(try JSON.Encoder.default.encodeTree(TestValue(value: false)), JSON.number(0)) + XCTAssertEqual(try JSON.Decoder.default.decodeTree(TestValue.self, from: JSON.number(1)), TestValue(value: true)) + XCTAssertEqual(try JSON.Decoder.default.decodeTree(TestValue.self, from: JSON.number(0)), TestValue(value: false)) + } + + func testSingleInt32StoredBool() throws { + + struct TestValue: Codable, Equatable { + var value: Bool + + init(value: Bool) { + self.value = value + } + + init(from decoder: Decoder) throws { + var container = try decoder.singleValueContainer() + value = try container.decode(using: BoolToInt()) + } + + func encode(to encoder: Encoder) throws { + var contaier = encoder.singleValueContainer() + try contaier.encode(value, using: BoolToInt()) + } + } + + XCTAssertEqual(try JSON.Encoder.default.encodeTree(TestValue(value: true)), JSON.number(1)) + XCTAssertEqual(try JSON.Encoder.default.encodeTree(TestValue(value: false)), JSON.number(0)) + XCTAssertEqual(try JSON.Decoder.default.decodeTree(TestValue.self, from: JSON.number(1)), TestValue(value: true)) + XCTAssertEqual(try JSON.Decoder.default.decodeTree(TestValue.self, from: JSON.number(0)), TestValue(value: false)) + } + + func testSingleInt64StoredBool() throws { + + struct TestValue: Codable, Equatable { + var value: Bool + + init(value: Bool) { + self.value = value + } + + init(from decoder: Decoder) throws { + var container = try decoder.singleValueContainer() + value = try container.decode(using: BoolToInt()) + } + + func encode(to encoder: Encoder) throws { + var contaier = encoder.singleValueContainer() + try contaier.encode(value, using: BoolToInt()) + } + } + + XCTAssertEqual(try JSON.Encoder.default.encodeTree(TestValue(value: true)), JSON.number(1)) + XCTAssertEqual(try JSON.Encoder.default.encodeTree(TestValue(value: false)), JSON.number(0)) + XCTAssertEqual(try JSON.Decoder.default.decodeTree(TestValue.self, from: JSON.number(1)), TestValue(value: true)) + XCTAssertEqual(try JSON.Decoder.default.decodeTree(TestValue.self, from: JSON.number(0)), TestValue(value: false)) + } + + func testSingleUIntStoredBool() throws { + + struct TestValue: Codable, Equatable { + var value: Bool + + init(value: Bool) { + self.value = value + } + + init(from decoder: Decoder) throws { + var container = try decoder.singleValueContainer() + value = try container.decode(using: BoolToInt()) + } + + func encode(to encoder: Encoder) throws { + var contaier = encoder.singleValueContainer() + try contaier.encode(value, using: BoolToInt()) + } + } + + XCTAssertEqual(try JSON.Encoder.default.encodeTree(TestValue(value: true)), JSON.number(1)) + XCTAssertEqual(try JSON.Encoder.default.encodeTree(TestValue(value: false)), JSON.number(0)) + XCTAssertEqual(try JSON.Decoder.default.decodeTree(TestValue.self, from: JSON.number(1)), TestValue(value: true)) + XCTAssertEqual(try JSON.Decoder.default.decodeTree(TestValue.self, from: JSON.number(0)), TestValue(value: false)) + } + + func testSingleUInt8StoredBool() throws { + + struct TestValue: Codable, Equatable { + var value: Bool + + init(value: Bool) { + self.value = value + } + + init(from decoder: Decoder) throws { + var container = try decoder.singleValueContainer() + value = try container.decode(using: BoolToInt()) + } + + func encode(to encoder: Encoder) throws { + var contaier = encoder.singleValueContainer() + try contaier.encode(value, using: BoolToInt()) + } + } + + XCTAssertEqual(try JSON.Encoder.default.encodeTree(TestValue(value: true)), JSON.number(1)) + XCTAssertEqual(try JSON.Encoder.default.encodeTree(TestValue(value: false)), JSON.number(0)) + XCTAssertEqual(try JSON.Decoder.default.decodeTree(TestValue.self, from: JSON.number(1)), TestValue(value: true)) + XCTAssertEqual(try JSON.Decoder.default.decodeTree(TestValue.self, from: JSON.number(0)), TestValue(value: false)) + } + + func testSingleUInt16StoredBool() throws { + + struct TestValue: Codable, Equatable { + var value: Bool + + init(value: Bool) { + self.value = value + } + + init(from decoder: Decoder) throws { + var container = try decoder.singleValueContainer() + value = try container.decode(using: BoolToInt()) + } + + func encode(to encoder: Encoder) throws { + var contaier = encoder.singleValueContainer() + try contaier.encode(value, using: BoolToInt()) + } + } -struct Test { + XCTAssertEqual(try JSON.Encoder.default.encodeTree(TestValue(value: true)), JSON.number(1)) + XCTAssertEqual(try JSON.Encoder.default.encodeTree(TestValue(value: false)), JSON.number(0)) + XCTAssertEqual(try JSON.Decoder.default.decodeTree(TestValue.self, from: JSON.number(1)), TestValue(value: true)) + XCTAssertEqual(try JSON.Decoder.default.decodeTree(TestValue.self, from: JSON.number(0)), TestValue(value: false)) + } + + func testSingleUInt32StoredBool() throws { + + struct TestValue: Codable, Equatable { + var value: Bool + + init(value: Bool) { + self.value = value + } + + init(from decoder: Decoder) throws { + var container = try decoder.singleValueContainer() + value = try container.decode(using: BoolToInt()) + } + + func encode(to encoder: Encoder) throws { + var contaier = encoder.singleValueContainer() + try contaier.encode(value, using: BoolToInt()) + } + } + + XCTAssertEqual(try JSON.Encoder.default.encodeTree(TestValue(value: true)), JSON.number(1)) + XCTAssertEqual(try JSON.Encoder.default.encodeTree(TestValue(value: false)), JSON.number(0)) + XCTAssertEqual(try JSON.Decoder.default.decodeTree(TestValue.self, from: JSON.number(1)), TestValue(value: true)) + XCTAssertEqual(try JSON.Decoder.default.decodeTree(TestValue.self, from: JSON.number(0)), TestValue(value: false)) + } + + func testSingleUInt64StoredBool() throws { + + struct TestValue: Codable, Equatable { + var value: Bool + + init(value: Bool) { + self.value = value + } + + init(from decoder: Decoder) throws { + var container = try decoder.singleValueContainer() + value = try container.decode(using: BoolToInt()) + } + + func encode(to encoder: Encoder) throws { + var contaier = encoder.singleValueContainer() + try contaier.encode(value, using: BoolToInt()) + } + } + + XCTAssertEqual(try JSON.Encoder.default.encodeTree(TestValue(value: true)), JSON.number(1)) + XCTAssertEqual(try JSON.Encoder.default.encodeTree(TestValue(value: false)), JSON.number(0)) + XCTAssertEqual(try JSON.Decoder.default.decodeTree(TestValue.self, from: JSON.number(1)), TestValue(value: true)) + XCTAssertEqual(try JSON.Decoder.default.decodeTree(TestValue.self, from: JSON.number(0)), TestValue(value: false)) + } + + func testSingleStringStoredBool() throws { + + struct TestValue: Codable, Equatable { + var value: Bool + + init(value: Bool) { + self.value = value + } + + init(from decoder: Decoder) throws { + var container = try decoder.singleValueContainer() + value = try container.decode(using: BoolToString()) + } + + func encode(to encoder: Encoder) throws { + var contaier = encoder.singleValueContainer() + try contaier.encode(value, using: BoolToString()) + } + } + + XCTAssertEqual(try JSON.Encoder.default.encodeTree(TestValue(value: true)), JSON.string("true")) + XCTAssertEqual(try JSON.Encoder.default.encodeTree(TestValue(value: false)), JSON.string("false")) + XCTAssertEqual(try JSON.Decoder.default.decodeTree(TestValue.self, from: JSON.string("true")), TestValue(value: true)) + XCTAssertEqual(try JSON.Decoder.default.decodeTree(TestValue.self, from: JSON.string("false")), TestValue(value: false)) + } + + func testSingleFloatStoredBool() throws { - let boolValue: Bool + struct TestValue: Codable, Equatable { + var value: Bool + + init(value: Bool) { + self.value = value + } + + init(from decoder: Decoder) throws { + var container = try decoder.singleValueContainer() + value = try container.decode(using: BoolToFloat()) + } + + func encode(to encoder: Encoder) throws { + var contaier = encoder.singleValueContainer() + try contaier.encode(value, using: BoolToFloat()) + } + } + + XCTAssertEqual(try JSON.Encoder.default.encodeTree(TestValue(value: true)), JSON.number(1.0)) + XCTAssertEqual(try JSON.Encoder.default.encodeTree(TestValue(value: false)), JSON.number(0.0)) + XCTAssertEqual(try JSON.Decoder.default.decodeTree(TestValue.self, from: JSON.number(1.0)), TestValue(value: true)) + XCTAssertEqual(try JSON.Decoder.default.decodeTree(TestValue.self, from: JSON.number(0.0)), TestValue(value: false)) + } + + func testSingleDoubleStoredBool() throws { + + struct TestValue: Codable, Equatable { + var value: Bool + + init(value: Bool) { + self.value = value + } + + init(from decoder: Decoder) throws { + var container = try decoder.singleValueContainer() + value = try container.decode(using: BoolToFloat()) + } + + func encode(to encoder: Encoder) throws { + var contaier = encoder.singleValueContainer() + try contaier.encode(value, using: BoolToFloat()) + } + } + + XCTAssertEqual(try JSON.Encoder.default.encodeTree(TestValue(value: true)), JSON.number(1.0)) + XCTAssertEqual(try JSON.Encoder.default.encodeTree(TestValue(value: false)), JSON.number(0.0)) + XCTAssertEqual(try JSON.Decoder.default.decodeTree(TestValue.self, from: JSON.number(1.0)), TestValue(value: true)) + XCTAssertEqual(try JSON.Decoder.default.decodeTree(TestValue.self, from: JSON.number(0.0)), TestValue(value: false)) + } + + func testSingleBoolValueStoredBool() throws { + + struct TestValue: Codable, Equatable { + var value: Bool + + init(value: Bool) { + self.value = value + } + + init(from decoder: Decoder) throws { + var container = try decoder.singleValueContainer() + value = try container.decode(using: BoolToBoolValue()) + } + + func encode(to encoder: Encoder) throws { + var contaier = encoder.singleValueContainer() + try contaier.encode(value, using: BoolToBoolValue()) + } + } + + XCTAssertEqual(try JSON.Encoder.default.encodeTree(TestValue(value: true)), JSON.bool(true)) + XCTAssertEqual(try JSON.Encoder.default.encodeTree(TestValue(value: false)), JSON.bool(false)) + XCTAssertEqual(try JSON.Decoder.default.decodeTree(TestValue.self, from: JSON.bool(true)), TestValue(value: true)) + XCTAssertEqual(try JSON.Decoder.default.decodeTree(TestValue.self, from: JSON.bool(false)), TestValue(value: false)) + } } -extension Test: Codable { + +struct TestKeyed: Codable, Equatable { + + var trueValueBool: Int = 1 + var falseValueBool: Int = 0 + + var trueValueInt: Bool = true + var falseValueInt: Bool = false + + var trueValueInt8: Bool = true + var falseValueInt8: Bool = false + + var trueValueInt16: Bool = true + var falseValueInt16: Bool = false + + var trueValueInt32: Bool = true + var falseValueInt32: Bool = false + + var trueValueInt64: Bool = true + var falseValueInt64: Bool = false + + var trueValueUInt: Bool = true + var falseValueUInt: Bool = false + + var trueValueUInt8: Bool = true + var falseValueUInt8: Bool = false + + var trueValueUInt16: Bool = true + var falseValueUInt16: Bool = false + + var trueValueUInt32: Bool = true + var falseValueUInt32: Bool = false + + var trueValueUInt64: Bool = true + var falseValueUInt64: Bool = false + + var trueValueFloat: Bool = true + var falseValueFloat: Bool = false + + var trueValueDouble: Bool = true + var falseValueDouble: Bool = false + + var trueValueString: Bool = true + var falseValueString: Bool = false + + var trueValueStruct: Bool = true + var falseValueStruct: Bool = false + + var someValueBool: Int? = 1 + var noneValueBool: Int? + + var someValueInt: Bool? = true + var noneValueInt: Bool? + + var someValueInt8: Bool? = true + var noneValueInt8: Bool? + + var someValueInt16: Bool? = true + var noneValueInt16: Bool? + + var someValueInt32: Bool? = true + var noneValueInt32: Bool? + + var someValueInt64: Bool? = true + var noneValueInt64: Bool? + + var someValueUInt: Bool? = true + var noneValueUInt: Bool? + + var someValueUInt8: Bool? = true + var noneValueUInt8: Bool? + + var someValueUInt16: Bool? = true + var noneValueUInt16: Bool? + + var someValueUInt32: Bool? = true + var noneValueUInt32: Bool? + + var someValueUInt64: Bool? = true + var noneValueUInt64: Bool? + + var someValueFloat: Bool? = true + var noneValueFloat: Bool? + + var someValueDouble: Bool? = true + var noneValueDouble: Bool? + + var someValueString: Bool? = true + var noneValueString: Bool? + + var someValueStruct: Bool? = true + var noneValueStruct: Bool? + + init() {} enum CodingKeys: CodingKey { - case boolValue + case trueValueBool + case falseValueBool + case trueValueInt + case falseValueInt + case trueValueInt8 + case falseValueInt8 + case trueValueInt16 + case falseValueInt16 + case trueValueInt32 + case falseValueInt32 + case trueValueInt64 + case falseValueInt64 + case trueValueUInt + case falseValueUInt + case trueValueUInt8 + case falseValueUInt8 + case trueValueUInt16 + case falseValueUInt16 + case trueValueUInt32 + case falseValueUInt32 + case trueValueUInt64 + case falseValueUInt64 + case trueValueFloat + case falseValueFloat + case trueValueDouble + case falseValueDouble + case trueValueString + case falseValueString + case trueValueStruct + case falseValueStruct + case someValueBool + case noneValueBool + case someValueInt + case noneValueInt + case someValueInt8 + case noneValueInt8 + case someValueInt16 + case noneValueInt16 + case someValueInt32 + case noneValueInt32 + case someValueInt64 + case noneValueInt64 + case someValueUInt + case noneValueUInt + case someValueUInt8 + case noneValueUInt8 + case someValueUInt16 + case noneValueUInt16 + case someValueUInt32 + case noneValueUInt32 + case someValueUInt64 + case noneValueUInt64 + case someValueFloat + case noneValueFloat + case someValueDouble + case noneValueDouble + case someValueString + case noneValueString + case someValueStruct + case noneValueStruct } init(from: Decoder) throws { let container = try from.container(keyedBy: Self.CodingKeys.self) - boolValue = try container.decode(forKey: .boolValue, using: IntegerBoolTransformer()) + + trueValueBool = try container.decode(forKey: .trueValueBool, using: IntToBool()) + falseValueBool = try container.decode(forKey: .falseValueBool, using: IntToBool()) + + trueValueInt = try container.decode(forKey: .trueValueInt, using: BoolToInt()) + falseValueInt = try container.decode(forKey: .falseValueInt, using: BoolToInt()) + trueValueInt8 = try container.decode(forKey: .trueValueInt8, using: BoolToInt()) + falseValueInt8 = try container.decode(forKey: .falseValueInt8, using: BoolToInt()) + trueValueInt16 = try container.decode(forKey: .trueValueInt16, using: BoolToInt()) + falseValueInt16 = try container.decode(forKey: .falseValueInt16, using: BoolToInt()) + trueValueInt32 = try container.decode(forKey: .trueValueInt32, using: BoolToInt()) + falseValueInt32 = try container.decode(forKey: .falseValueInt32, using: BoolToInt()) + trueValueInt64 = try container.decode(forKey: .trueValueInt64, using: BoolToInt()) + falseValueInt64 = try container.decode(forKey: .falseValueInt64, using: BoolToInt()) + trueValueUInt = try container.decode(forKey: .trueValueUInt, using: BoolToInt()) + falseValueUInt = try container.decode(forKey: .falseValueUInt, using: BoolToInt()) + trueValueUInt8 = try container.decode(forKey: .trueValueUInt8, using: BoolToInt()) + falseValueUInt8 = try container.decode(forKey: .falseValueUInt8, using: BoolToInt()) + trueValueUInt16 = try container.decode(forKey: .trueValueUInt16, using: BoolToInt()) + falseValueUInt16 = try container.decode(forKey: .falseValueUInt16, using: BoolToInt()) + trueValueUInt32 = try container.decode(forKey: .trueValueUInt32, using: BoolToInt()) + falseValueUInt32 = try container.decode(forKey: .falseValueUInt32, using: BoolToInt()) + trueValueUInt64 = try container.decode(forKey: .trueValueUInt64, using: BoolToInt()) + falseValueUInt64 = try container.decode(forKey: .falseValueUInt64, using: BoolToInt()) + + trueValueFloat = try container.decode(forKey: .trueValueFloat, using: BoolToFloat()) + falseValueFloat = try container.decode(forKey: .falseValueFloat, using: BoolToFloat()) + trueValueDouble = try container.decode(forKey: .trueValueDouble, using: BoolToFloat()) + falseValueDouble = try container.decode(forKey: .falseValueDouble, using: BoolToFloat()) + + trueValueString = try container.decode(forKey: .trueValueString, using: BoolToString()) + falseValueString = try container.decode(forKey: .falseValueString, using: BoolToString()) + + trueValueStruct = try container.decode(forKey: .trueValueStruct, using: BoolToBoolValue()) + falseValueStruct = try container.decode(forKey: .falseValueStruct, using: BoolToBoolValue()) + + someValueBool = try container.decodeIfPresent(forKey: .someValueBool, using: IntToBool()) + noneValueBool = try container.decodeIfPresent(forKey: .noneValueBool, using: IntToBool()) + + someValueInt = try container.decodeIfPresent(forKey: .someValueInt, using: BoolToInt()) + noneValueInt = try container.decodeIfPresent(forKey: .noneValueInt, using: BoolToInt()) + someValueInt8 = try container.decodeIfPresent(forKey: .someValueInt8, using: BoolToInt()) + noneValueInt8 = try container.decodeIfPresent(forKey: .noneValueInt8, using: BoolToInt()) + someValueInt16 = try container.decodeIfPresent(forKey: .someValueInt16, using: BoolToInt()) + noneValueInt16 = try container.decodeIfPresent(forKey: .noneValueInt16, using: BoolToInt()) + someValueInt32 = try container.decodeIfPresent(forKey: .someValueInt32, using: BoolToInt()) + noneValueInt32 = try container.decodeIfPresent(forKey: .noneValueInt32, using: BoolToInt()) + someValueInt64 = try container.decodeIfPresent(forKey: .someValueInt64, using: BoolToInt()) + noneValueInt64 = try container.decodeIfPresent(forKey: .noneValueInt64, using: BoolToInt()) + someValueUInt = try container.decodeIfPresent(forKey: .someValueUInt, using: BoolToInt()) + noneValueUInt = try container.decodeIfPresent(forKey: .noneValueUInt, using: BoolToInt()) + someValueUInt8 = try container.decodeIfPresent(forKey: .someValueUInt8, using: BoolToInt()) + noneValueUInt8 = try container.decodeIfPresent(forKey: .noneValueUInt8, using: BoolToInt()) + someValueUInt16 = try container.decodeIfPresent(forKey: .someValueUInt16, using: BoolToInt()) + noneValueUInt16 = try container.decodeIfPresent(forKey: .noneValueUInt16, using: BoolToInt()) + someValueUInt32 = try container.decodeIfPresent(forKey: .someValueUInt32, using: BoolToInt()) + noneValueUInt32 = try container.decodeIfPresent(forKey: .noneValueUInt32, using: BoolToInt()) + someValueUInt64 = try container.decodeIfPresent(forKey: .someValueUInt64, using: BoolToInt()) + noneValueUInt64 = try container.decodeIfPresent(forKey: .noneValueUInt64, using: BoolToInt()) + + someValueFloat = try container.decodeIfPresent(forKey: .someValueFloat, using: BoolToFloat()) + noneValueFloat = try container.decodeIfPresent(forKey: .noneValueFloat, using: BoolToFloat()) + someValueDouble = try container.decodeIfPresent(forKey: .someValueDouble, using: BoolToFloat()) + noneValueDouble = try container.decodeIfPresent(forKey: .noneValueDouble, using: BoolToFloat()) + + someValueString = try container.decodeIfPresent(forKey: .someValueString, using: BoolToString()) + noneValueString = try container.decodeIfPresent(forKey: .noneValueString, using: BoolToString()) + + someValueStruct = try container.decodeIfPresent(forKey: .someValueStruct, using: BoolToBoolValue()) + noneValueStruct = try container.decodeIfPresent(forKey: .noneValueStruct, using: BoolToBoolValue()) + } func encode(to: Encoder) throws { var container = to.container(keyedBy: Self.CodingKeys.self) - try container.encode(boolValue, forKey: .boolValue, using: IntegerBoolTransformer()) + + try container.encode(trueValueBool, forKey: .trueValueBool, using: IntToBool()) + try container.encode(falseValueBool, forKey: .falseValueBool, using: IntToBool()) + + try container.encode(trueValueInt, forKey: .trueValueInt, using: BoolToInt()) + try container.encode(falseValueInt, forKey: .falseValueInt, using: BoolToInt()) + try container.encode(trueValueInt8, forKey: .trueValueInt8, using: BoolToInt()) + try container.encode(falseValueInt8, forKey: .falseValueInt8, using: BoolToInt()) + try container.encode(trueValueInt16, forKey: .trueValueInt16, using: BoolToInt()) + try container.encode(falseValueInt16, forKey: .falseValueInt16, using: BoolToInt()) + try container.encode(trueValueInt32, forKey: .trueValueInt32, using: BoolToInt()) + try container.encode(falseValueInt32, forKey: .falseValueInt32, using: BoolToInt()) + try container.encode(trueValueInt64, forKey: .trueValueInt64, using: BoolToInt()) + try container.encode(falseValueInt64, forKey: .falseValueInt64, using: BoolToInt()) + try container.encode(trueValueUInt, forKey: .trueValueUInt, using: BoolToInt()) + try container.encode(falseValueUInt, forKey: .falseValueUInt, using: BoolToInt()) + try container.encode(trueValueUInt8, forKey: .trueValueUInt8, using: BoolToInt()) + try container.encode(falseValueUInt8, forKey: .falseValueUInt8, using: BoolToInt()) + try container.encode(trueValueUInt16, forKey: .trueValueUInt16, using: BoolToInt()) + try container.encode(falseValueUInt16, forKey: .falseValueUInt16, using: BoolToInt()) + try container.encode(trueValueUInt32, forKey: .trueValueUInt32, using: BoolToInt()) + try container.encode(falseValueUInt32, forKey: .falseValueUInt32, using: BoolToInt()) + try container.encode(trueValueUInt64, forKey: .trueValueUInt64, using: BoolToInt()) + try container.encode(falseValueUInt64, forKey: .falseValueUInt64, using: BoolToInt()) + + try container.encode(trueValueFloat, forKey: .trueValueFloat, using: BoolToFloat()) + try container.encode(falseValueFloat, forKey: .falseValueFloat, using: BoolToFloat()) + try container.encode(trueValueDouble, forKey: .trueValueDouble, using: BoolToFloat()) + try container.encode(falseValueDouble, forKey: .falseValueDouble, using: BoolToFloat()) + + try container.encode(trueValueString, forKey: .trueValueString, using: BoolToString()) + try container.encode(falseValueString, forKey: .falseValueString, using: BoolToString()) + + try container.encode(trueValueStruct, forKey: .trueValueStruct, using: BoolToBoolValue()) + try container.encode(falseValueStruct, forKey: .falseValueStruct, using: BoolToBoolValue()) + + try container.encodeIfPresent(someValueBool, forKey: .someValueBool, using: IntToBool()) + try container.encodeIfPresent(noneValueBool, forKey: .noneValueBool, using: IntToBool()) + + try container.encodeIfPresent(someValueInt, forKey: .someValueInt, using: BoolToInt()) + try container.encodeIfPresent(noneValueInt, forKey: .noneValueInt, using: BoolToInt()) + try container.encodeIfPresent(someValueInt8, forKey: .someValueInt8, using: BoolToInt()) + try container.encodeIfPresent(noneValueInt8, forKey: .noneValueInt8, using: BoolToInt()) + try container.encodeIfPresent(someValueInt16, forKey: .someValueInt16, using: BoolToInt()) + try container.encodeIfPresent(noneValueInt16, forKey: .noneValueInt16, using: BoolToInt()) + try container.encodeIfPresent(someValueInt32, forKey: .someValueInt32, using: BoolToInt()) + try container.encodeIfPresent(noneValueInt32, forKey: .noneValueInt32, using: BoolToInt()) + try container.encodeIfPresent(someValueInt64, forKey: .someValueInt64, using: BoolToInt()) + try container.encodeIfPresent(noneValueInt64, forKey: .noneValueInt64, using: BoolToInt()) + try container.encodeIfPresent(someValueUInt, forKey: .someValueUInt, using: BoolToInt()) + try container.encodeIfPresent(noneValueUInt, forKey: .noneValueUInt, using: BoolToInt()) + try container.encodeIfPresent(someValueUInt8, forKey: .someValueUInt8, using: BoolToInt()) + try container.encodeIfPresent(noneValueUInt8, forKey: .noneValueUInt8, using: BoolToInt()) + try container.encodeIfPresent(someValueUInt16, forKey: .someValueUInt16, using: BoolToInt()) + try container.encodeIfPresent(noneValueUInt16, forKey: .noneValueUInt16, using: BoolToInt()) + try container.encodeIfPresent(someValueUInt32, forKey: .someValueUInt32, using: BoolToInt()) + try container.encodeIfPresent(noneValueUInt32, forKey: .noneValueUInt32, using: BoolToInt()) + try container.encodeIfPresent(someValueUInt64, forKey: .someValueUInt64, using: BoolToInt()) + try container.encodeIfPresent(noneValueUInt64, forKey: .noneValueUInt64, using: BoolToInt()) + + try container.encodeIfPresent(someValueFloat, forKey: .someValueFloat, using: BoolToFloat()) + try container.encodeIfPresent(noneValueFloat, forKey: .noneValueFloat, using: BoolToFloat()) + try container.encodeIfPresent(someValueDouble, forKey: .someValueDouble, using: BoolToFloat()) + try container.encodeIfPresent(noneValueDouble, forKey: .noneValueDouble, using: BoolToFloat()) + + try container.encodeIfPresent(someValueString, forKey: .someValueString, using: BoolToString()) + try container.encodeIfPresent(noneValueString, forKey: .noneValueString, using: BoolToString()) + + try container.encodeIfPresent(someValueStruct, forKey: .someValueStruct, using: BoolToBoolValue()) + try container.encodeIfPresent(noneValueStruct, forKey: .noneValueStruct, using: BoolToBoolValue()) } } +struct TestUnkeyed: Codable, Equatable { -class ValueTransformerTests: XCTestCase { + var trueValueBool: Int = 1 + var falseValueBool: Int = 0 - let encoder = JSON.Encoder() + var trueValueInt: Bool = true + var falseValueInt: Bool = false - func testSimple() throws { + var trueValueInt8: Bool = true + var falseValueInt8: Bool = false - let expected: JSON = [ - "boolValue": 1.0, - ] + var trueValueInt16: Bool = true + var falseValueInt16: Bool = false + + var trueValueInt32: Bool = true + var falseValueInt32: Bool = false + + var trueValueInt64: Bool = true + var falseValueInt64: Bool = false + + var trueValueUInt: Bool = true + var falseValueUInt: Bool = false + + var trueValueUInt8: Bool = true + var falseValueUInt8: Bool = false + + var trueValueUInt16: Bool = true + var falseValueUInt16: Bool = false + + var trueValueUInt32: Bool = true + var falseValueUInt32: Bool = false + + var trueValueUInt64: Bool = true + var falseValueUInt64: Bool = false + + var trueValueFloat: Bool = true + var falseValueFloat: Bool = false + + var trueValueDouble: Bool = true + var falseValueDouble: Bool = false + + var trueValueString: Bool = true + var falseValueString: Bool = false + + var someValueBool: Int? = 1 + var noneValueBool: Int? + + var someValueInt: Bool? = true + var noneValueInt: Bool? + + var someValueInt8: Bool? = true + var noneValueInt8: Bool? + + var someValueInt16: Bool? = true + var noneValueInt16: Bool? + + var someValueInt32: Bool? = true + var noneValueInt32: Bool? + + var someValueInt64: Bool? = true + var noneValueInt64: Bool? + + var someValueUInt: Bool? = true + var noneValueUInt: Bool? + + var someValueUInt8: Bool? = true + var noneValueUInt8: Bool? + + var someValueUInt16: Bool? = true + var noneValueUInt16: Bool? + + var someValueUInt32: Bool? = true + var noneValueUInt32: Bool? + + var someValueUInt64: Bool? = true + var noneValueUInt64: Bool? + + var someValueFloat: Bool? = true + var noneValueFloat: Bool? + + var someValueDouble: Bool? = true + var noneValueDouble: Bool? + + var someValueString: Bool? = true + var noneValueString: Bool? + + init() {} + + enum CodingKeys: CodingKey { + case trueValueBool + case falseValueBool + case trueValueInt + case falseValueInt + case trueValueInt8 + case falseValueInt8 + case trueValueInt16 + case falseValueInt16 + case trueValueInt32 + case falseValueInt32 + case trueValueInt64 + case falseValueInt64 + case trueValueUInt + case falseValueUInt + case trueValueUInt8 + case falseValueUInt8 + case trueValueUInt16 + case falseValueUInt16 + case trueValueUInt32 + case falseValueUInt32 + case trueValueUInt64 + case falseValueUInt64 + case trueValueFloat + case falseValueFloat + case trueValueDouble + case falseValueDouble + case trueValueString + case falseValueString + case someValueBool + case noneValueBool + case someValueInt + case noneValueInt + case someValueInt8 + case noneValueInt8 + case someValueInt16 + case noneValueInt16 + case someValueInt32 + case noneValueInt32 + case someValueInt64 + case noneValueInt64 + case someValueUInt + case noneValueUInt + case someValueUInt8 + case noneValueUInt8 + case someValueUInt16 + case noneValueUInt16 + case someValueUInt32 + case noneValueUInt32 + case someValueUInt64 + case noneValueUInt64 + case someValueFloat + case noneValueFloat + case someValueDouble + case noneValueDouble + case someValueString + case noneValueString + } + + init(from: Decoder) throws { + var container = try from.unkeyedContainer() + + trueValueBool = try container.decode(using: IntToBool()) + falseValueBool = try container.decode(using: IntToBool()) + + trueValueInt = try container.decode(using: BoolToInt()) + falseValueInt = try container.decode(using: BoolToInt()) + trueValueInt8 = try container.decode(using: BoolToInt()) + falseValueInt8 = try container.decode(using: BoolToInt()) + trueValueInt16 = try container.decode(using: BoolToInt()) + falseValueInt16 = try container.decode(using: BoolToInt()) + trueValueInt32 = try container.decode(using: BoolToInt()) + falseValueInt32 = try container.decode(using: BoolToInt()) + trueValueInt64 = try container.decode(using: BoolToInt()) + falseValueInt64 = try container.decode(using: BoolToInt()) + trueValueUInt = try container.decode(using: BoolToInt()) + falseValueUInt = try container.decode(using: BoolToInt()) + trueValueUInt8 = try container.decode(using: BoolToInt()) + falseValueUInt8 = try container.decode(using: BoolToInt()) + trueValueUInt16 = try container.decode(using: BoolToInt()) + falseValueUInt16 = try container.decode(using: BoolToInt()) + trueValueUInt32 = try container.decode(using: BoolToInt()) + falseValueUInt32 = try container.decode(using: BoolToInt()) + trueValueUInt64 = try container.decode(using: BoolToInt()) + falseValueUInt64 = try container.decode(using: BoolToInt()) + trueValueFloat = try container.decode(using: BoolToFloat()) + falseValueFloat = try container.decode(using: BoolToFloat()) + trueValueDouble = try container.decode(using: BoolToFloat()) + falseValueDouble = try container.decode(using: BoolToFloat()) + trueValueString = try container.decode(using: BoolToString()) + falseValueString = try container.decode(using: BoolToString()) + + someValueBool = try container.decodeIfPresent(using: IntToBool()) + noneValueBool = try container.decodeIfPresent(using: IntToBool()) + + someValueInt = try container.decodeIfPresent(using: BoolToInt()) + noneValueInt = try container.decodeIfPresent(using: BoolToInt()) + someValueInt8 = try container.decodeIfPresent(using: BoolToInt()) + noneValueInt8 = try container.decodeIfPresent(using: BoolToInt()) + someValueInt16 = try container.decodeIfPresent(using: BoolToInt()) + noneValueInt16 = try container.decodeIfPresent(using: BoolToInt()) + someValueInt32 = try container.decodeIfPresent(using: BoolToInt()) + noneValueInt32 = try container.decodeIfPresent(using: BoolToInt()) + someValueInt64 = try container.decodeIfPresent(using: BoolToInt()) + noneValueInt64 = try container.decodeIfPresent(using: BoolToInt()) + someValueUInt = try container.decodeIfPresent(using: BoolToInt()) + noneValueUInt = try container.decodeIfPresent(using: BoolToInt()) + someValueUInt8 = try container.decodeIfPresent(using: BoolToInt()) + noneValueUInt8 = try container.decodeIfPresent(using: BoolToInt()) + someValueUInt16 = try container.decodeIfPresent(using: BoolToInt()) + noneValueUInt16 = try container.decodeIfPresent(using: BoolToInt()) + someValueUInt32 = try container.decodeIfPresent(using: BoolToInt()) + noneValueUInt32 = try container.decodeIfPresent(using: BoolToInt()) + someValueUInt64 = try container.decodeIfPresent(using: BoolToInt()) + noneValueUInt64 = try container.decodeIfPresent(using: BoolToInt()) + someValueFloat = try container.decodeIfPresent(using: BoolToFloat()) + noneValueFloat = try container.decodeIfPresent(using: BoolToFloat()) + someValueDouble = try container.decodeIfPresent(using: BoolToFloat()) + noneValueDouble = try container.decodeIfPresent(using: BoolToFloat()) + someValueString = try container.decodeIfPresent(using: BoolToString()) + noneValueString = try container.decodeIfPresent(using: BoolToString()) + } + + func encode(to: Encoder) throws { + var container = to.unkeyedContainer() + + try container.encode(trueValueBool, using: IntToBool()) + try container.encode(falseValueBool, using: IntToBool()) - XCTAssertEqual(try encoder.encodeTree(Test(boolValue: true)).stableText, expected.stableText) + try container.encode(trueValueInt, using: BoolToInt()) + try container.encode(falseValueInt, using: BoolToInt()) + try container.encode(trueValueInt8, using: BoolToInt()) + try container.encode(falseValueInt8, using: BoolToInt()) + try container.encode(trueValueInt16, using: BoolToInt()) + try container.encode(falseValueInt16, using: BoolToInt()) + try container.encode(trueValueInt32, using: BoolToInt()) + try container.encode(falseValueInt32, using: BoolToInt()) + try container.encode(trueValueInt64, using: BoolToInt()) + try container.encode(falseValueInt64, using: BoolToInt()) + try container.encode(trueValueUInt, using: BoolToInt()) + try container.encode(falseValueUInt, using: BoolToInt()) + try container.encode(trueValueUInt8, using: BoolToInt()) + try container.encode(falseValueUInt8, using: BoolToInt()) + try container.encode(trueValueUInt16, using: BoolToInt()) + try container.encode(falseValueUInt16, using: BoolToInt()) + try container.encode(trueValueUInt32, using: BoolToInt()) + try container.encode(falseValueUInt32, using: BoolToInt()) + try container.encode(trueValueUInt64, using: BoolToInt()) + try container.encode(falseValueUInt64, using: BoolToInt()) + try container.encode(trueValueFloat, using: BoolToFloat()) + try container.encode(falseValueFloat, using: BoolToFloat()) + try container.encode(trueValueDouble, using: BoolToFloat()) + try container.encode(falseValueDouble, using: BoolToFloat()) + try container.encode(trueValueString, using: BoolToString()) + try container.encode(falseValueString, using: BoolToString()) + + try container.encode(someValueBool, using: IntToBool()) + try container.encode(noneValueBool, using: IntToBool()) + + try container.encode(someValueInt, using: BoolToInt()) + try container.encode(noneValueInt, using: BoolToInt()) + try container.encode(someValueInt8, using: BoolToInt()) + try container.encode(noneValueInt8, using: BoolToInt()) + try container.encode(someValueInt16, using: BoolToInt()) + try container.encode(noneValueInt16, using: BoolToInt()) + try container.encode(someValueInt32, using: BoolToInt()) + try container.encode(noneValueInt32, using: BoolToInt()) + try container.encode(someValueInt64, using: BoolToInt()) + try container.encode(noneValueInt64, using: BoolToInt()) + try container.encode(someValueUInt, using: BoolToInt()) + try container.encode(noneValueUInt, using: BoolToInt()) + try container.encode(someValueUInt8, using: BoolToInt()) + try container.encode(noneValueUInt8, using: BoolToInt()) + try container.encode(someValueUInt16, using: BoolToInt()) + try container.encode(noneValueUInt16, using: BoolToInt()) + try container.encode(someValueUInt32, using: BoolToInt()) + try container.encode(noneValueUInt32, using: BoolToInt()) + try container.encode(someValueUInt64, using: BoolToInt()) + try container.encode(noneValueUInt64, using: BoolToInt()) + try container.encode(someValueFloat, using: BoolToFloat()) + try container.encode(noneValueFloat, using: BoolToFloat()) + try container.encode(someValueDouble, using: BoolToFloat()) + try container.encode(noneValueDouble, using: BoolToFloat()) + try container.encode(someValueString, using: BoolToString()) + try container.encode(noneValueString, using: BoolToString()) + } + +} + +struct BoolValue: Codable, Equatable { + var value: Bool + + init(value: Bool) { + self.value = value + } + + init(from decoder: Decoder) throws { + let container = try decoder.singleValueContainer() + value = try container.decode(Bool.self) + } + func encode(to encoder: Encoder) throws { + var container = encoder.singleValueContainer() + try container.encode(value) + } + +} + +struct BoolToBoolValue: ValueCodingTransformer { + + func encode(_ value: Bool) throws -> BoolValue { + return BoolValue(value: value) + } + + func decode(_ value: BoolValue) throws -> Bool { + return value.value + } + +} + +struct IntToBool: ValueCodingTransformer { + + func encode(_ value: Int) throws -> Bool { + return value != 0 } + func decode(_ value: Bool) throws -> Int { + return value ? 1 : 0 + } +} + +struct BoolToInt: ValueCodingTransformer { + + func encode(_ value: Bool) throws -> Value { + return value ? 1 : 0 + } + + func decode(_ value: Value) throws -> Bool { + return value != 0 + } +} + +struct BoolToFloat: ValueCodingTransformer { + + func encode(_ value: Bool) throws -> Value { + return value ? 1.0 : 0.0 + } + + func decode(_ value: Value) throws -> Bool { + return value != 0.0 + } +} + +struct BoolToString: ValueCodingTransformer { + + func encode(_ value: Bool) throws -> String { + return value ? "true" : "false" + } + + func decode(_ value: String) throws -> Bool { + return value == "true" + } } diff --git a/Tests/YAMLAnyValueTests.swift b/Tests/YAMLAnyValueTests.swift new file mode 100644 index 000000000..01fad6ff2 --- /dev/null +++ b/Tests/YAMLAnyValueTests.swift @@ -0,0 +1,189 @@ +// +// YAMLAnyValueTests.swift +// PotentCodables +// +// Copyright © 2021 Outfox, inc. +// +// +// Distributed under the MIT License, See LICENSE for details. +// + +import BigInt +import Foundation +import PotentCodables +import PotentYAML +import XCTest + + +class YAMLAnyValueTests: XCTestCase { + + func testDecode() throws { + + struct TestValue: Codable { + var `nil`: AnyValue + var bool: AnyValue + var number: AnyValue + var string: AnyValue + var array: AnyValue + var object: AnyValue + } + + let yaml = + """ + --- + nil: null + bool: true + number: 123.456 + string: "Hello World!" + array: + - null + - false + - 456 + - "a" + object: + c: 1 + a: 2 + d: 3 + b: 4 + """ + + let value = try YAML.Decoder.default.decode(TestValue.self, from: yaml) + XCTAssertEqual(value.nil, AnyValue.nil) + XCTAssertEqual(value.bool, AnyValue.bool(true)) + XCTAssertEqual(value.number, AnyValue.double(123.456)) + XCTAssertEqual(value.string, AnyValue.string("Hello World!")) + XCTAssertEqual(value.array, AnyValue.array([.nil, .bool(false), .int64(456), .string("a")])) + XCTAssertEqual(value.object, AnyValue.dictionary(["c": 1, "a": 2, "d": 3, "b": 4])) + } + + func testEncode() throws { + + struct TestValue: Codable { + var `nil`: AnyValue = .nil + var bool: AnyValue = .bool(true) + var string: AnyValue = .string("Hello World!") + var pi8: AnyValue = .int8(2) + var pi16: AnyValue = .int16(500) + var pi32: AnyValue = .int32(70_000) + var pi64: AnyValue = .int64(5_000_000_000) + var ni8: AnyValue = .int8(-2) + var ni16: AnyValue = .int16(-500) + var ni32: AnyValue = .int32(-70_000) + var ni64: AnyValue = .int64(-5_000_000_000) + var u8: AnyValue = .uint8(UInt8.max) + var u16: AnyValue = .uint16(UInt16.max) + var u32: AnyValue = .uint32(UInt32.max) + var u64: AnyValue = .uint64(UInt64.max) + var nint: AnyValue = .integer(BigInt("-999000000000000000000000000000")) + var pint: AnyValue = .integer(BigInt("999000000000000000000000000000")) + var uint: AnyValue = .unsignedInteger(BigUInt("999000000000000000000000000000")) + var f16: AnyValue = .float16(1.5) + var f32: AnyValue = .float(12.34567) + var f64: AnyValue = .double(123.4567) + var pdec: AnyValue = .decimal(Decimal(sign: .plus, exponent: -3, significand: 1234567)) + var ndec: AnyValue = .decimal(Decimal(sign: .minus, exponent: -3, significand: 1234567)) + var data: AnyValue = .data("Binary Data".data(using: .utf8)!) + var url: AnyValue = .url(URL(string: "https://example.com/some/thing")!) + var uuid: AnyValue = .uuid(UUID(uuidString: "46076D06-86E8-4B3B-80EF-B24115D4C609")!) + var date: AnyValue = .date(Date(timeIntervalSinceReferenceDate: 1234567.89)) + var array: AnyValue = .array([nil, false, 456, "a"]) + var object: AnyValue = .dictionary([ + "c": 1, + "a": 2, + "d": 3, + "b": 4, + ]) + var intObject: AnyValue = .dictionary([ + 2: "a", + 1: "b", + 4: "c", + 3: "d", + ]) + } + let srcValue = TestValue() + + let yaml = + """ + --- + nil: null + bool: true + string: Hello World! + pi8: 2 + pi16: 500 + pi32: 70000 + pi64: 5000000000 + ni8: -2 + ni16: -500 + ni32: -70000 + ni64: -5000000000 + u8: 255 + u16: 65535 + u32: 4294967295 + u64: 18446744073709551615 + nint: -999000000000000000000000000000 + pint: 999000000000000000000000000000 + uint: 999000000000000000000000000000 + f16: 1.5 + f32: 12.34567 + f64: 123.4567 + pdec: 1234.567 + ndec: -1234.567 + data: QmluYXJ5IERhdGE= + url: https://example.com/some/thing + uuid: 46076D06-86E8-4B3B-80EF-B24115D4C609 + date: 1234567.89 + array: + - null + - false + - 456 + - a + object: + c: 1 + a: 2 + d: 3 + b: 4 + intObject: + 2: a + 1: b + 4: c + 3: d + ... + + """ + + XCTAssertEqual(try YAMLEncoder.default.encodeString(TestValue()), yaml) + + let dstValue = try YAML.Decoder.default.decode(TestValue.self, from: yaml) + XCTAssertEqual(dstValue.nil, srcValue.nil) + XCTAssertEqual(dstValue.bool, srcValue.bool) + XCTAssertEqual(dstValue.string, srcValue.string) + XCTAssertEqual(dstValue.pi8, .int64(2)) + XCTAssertEqual(dstValue.pi16, .int64(500)) + XCTAssertEqual(dstValue.pi32, .int64(70_000)) + XCTAssertEqual(dstValue.pi64, srcValue.pi64) + XCTAssertEqual(dstValue.ni8, .int64(-2)) + XCTAssertEqual(dstValue.ni16, .int64(-500)) + XCTAssertEqual(dstValue.ni32, .int64(-70_000)) + XCTAssertEqual(dstValue.ni64, srcValue.ni64) + XCTAssertEqual(dstValue.u8, .int64(Int64(UInt8.max))) + XCTAssertEqual(dstValue.u16, .int64(Int64(UInt16.max))) + XCTAssertEqual(dstValue.u32, .int64(Int64(UInt32.max))) + XCTAssertEqual(dstValue.u64, .uint64(UInt64.max)) + XCTAssertEqual(dstValue.pint, srcValue.pint) + XCTAssertEqual(dstValue.nint, srcValue.nint) + XCTAssertEqual(dstValue.uint, .integer(BigInt("999000000000000000000000000000"))) + XCTAssertEqual(dstValue.f16, .double(1.5)) + XCTAssertEqual(dstValue.f32, .double(12.34567)) + XCTAssertEqual(dstValue.f64, srcValue.f64) + XCTAssertEqual(dstValue.pdec, .double(1234.567)) + XCTAssertEqual(dstValue.ndec, .double(-1234.567)) + XCTAssertEqual(dstValue.data, .string(srcValue.data.dataValue!.base64EncodedString())) + XCTAssertEqual(dstValue.url, .string(srcValue.url.urlValue!.absoluteString)) + XCTAssertEqual(dstValue.uuid, .string(srcValue.uuid.uuidValue!.uuidString)) + XCTAssertEqual(dstValue.date, .double(srcValue.date.dateValue!.timeIntervalSinceReferenceDate)) + XCTAssertEqual(dstValue.array, .array([nil, false, .int64(456), "a"])) + XCTAssertEqual(dstValue.object, .dictionary(["c": .int64(1), "a": .int64(2), "d": .int64(3), "b": .int64(4)])) + XCTAssertEqual(dstValue.intObject, .dictionary([.int64(2): "a", .int64(1): "b", .int64(4): "c", .int64(3): "d"])) + } + +} diff --git a/Tests/YAMLDecoderTests.swift b/Tests/YAMLDecoderTests.swift new file mode 100644 index 000000000..5d88ae28c --- /dev/null +++ b/Tests/YAMLDecoderTests.swift @@ -0,0 +1,3000 @@ +// +// YAMLDecoderTests.swift +// PotentCodables +// +// Copyright © 2021 Outfox, inc. +// +// +// Distributed under the MIT License, See LICENSE for details. +// + +import BigInt +import Foundation +import PotentCodables +import PotentYAML +import XCTest + + +class YAMLDecoderTests: XCTestCase { + + func testDecodeFromUTF8Data() { + + struct TestValue: Codable { + var test: String + } + + let yaml = + """ + test: Hello World! + """ + + let decoder = YAML.Decoder() + decoder.keyDecodingStrategy = .useDefaultKeys + + XCTAssertNoThrow(try decoder.decode(TestValue.self, from: yaml.data(using: .utf8)!)) + } + + func testDecodeWithDefaultKeyStrategy() { + + struct TestValue: Codable { + var camelCased: String + } + + let yaml = + """ + camelCased: Hello World! + """ + + let decoder = YAML.Decoder() + decoder.keyDecodingStrategy = .useDefaultKeys + + XCTAssertNoThrow(try decoder.decode(TestValue.self, from: yaml)) + } + + func testDecodeWithSnakeCaseKeyStrategy() { + + struct TestValue: Codable { + var snakeCased: String + } + + let yaml = + """ + snake_cased: Hello World! + """ + + let decoder = YAML.Decoder() + decoder.keyDecodingStrategy = .convertFromSnakeCase + + XCTAssertNoThrow(try decoder.decode(TestValue.self, from: yaml)) + } + + func testDecodeWithCustomKeyStrategy() { + + struct TestValue: Codable { + var kebabCased: String + } + + let yaml = + """ + kebab-cased: Hello World! + """ + + let decoder = YAML.Decoder() + decoder.keyDecodingStrategy = .custom { _ in + return AnyCodingKey(stringValue: "kebabCased") + } + + XCTAssertNoThrow(try decoder.decode(TestValue.self, from: yaml)) + } + + func testDecodeISO8601Date() throws { + + let date = ZonedDate(date: Date(), timeZone: .utc) + let parsedDate = ZonedDate(iso8601Encoded: date.iso8601EncodedString())! + + struct TestValue: Codable { + var date: Date + } + + let yaml = + """ + --- + date: \(parsedDate.iso8601EncodedString()) + ... + + """ + + let decoder = YAML.Decoder() + decoder.dateDecodingStrategy = .iso8601 + + let testValue = try decoder.decode(TestValue.self, from: yaml) + XCTAssertEqual(testValue.date, parsedDate.utcDate) + } + + func testDecodeEpochSecondsDate() throws { + + let date = Date() + + struct TestValue: Codable { + var date: Date + } + + let yaml = + """ + --- + date: \(date.timeIntervalSince1970) + ... + + """ + + let decoder = YAML.Decoder() + decoder.dateDecodingStrategy = .secondsSince1970 + + let testValue = try decoder.decode(TestValue.self, from: yaml) + XCTAssertEqual(testValue.date.timeIntervalSince1970, date.timeIntervalSince1970) + } + + func testDecodeEpochMillisecondsDate() throws { + + let date = Date() + + struct TestValue: Codable { + var date: Date + } + + let yaml = + """ + --- + date: \(date.timeIntervalSince1970 * 1000.0) + ... + + """ + + let decoder = YAML.Decoder() + decoder.dateDecodingStrategy = .millisecondsSince1970 + + let testValue = try decoder.decode(TestValue.self, from: yaml) + XCTAssertEqual((testValue.date.timeIntervalSince1970 * 1000.0).rounded(), + (date.timeIntervalSince1970 * 1000.0).rounded()) + } + + func testDecodeDeferredToDate() throws { + + let date = Date() + + struct TestValue: Codable { + var date: Date + } + + let yaml = + """ + --- + date: \(date.timeIntervalSinceReferenceDate) + ... + + """ + + let decoder = YAML.Decoder() + decoder.dateDecodingStrategy = .deferredToDate + + let testValue = try decoder.decode(TestValue.self, from: yaml) + XCTAssertEqual(testValue.date.timeIntervalSince1970, date.timeIntervalSince1970) + } + + func testDecodeWithCustomDecoder() throws { + + let date = Date() + + struct TestValue: Codable { + var date: Date + } + + let yaml = + """ + --- + date: \(date.timeIntervalSince1970) + ... + + """ + + let decoder = YAML.Decoder() + decoder.dateDecodingStrategy = .custom { + Date(timeIntervalSince1970: try $0.singleValueContainer().decode(Double.self)) + } + + let testValue = try decoder.decode(TestValue.self, from: yaml) + XCTAssertEqual(testValue.date.timeIntervalSince1970, date.timeIntervalSince1970) + } + + func testDecodeWithFormatter() throws { + + let formatter = DateFormatter() + formatter.dateFormat = "yyyy-MM-dd HH:mm:ss.SSS XXXX" + + let date = Date().truncatedToMillisecs + + struct TestValue: Codable { + var date: Date + } + + let yaml = + """ + --- + date: \(formatter.string(from: date)) + ... + + """ + + let decoder = YAML.Decoder() + decoder.dateDecodingStrategy = .formatted(formatter) + + let testValue = try decoder.decode(TestValue.self, from: yaml) + XCTAssertEqual((testValue.date.timeIntervalSince1970 * 1000.0).rounded(), + (date.timeIntervalSince1970 * 1000.0).rounded()) + } + + func testDecodeBadFormattedDate() throws { + + let formatter = DateFormatter() + formatter.dateFormat = "yyyy-MM-dd HH:mm:ss.SSS XXXX" + + let date = Date().truncatedToMillisecs + + struct TestValue: Codable { + var date: Date + } + + let yaml = + """ + --- + date: \(date) + ... + + """ + + let decoder = YAML.Decoder() + decoder.dateDecodingStrategy = .formatted(formatter) + + XCTAssertThrowsError(try decoder.decode(TestValue.self, from: yaml)) { error in + AssertDecodingDataCorrupted(error) + } + } + + func testDecodeBadISO8601Date() throws { + + struct TestValue: Codable { + var date: Date + } + + let yaml = + """ + --- + date: 2020 Wed 09 + ... + + """ + + let decoder = YAML.Decoder() + decoder.dateDecodingStrategy = .iso8601 + + XCTAssertThrowsError(try decoder.decode(TestValue.self, from: yaml)) { error in + AssertDecodingDataCorrupted(error) + } + } + + func testDecodeDateFromNull() throws { + + struct TestValue: Codable { + var date: Date + } + + let yaml = + """ + --- + date: null + ... + + """ + + XCTAssertThrowsError(try YAML.Decoder.default.decode(TestValue.self, from: yaml)) { error in + AssertDecodingValueNotFound(error) + } + } + + func testDecodeDateFromNullItem() throws { + + struct TestValue: Codable { + init(from decoder: Decoder) throws { + var container = try decoder.unkeyedContainer() + _ = try container.decode(Date.self) + } + func encode(to encoder: Encoder) throws { + } + } + + XCTAssertThrowsError(try YAML.Decoder.default.decodeTree(TestValue.self, from: .sequence([nil]))) { error in + AssertDecodingValueNotFound(error) + } + } + + func testDecodeBase64Data() throws { + + let data = "Hello World!".data(using: .utf8)! + + struct TestValue: Codable { + var data: Data + } + + let yaml = + """ + --- + data: \(data.base64EncodedString()) + ... + + """ + + let decoder = YAML.Decoder() + decoder.dataDecodingStrategy = .base64 + + let testValue = try decoder.decode(TestValue.self, from: yaml) + XCTAssertEqual(testValue.data, data) + } + + func testDecodeDeferredToData() throws { + + let data = "Hello World!".data(using: .utf8)! + + struct TestValue: Codable { + var data: Data + } + + let yaml = + """ + --- + data: [\(data.map { String($0) }.joined(separator: ", "))] + ... + + """ + + let decoder = YAML.Decoder() + decoder.dataDecodingStrategy = .deferredToData + + let testValue = try decoder.decode(TestValue.self, from: yaml) + XCTAssertEqual(testValue.data, data) + } + + func testDecodeCustomData() throws { + + let data = "Hello World!".data(using: .utf8)! + + struct TestValue: Codable { + var data: Data + } + + let yaml = + """ + --- + data: \(data.hexEncodedString()) + ... + + """ + + let decoder = YAML.Decoder() + decoder.dataDecodingStrategy = .custom { decoder in + return Data(hexEncoded: try decoder.singleValueContainer().decode(String.self)) + } + + let testValue = try decoder.decode(TestValue.self, from: yaml) + XCTAssertEqual(testValue.data, data) + } + + func testDecodeBadBase64Data() throws { + + struct TestValue: Codable { + var data: Data + } + + let yaml = + """ + --- + data: This is not base64 + ... + + """ + + let decoder = YAML.Decoder() + decoder.dataDecodingStrategy = .base64 + + XCTAssertThrowsError(try decoder.decode(TestValue.self, from: yaml)) { error in + AssertDecodingDataCorrupted(error) + } + } + + func testDecodeIncorrectBase64Data() throws { + + struct TestValue: Codable { + var data: Data + } + + let yaml = + """ + --- + data: [1, 2, 3, 4, 5] + ... + + """ + + let decoder = YAML.Decoder() + decoder.dataDecodingStrategy = .base64 + + XCTAssertThrowsError(try decoder.decode(TestValue.self, from: yaml)) { error in + AssertDecodingTypeMismatch(error) + } + } + + func testDecodeDataFromNull() throws { + + struct TestValue: Codable { + var data: Data + } + + let yaml = + """ + --- + data: null + ... + + """ + + XCTAssertThrowsError(try YAML.Decoder.default.decode(TestValue.self, from: yaml)) { error in + AssertDecodingValueNotFound(error) + } + } + + func testDecodeDataFromNullItem() throws { + + struct TestValue: Codable { + init(from decoder: Decoder) throws { + var container = try decoder.unkeyedContainer() + _ = try container.decode(Data.self) + } + func encode(to encoder: Encoder) throws { + } + } + + XCTAssertThrowsError(try YAML.Decoder.default.decodeTree(TestValue.self, from: .sequence([nil]))) { error in + AssertDecodingValueNotFound(error) + } + } + + func testDecodeURL() throws { + + struct TestValue: Codable { + var url: URL + } + + let yaml = + """ + --- + url: https://example.com/some/thing + ... + + """ + + let testValue = try YAML.Decoder.default.decode(TestValue.self, from: yaml) + XCTAssertEqual(testValue.url, URL(string: "https://example.com/some/thing")) + } + + func testDecodeBadURL() throws { + + struct TestValue: Codable { + var url: URL + } + + let yaml = + """ + --- + url: Not a URL + ... + + """ + + XCTAssertThrowsError(try YAML.Decoder.default.decode(TestValue.self, from: yaml)) { error in + AssertDecodingDataCorrupted(error) + } + } + + func testDecodeURLFromNonString() throws { + + struct TestValue: Codable { + var url: URL + } + + let yaml = + """ + --- + url: 12345 + ... + + """ + + XCTAssertThrowsError(try YAML.Decoder.default.decode(TestValue.self, from: yaml)) { error in + AssertDecodingTypeMismatch(error) + } + } + + func testDecodeURLFromNull() throws { + + struct TestValue: Codable { + var url: URL + } + + let yaml = + """ + --- + url: null + ... + + """ + + XCTAssertThrowsError(try YAML.Decoder.default.decode(TestValue.self, from: yaml)) { error in + AssertDecodingValueNotFound(error) + } + } + + func testDecodeURLFromNullItem() throws { + + struct TestValue: Codable { + init(from decoder: Decoder) throws { + var container = try decoder.unkeyedContainer() + _ = try container.decode(URL.self) + } + func encode(to encoder: Encoder) throws { + } + } + + XCTAssertThrowsError(try YAML.Decoder.default.decodeTree(TestValue.self, from: .sequence([nil]))) { error in + AssertDecodingValueNotFound(error) + } + } + + func testDecodeUUID() throws { + + let uuid = UUID() + + struct TestValue: Codable { + var uuid: UUID + } + + let yaml = + """ + --- + uuid: \(uuid.uuidString) + ... + + """ + + let testValue = try YAML.Decoder.default.decode(TestValue.self, from: yaml) + XCTAssertEqual(testValue.uuid, uuid) + } + + func testDecodeBadUUID() throws { + + struct TestValue: Codable { + var uuid: UUID + } + + let yaml = + """ + --- + uuid: Not a UUID + ... + + """ + + XCTAssertThrowsError(try YAML.Decoder.default.decode(TestValue.self, from: yaml)) { error in + AssertDecodingDataCorrupted(error) + } + } + + func testDecodeUUIDFromNonString() throws { + + struct TestValue: Codable { + var uuid: UUID + } + + let yaml = + """ + --- + uuid: 12345 + ... + + """ + + XCTAssertThrowsError(try YAML.Decoder.default.decode(TestValue.self, from: yaml)) { error in + AssertDecodingTypeMismatch(error) + } + } + + func testDecodeUUIDFromNull() throws { + + struct TestValue: Codable { + var uuid: UUID + } + + let yaml = + """ + --- + uuid: null + ... + + """ + + XCTAssertThrowsError(try YAML.Decoder.default.decode(TestValue.self, from: yaml)) { error in + AssertDecodingValueNotFound(error) + } + } + + func testDecodeUUIDFromNullItem() throws { + + struct TestValue: Codable { + init(from decoder: Decoder) throws { + var container = try decoder.unkeyedContainer() + _ = try container.decode(UUID.self) + } + func encode(to encoder: Encoder) throws { + } + } + + XCTAssertThrowsError(try YAML.Decoder.default.decodeTree(TestValue.self, from: .sequence([nil]))) { error in + AssertDecodingValueNotFound(error) + } + } + + func testDecodeString() throws { + + let string = "Hello World!" + + struct TestValue: Codable { + var string: String + } + + let yaml = + """ + --- + string: \(string) + ... + + """ + + let testValue = try YAML.Decoder.default.decode(TestValue.self, from: yaml) + XCTAssertEqual(testValue.string, string) + } + + func testDecodeStringFromNonString() throws { + + struct TestValue: Codable { + var string: String + } + + let yaml = + """ + --- + string: 12345 + ... + + """ + + XCTAssertThrowsError(try YAML.Decoder.default.decode(TestValue.self, from: yaml)) { error in + AssertDecodingTypeMismatch(error) + } + } + + func testDecodeStringFromNull() throws { + + struct TestValue: Codable { + var string: String + } + + let yaml = + """ + --- + string: null + ... + + """ + + XCTAssertThrowsError(try YAML.Decoder.default.decode(TestValue.self, from: yaml)) { error in + AssertDecodingValueNotFound(error) + } + } + + func testDecodeStringFromNullItem() throws { + + struct TestValue: Codable { + init(from decoder: Decoder) throws { + var container = try decoder.unkeyedContainer() + _ = try container.decode(String.self) + } + func encode(to encoder: Encoder) throws { + } + } + + XCTAssertThrowsError(try YAML.Decoder.default.decodeTree(TestValue.self, from: .sequence([nil]))) { error in + AssertDecodingValueNotFound(error) + } + } + + func testDecodeDecimalFromPositiveDecimal() throws { + + let decimal = Decimal(sign: .plus, exponent: -3, significand: 1234567) + + struct TestValue: Codable { + var decimal: Decimal + } + + let yaml = + """ + --- + decimal: \(decimal) + ... + + """ + + let testValue = try YAML.Decoder.default.decode(TestValue.self, from: yaml) + XCTAssertEqual(testValue.decimal, decimal) + } + + func testDecodeDecimalFromNegativeDecimal() throws { + + let decimal = Decimal(sign: .minus, exponent: -3, significand: 1234567) + + struct TestValue: Codable { + var decimal: Decimal + } + + let yaml = + """ + --- + decimal: \(decimal) + ... + + """ + + let testValue = try YAML.Decoder.default.decode(TestValue.self, from: yaml) + XCTAssertEqual(testValue.decimal, decimal) + } + + func testDecodeDecimalFromPositiveInteger() throws { + + let integer = 1234567 + + struct TestValue: Codable { + var decimal: Decimal + } + + let yaml = + """ + --- + decimal: \(integer) + ... + + """ + + let testValue = try YAML.Decoder.default.decode(TestValue.self, from: yaml) + XCTAssertEqual(testValue.decimal, Decimal(sign: .plus, exponent: 0, significand: Decimal(integer))) + } + + func testDecodeDecimalFromNegativeInteger() throws { + + let integer = -1234567 + + struct TestValue: Codable { + var decimal: Decimal + } + + let yaml = + """ + --- + decimal: \(integer) + ... + + """ + + let testValue = try YAML.Decoder.default.decode(TestValue.self, from: yaml) + XCTAssertEqual(testValue.decimal, Decimal(sign: .minus, exponent: 0, significand: Decimal(integer.magnitude))) + } + + func testDecodeDecimalFromNaN() throws { + + struct TestValue: Codable { + var decimal: Decimal + } + + let yaml = + """ + --- + decimal: .nan + ... + + """ + + let testValue = try YAMLDecoder.default.decode(TestValue.self, from: yaml) + XCTAssertTrue(testValue.decimal.isNaN) + } + + func testDecodeNonConformingDecimalThrowsError() throws { + + struct TestValue: Codable { + var decimal: Decimal + } + + let yaml = + """ + --- + decimal: +.inf + ... + + """ + + XCTAssertThrowsError(try YAMLDecoder.default.decode(TestValue.self, from: yaml)) { error in + AssertDecodingTypeMismatch(error) + } + } + + func testDecodeDecimalFromNonNumber() throws { + + struct TestValue: Codable { + var decimal: Decimal + } + + let yaml = + """ + --- + decimal: Not a Number + ... + + """ + + XCTAssertThrowsError(try YAML.Decoder.default.decode(TestValue.self, from: yaml)) { error in + AssertDecodingTypeMismatch(error) + } + } + + func testDecodeDecimalFromNull() throws { + + struct TestValue: Codable { + var decimal: Decimal + } + + let yaml = + """ + --- + decimal: null + ... + + """ + + XCTAssertThrowsError(try YAML.Decoder.default.decode(TestValue.self, from: yaml)) { error in + AssertDecodingValueNotFound(error) + } + } + + func testDecodeDecimalFromNullItem() throws { + + struct TestValue: Codable { + init(from decoder: Decoder) throws { + var container = try decoder.unkeyedContainer() + _ = try container.decode(Decimal.self) + } + func encode(to encoder: Encoder) throws { + } + } + + XCTAssertThrowsError(try YAML.Decoder.default.decodeTree(TestValue.self, from: .sequence([nil]))) { error in + AssertDecodingValueNotFound(error) + } + } + + func testDecodeDoubleFromPositiveDecimal() throws { + + let double = Double(sign: .plus, exponent: -3, significand: 1234567) + + struct TestValue: Codable { + var double: Double + } + + let yaml = + """ + --- + double: \(double) + ... + + """ + + let testValue = try YAML.Decoder.default.decode(TestValue.self, from: yaml) + XCTAssertEqual(testValue.double, double) + } + + func testDecodeDoubleFromNegativeDecimal() throws { + + let double = Double(sign: .minus, exponent: -3, significand: 1234567) + + struct TestValue: Codable { + var double: Double + } + + let yaml = + """ + --- + double: \(double) + ... + + """ + + let testValue = try YAML.Decoder.default.decode(TestValue.self, from: yaml) + XCTAssertEqual(testValue.double, double) + } + + func testDecodeDoubleFromPositiveInteger() throws { + + let integer = 1234567 + + struct TestValue: Codable { + var double: Double + } + + let yaml = + """ + --- + double: \(integer) + ... + + """ + + let testValue = try YAML.Decoder.default.decode(TestValue.self, from: yaml) + XCTAssertEqual(testValue.double, Double(sign: .plus, exponent: 0, significand: Double(integer))) + } + + func testDecodeDoubleFromNegativeInteger() throws { + + let integer = -1234567 + + struct TestValue: Codable { + var double: Double + } + + let yaml = + """ + --- + double: \(integer) + ... + + """ + + let testValue = try YAML.Decoder.default.decode(TestValue.self, from: yaml) + XCTAssertEqual(testValue.double, Double(sign: .minus, exponent: 0, significand: Double(integer.magnitude))) + } + + func testDecodeDoubleFromNaN() throws { + + struct TestValue: Codable { + var double: Double + } + + let yaml = + """ + --- + double: .nan + ... + + """ + + let testValue = try YAMLDecoder.default.decode(TestValue.self, from: yaml) + XCTAssertTrue(testValue.double.isNaN) + } + + func testDecodeDoubleFromPositiveInfinity() throws { + + struct TestValue: Codable { + var double: Double + } + + let yaml = + """ + --- + double: +.inf + ... + + """ + + let testValue = try YAMLDecoder.default.decode(TestValue.self, from: yaml) + XCTAssertEqual(testValue.double, Double.infinity) + } + + func testDecodeDoubleFromNegativeInfinity() throws { + + struct TestValue: Codable { + var double: Double + } + + let yaml = + """ + --- + double: -.inf + ... + + """ + + let testValue = try YAMLDecoder.default.decode(TestValue.self, from: yaml) + XCTAssertEqual(testValue.double, -Double.infinity) + } + + func testDecodeDoubleFromNonNumber() throws { + + struct TestValue: Codable { + var double: Double + } + + let yaml = + """ + --- + double: Not a Number + ... + + """ + + XCTAssertThrowsError(try YAML.Decoder.default.decode(TestValue.self, from: yaml)) { error in + AssertDecodingTypeMismatch(error) + } + } + + func testDecodeDoubleFromNull() throws { + + struct TestValue: Codable { + var double: Double + } + + let yaml = + """ + --- + double: null + ... + + """ + + XCTAssertThrowsError(try YAML.Decoder.default.decode(TestValue.self, from: yaml)) { error in + AssertDecodingValueNotFound(error) + } + } + + func testDecodeDoubleFromNullItem() throws { + + struct TestValue: Codable { + init(from decoder: Decoder) throws { + var container = try decoder.unkeyedContainer() + _ = try container.decode(Double.self) + } + func encode(to encoder: Encoder) throws { + } + } + + XCTAssertThrowsError(try YAML.Decoder.default.decodeTree(TestValue.self, from: .sequence([nil]))) { error in + AssertDecodingValueNotFound(error) + } + } + + func testDecodeFloatFromPositiveDecimal() throws { + + let float = Float(sign: .plus, exponent: -3, significand: 1234567) + + struct TestValue: Codable { + var float: Float + } + + let yaml = + """ + --- + float: \(float) + ... + + """ + + let testValue = try YAML.Decoder.default.decode(TestValue.self, from: yaml) + XCTAssertEqual(testValue.float, float) + } + + func testDecodeFloatFromNegativeDecimal() throws { + + let float = Float(sign: .minus, exponent: -3, significand: 1234567) + + struct TestValue: Codable { + var float: Float + } + + let yaml = + """ + --- + float: \(float) + ... + + """ + + let testValue = try YAML.Decoder.default.decode(TestValue.self, from: yaml) + XCTAssertEqual(testValue.float, float) + } + + func testDecodeFloatFromPositiveInteger() throws { + + let integer = 1234567 + + struct TestValue: Codable { + var float: Float + } + + let yaml = + """ + --- + float: \(integer) + ... + + """ + + let testValue = try YAML.Decoder.default.decode(TestValue.self, from: yaml) + XCTAssertEqual(testValue.float, Float(sign: .plus, exponent: 0, significand: Float(integer))) + } + + func testDecodeFloatFromNegativeInteger() throws { + + let integer = -1234567 + + struct TestValue: Codable { + var float: Float + } + + let yaml = + """ + --- + float: \(integer) + ... + + """ + + let testValue = try YAML.Decoder.default.decode(TestValue.self, from: yaml) + XCTAssertEqual(testValue.float, Float(sign: .minus, exponent: 0, significand: Float(integer.magnitude))) + } + + func testDecodeFloatFromNaN() throws { + + struct TestValue: Codable { + var float: Float + } + + let yaml = + """ + --- + float: .nan + ... + + """ + + let testValue = try YAMLDecoder.default.decode(TestValue.self, from: yaml) + XCTAssertTrue(testValue.float.isNaN) + } + + func testDecodeFloatFromPositiveInfinity() throws { + + struct TestValue: Codable { + var float: Float + } + + let yaml = + """ + --- + float: +.inf + ... + + """ + + let testValue = try YAMLDecoder.default.decode(TestValue.self, from: yaml) + XCTAssertEqual(testValue.float, Float.infinity) + } + + func testDecodeFloatFromNegativeInfinity() throws { + + struct TestValue: Codable { + var float: Float + } + + let yaml = + """ + --- + float: -.inf + ... + + """ + + let testValue = try YAMLDecoder.default.decode(TestValue.self, from: yaml) + XCTAssertEqual(testValue.float, -Float.infinity) + } + + func testDecodeFloatFromNonNumber() throws { + + struct TestValue: Codable { + var float: Float + } + + let yaml = + """ + --- + float: Not a Number + ... + + """ + + XCTAssertThrowsError(try YAML.Decoder.default.decode(TestValue.self, from: yaml)) { error in + AssertDecodingTypeMismatch(error) + } + } + + func testDecodeFloatFromNull() throws { + + struct TestValue: Codable { + var float: Float + } + + let yaml = + """ + --- + float: null + ... + + """ + + XCTAssertThrowsError(try YAML.Decoder.default.decode(TestValue.self, from: yaml)) { error in + AssertDecodingValueNotFound(error) + } + } + + func testDecodeFloatFromNullItem() throws { + + struct TestValue: Codable { + init(from decoder: Decoder) throws { + var container = try decoder.unkeyedContainer() + _ = try container.decode(Float.self) + } + func encode(to encoder: Encoder) throws { + } + } + + XCTAssertThrowsError(try YAML.Decoder.default.decodeTree(TestValue.self, from: .sequence([nil]))) { error in + AssertDecodingValueNotFound(error) + } + } + + func testDecodeFloat16FromPositiveDecimal() throws { + + let float = Float16(sign: .plus, exponent: -3, significand: 1234) + + struct TestValue: Codable { + var float: Float16 + } + + let yaml = + """ + --- + float: \(float) + ... + + """ + + let testValue = try YAML.Decoder.default.decode(TestValue.self, from: yaml) + XCTAssertEqual(testValue.float, float) + } + + func testDecodeFloat16FromNegativeDecimal() throws { + + let float = Float16(sign: .minus, exponent: -3, significand: 1234) + + struct TestValue: Codable { + var float: Float16 + } + + let yaml = + """ + --- + float: \(float) + ... + + """ + + let testValue = try YAML.Decoder.default.decode(TestValue.self, from: yaml) + XCTAssertEqual(testValue.float, float) + } + + func testDecodeFloat16FromPositiveInteger() throws { + + let integer = 1234567 + + struct TestValue: Codable { + var float: Float16 + } + + let yaml = + """ + --- + float: \(integer) + ... + + """ + + let testValue = try YAML.Decoder.default.decode(TestValue.self, from: yaml) + XCTAssertEqual(testValue.float, Float16(sign: .plus, exponent: 0, significand: Float16(integer))) + } + + func testDecodeFloat16FromNegativeInteger() throws { + + let integer = -1234567 + + struct TestValue: Codable { + var float: Float16 + } + + let yaml = + """ + --- + float: \(integer) + ... + + """ + + let testValue = try YAML.Decoder.default.decode(TestValue.self, from: yaml) + XCTAssertEqual(testValue.float, Float16(sign: .minus, exponent: 0, significand: Float16(integer.magnitude))) + } + + func testDecodeFloat16FromNaN() throws { + + struct TestValue: Codable { + var float: Float16 + } + + let yaml = + """ + --- + float: .nan + ... + + """ + + let testValue = try YAMLDecoder.default.decode(TestValue.self, from: yaml) + XCTAssertTrue(testValue.float.isNaN) + } + + func testDecodeFloat16FromPositiveInfinity() throws { + + struct TestValue: Codable { + var float: Float16 + } + + let yaml = + """ + --- + float: +.inf + ... + + """ + + let testValue = try YAMLDecoder.default.decode(TestValue.self, from: yaml) + XCTAssertEqual(testValue.float, Float16.infinity) + } + + func testDecodeFloat16FromNegativeInfinity() throws { + + struct TestValue: Codable { + var float: Float16 + } + + let yaml = + """ + --- + float: -.inf + ... + + """ + + let testValue = try YAMLDecoder.default.decode(TestValue.self, from: yaml) + XCTAssertEqual(testValue.float, -Float16.infinity) + } + + func testDecodeFloat16FromNonNumber() throws { + + struct TestValue: Codable { + var float: Float16 + } + + let yaml = + """ + --- + float: Not a Number + ... + + """ + + XCTAssertThrowsError(try YAML.Decoder.default.decode(TestValue.self, from: yaml)) { error in + AssertDecodingTypeMismatch(error) + } + } + + func testDecodeFloat16FromNull() throws { + + struct TestValue: Codable { + var float: Float16 + } + + let yaml = + """ + --- + float: null + ... + + """ + + XCTAssertThrowsError(try YAML.Decoder.default.decode(TestValue.self, from: yaml)) { error in + AssertDecodingValueNotFound(error) + } + } + + func testDecodeFloat16FromNullItem() throws { + + struct TestValue: Codable { + init(from decoder: Decoder) throws { + var container = try decoder.unkeyedContainer() + _ = try container.decode(Float16.self) + } + func encode(to encoder: Encoder) throws { + } + } + + XCTAssertThrowsError(try YAML.Decoder.default.decodeTree(TestValue.self, from: .sequence([nil]))) { error in + AssertDecodingValueNotFound(error) + } + } + + func testDecodeBigIntFromPositiveInteger() throws { + + let integer = 1234567 + + struct TestValue: Codable { + var bigInt: BigInt + } + + let yaml = + """ + --- + bigInt: \(integer) + ... + + """ + + let testValue = try YAML.Decoder.default.decode(TestValue.self, from: yaml) + XCTAssertEqual(testValue.bigInt, BigInt(integer)) + } + + func testDecodeBigIntFromNegativeInteger() throws { + + let integer = -1234567 + + struct TestValue: Codable { + var bigInt: BigInt + } + + let yaml = + """ + --- + bigInt: \(integer) + ... + + """ + + let testValue = try YAML.Decoder.default.decode(TestValue.self, from: yaml) + XCTAssertEqual(testValue.bigInt, BigInt(integer)) + } + + func testDecodeBigIntFromDecimalFails() throws { + + struct TestValue: Codable { + var bigInt: BigInt + } + + let yaml = + """ + --- + bigInt: 123.456 + ... + + """ + + XCTAssertThrowsError(try YAML.Decoder.default.decode(TestValue.self, from: yaml)) { error in + AssertDecodingTypeMismatch(error) + } + } + + func testDecodeBigIntFromNonNumber() throws { + + struct TestValue: Codable { + var bigInt: BigInt + } + + let yaml = + """ + --- + bigInt: Not a Number + ... + + """ + + XCTAssertThrowsError(try YAML.Decoder.default.decode(TestValue.self, from: yaml)) { error in + AssertDecodingTypeMismatch(error) + } + } + + func testDecodeBigIntFromNull() throws { + + struct TestValue: Codable { + var bigInt: BigInt + } + + let yaml = + """ + --- + bigInt: null + ... + + """ + + XCTAssertThrowsError(try YAML.Decoder.default.decode(TestValue.self, from: yaml)) { error in + AssertDecodingValueNotFound(error) + } + } + + func testDecodeBigIntFromNullItem() throws { + + struct TestValue: Codable { + init(from decoder: Decoder) throws { + var container = try decoder.unkeyedContainer() + _ = try container.decode(BigInt.self) + } + func encode(to encoder: Encoder) throws { + } + } + + XCTAssertThrowsError(try YAML.Decoder.default.decodeTree(TestValue.self, from: .sequence([nil]))) { error in + AssertDecodingValueNotFound(error) + } + } + + func testDecodeBigUIntFromInteger() throws { + + let integer = 1234567 + + struct TestValue: Codable { + var bigUInt: BigUInt + } + + let yaml = + """ + --- + bigUInt: \(integer) + ... + + """ + + let testValue = try YAML.Decoder.default.decode(TestValue.self, from: yaml) + XCTAssertEqual(testValue.bigUInt, BigUInt(integer)) + } + + func testDecodeBigUIntFromNegativeIntegerFails() throws { + + let integer = -1234567 + + struct TestValue: Codable { + var bigUInt: BigUInt + } + + let yaml = + """ + --- + bigUInt: \(integer) + ... + + """ + + XCTAssertThrowsError(try YAML.Decoder.default.decode(TestValue.self, from: yaml)) { error in + AssertDecodingTypeMismatch(error) + } + } + + func testDecodeBigUIntFromNonNumber() throws { + + struct TestValue: Codable { + var bigUInt: BigUInt + } + + let yaml = + """ + --- + bigUInt: Not a Number + ... + + """ + + XCTAssertThrowsError(try YAML.Decoder.default.decode(TestValue.self, from: yaml)) { error in + AssertDecodingTypeMismatch(error) + } + } + + func testDecodeBigUIntFromNull() throws { + + struct TestValue: Codable { + var bigUInt: BigUInt + } + + let yaml = + """ + --- + bigUInt: null + ... + + """ + + XCTAssertThrowsError(try YAML.Decoder.default.decode(TestValue.self, from: yaml)) { error in + AssertDecodingValueNotFound(error) + } + } + + func testDecodeBigUIntFromNullItem() throws { + + struct TestValue: Codable { + init(from decoder: Decoder) throws { + var container = try decoder.unkeyedContainer() + _ = try container.decode(BigUInt.self) + } + func encode(to encoder: Encoder) throws { + } + } + + XCTAssertThrowsError(try YAML.Decoder.default.decodeTree(TestValue.self, from: .sequence([nil]))) { error in + AssertDecodingValueNotFound(error) + } + } + + func testDecodeUIntFromPositiveInteger() throws { + + let integer = 1234567 + + struct TestValue: Codable { + var int: UInt + } + + let yaml = + """ + --- + int: \(integer) + ... + + """ + + let testValue = try YAML.Decoder.default.decode(TestValue.self, from: yaml) + XCTAssertEqual(testValue.int, UInt(integer)) + } + + func testDecodeUIntFromNegativeIntegerFails() throws { + + let integer = -1234567 + + struct TestValue: Codable { + var int: UInt + } + + let yaml = + """ + --- + int: \(integer) + ... + + """ + + XCTAssertThrowsError(try YAML.Decoder.default.decode(TestValue.self, from: yaml)) { error in + AssertDecodingTypeMismatch(error) + } + } + + func testDecodeUIntFromDecimalFails() throws { + + struct TestValue: Codable { + var int: UInt + } + + let yaml = + """ + --- + int: 123.456 + ... + + """ + + XCTAssertThrowsError(try YAML.Decoder.default.decode(TestValue.self, from: yaml)) { error in + AssertDecodingTypeMismatch(error) + } + } + + func testDecodeUIntFromNonNumber() throws { + + struct TestValue: Codable { + var int: UInt + } + + let yaml = + """ + --- + int: Not a Number + ... + + """ + + XCTAssertThrowsError(try YAML.Decoder.default.decode(TestValue.self, from: yaml)) { error in + AssertDecodingTypeMismatch(error) + } + } + + func testDecodeUIntFromNull() throws { + + struct TestValue: Codable { + var int: UInt + } + + let yaml = + """ + --- + int: null + ... + + """ + + XCTAssertThrowsError(try YAML.Decoder.default.decode(TestValue.self, from: yaml)) { error in + AssertDecodingValueNotFound(error) + } + } + + func testDecodeUIntFromNullItem() throws { + + struct TestValue: Codable { + init(from decoder: Decoder) throws { + var container = try decoder.unkeyedContainer() + _ = try container.decode(UInt.self) + } + func encode(to encoder: Encoder) throws { + } + } + + XCTAssertThrowsError(try YAML.Decoder.default.decodeTree(TestValue.self, from: .sequence([nil]))) { error in + AssertDecodingValueNotFound(error) + } + } + + func testDecodeUInt64FromPositiveInteger() throws { + + let integer = 1234567 + + struct TestValue: Codable { + var int: UInt64 + } + + let yaml = + """ + --- + int: \(integer) + ... + + """ + + let testValue = try YAML.Decoder.default.decode(TestValue.self, from: yaml) + XCTAssertEqual(testValue.int, UInt64(integer)) + } + + func testDecodeUInt64FromNegativeIntegerFails() throws { + + let integer = -1234567 + + struct TestValue: Codable { + var int: UInt64 + } + + let yaml = + """ + --- + int: \(integer) + ... + + """ + + XCTAssertThrowsError(try YAML.Decoder.default.decode(TestValue.self, from: yaml)) { error in + AssertDecodingTypeMismatch(error) + } + } + + func testDecodeUInt64FromDecimalFails() throws { + + struct TestValue: Codable { + var int: UInt64 + } + + let yaml = + """ + --- + int: 123.456 + ... + + """ + + XCTAssertThrowsError(try YAML.Decoder.default.decode(TestValue.self, from: yaml)) { error in + AssertDecodingTypeMismatch(error) + } + } + + func testDecodeUInt64FromNonNumber() throws { + + struct TestValue: Codable { + var int: UInt64 + } + + let yaml = + """ + --- + int: Not a Number + ... + + """ + + XCTAssertThrowsError(try YAML.Decoder.default.decode(TestValue.self, from: yaml)) { error in + AssertDecodingTypeMismatch(error) + } + } + + func testDecodeUInt64FromNull() throws { + + struct TestValue: Codable { + var int: UInt64 + } + + let yaml = + """ + --- + int: null + ... + + """ + + XCTAssertThrowsError(try YAML.Decoder.default.decode(TestValue.self, from: yaml)) { error in + AssertDecodingValueNotFound(error) + } + } + + func testDecodeUInt64FromNullItem() throws { + + struct TestValue: Codable { + init(from decoder: Decoder) throws { + var container = try decoder.unkeyedContainer() + _ = try container.decode(UInt64.self) + } + func encode(to encoder: Encoder) throws { + } + } + + XCTAssertThrowsError(try YAML.Decoder.default.decodeTree(TestValue.self, from: .sequence([nil]))) { error in + AssertDecodingValueNotFound(error) + } + } + + func testDecodeUInt32FromPositiveInteger() throws { + + let integer = 1234567 + + struct TestValue: Codable { + var int: UInt32 + } + + let yaml = + """ + --- + int: \(integer) + ... + + """ + + let testValue = try YAML.Decoder.default.decode(TestValue.self, from: yaml) + XCTAssertEqual(testValue.int, UInt32(integer)) + } + + func testDecodeUInt32FromNegativeIntegerFails() throws { + + let integer = -1234567 + + struct TestValue: Codable { + var int: UInt32 + } + + let yaml = + """ + --- + int: \(integer) + ... + + """ + + XCTAssertThrowsError(try YAML.Decoder.default.decode(TestValue.self, from: yaml)) { error in + AssertDecodingTypeMismatch(error) + } + } + + func testDecodeUInt32FromDecimalFails() throws { + + struct TestValue: Codable { + var int: UInt32 + } + + let yaml = + """ + --- + int: 123.456 + ... + + """ + + XCTAssertThrowsError(try YAML.Decoder.default.decode(TestValue.self, from: yaml)) { error in + AssertDecodingTypeMismatch(error) + } + } + + func testDecodeUInt32FromNonNumber() throws { + + struct TestValue: Codable { + var int: UInt32 + } + + let yaml = + """ + --- + int: Not a Number + ... + + """ + + XCTAssertThrowsError(try YAML.Decoder.default.decode(TestValue.self, from: yaml)) { error in + AssertDecodingTypeMismatch(error) + } + } + + func testDecodeUInt32FromNull() throws { + + struct TestValue: Codable { + var int: UInt32 + } + + let yaml = + """ + --- + int: null + ... + + """ + + XCTAssertThrowsError(try YAML.Decoder.default.decode(TestValue.self, from: yaml)) { error in + AssertDecodingValueNotFound(error) + } + } + + func testDecodeUInt32FromNullItem() throws { + + struct TestValue: Codable { + init(from decoder: Decoder) throws { + var container = try decoder.unkeyedContainer() + _ = try container.decode(UInt32.self) + } + func encode(to encoder: Encoder) throws { + } + } + + XCTAssertThrowsError(try YAML.Decoder.default.decodeTree(TestValue.self, from: .sequence([nil]))) { error in + AssertDecodingValueNotFound(error) + } + } + + func testDecodeUInt16FromPositiveInteger() throws { + + let integer = 65432 + + struct TestValue: Codable { + var int: UInt16 + } + + let yaml = + """ + --- + int: \(integer) + ... + + """ + + let testValue = try YAML.Decoder.default.decode(TestValue.self, from: yaml) + XCTAssertEqual(testValue.int, UInt16(integer)) + } + + func testDecodeUInt16FromNegativeIntegerFails() throws { + + let integer = -65432 + + struct TestValue: Codable { + var int: UInt16 + } + + let yaml = + """ + --- + int: \(integer) + ... + + """ + + XCTAssertThrowsError(try YAML.Decoder.default.decode(TestValue.self, from: yaml)) { error in + AssertDecodingTypeMismatch(error) + } + } + + func testDecodeUInt16FromDecimalFails() throws { + + struct TestValue: Codable { + var int: UInt16 + } + + let yaml = + """ + --- + int: 123.456 + ... + + """ + + XCTAssertThrowsError(try YAML.Decoder.default.decode(TestValue.self, from: yaml)) { error in + AssertDecodingTypeMismatch(error) + } + } + + func testDecodeUInt16FromNonNumber() throws { + + struct TestValue: Codable { + var int: UInt16 + } + + let yaml = + """ + --- + int: Not a Number + ... + + """ + + XCTAssertThrowsError(try YAML.Decoder.default.decode(TestValue.self, from: yaml)) { error in + AssertDecodingTypeMismatch(error) + } + } + + func testDecodeUInt16FromNull() throws { + + struct TestValue: Codable { + var int: UInt16 + } + + let yaml = + """ + --- + int: null + ... + + """ + + XCTAssertThrowsError(try YAML.Decoder.default.decode(TestValue.self, from: yaml)) { error in + AssertDecodingValueNotFound(error) + } + } + + func testDecodeUInt16FromNullItem() throws { + + struct TestValue: Codable { + init(from decoder: Decoder) throws { + var container = try decoder.unkeyedContainer() + _ = try container.decode(UInt16.self) + } + func encode(to encoder: Encoder) throws { + } + } + + XCTAssertThrowsError(try YAML.Decoder.default.decodeTree(TestValue.self, from: .sequence([nil]))) { error in + AssertDecodingValueNotFound(error) + } + } + + func testDecodeUInt8FromPositiveInteger() throws { + + let integer = 234 + + struct TestValue: Codable { + var int: UInt8 + } + + let yaml = + """ + --- + int: \(integer) + ... + + """ + + let testValue = try YAML.Decoder.default.decode(TestValue.self, from: yaml) + XCTAssertEqual(testValue.int, UInt8(integer)) + } + + func testDecodeUInt8FromNegativeIntegerFails() throws { + + let integer = -234 + + struct TestValue: Codable { + var int: UInt8 + } + + let yaml = + """ + --- + int: \(integer) + ... + + """ + + XCTAssertThrowsError(try YAML.Decoder.default.decode(TestValue.self, from: yaml)) { error in + AssertDecodingTypeMismatch(error) + } + } + + func testDecodeUInt8FromDecimalFails() throws { + + struct TestValue: Codable { + var int: UInt8 + } + + let yaml = + """ + --- + int: 123.456 + ... + + """ + + XCTAssertThrowsError(try YAML.Decoder.default.decode(TestValue.self, from: yaml)) { error in + AssertDecodingTypeMismatch(error) + } + } + + func testDecodeUInt8FromNonNumber() throws { + + struct TestValue: Codable { + var int: UInt8 + } + + let yaml = + """ + --- + int: Not a Number + ... + + """ + + XCTAssertThrowsError(try YAML.Decoder.default.decode(TestValue.self, from: yaml)) { error in + AssertDecodingTypeMismatch(error) + } + } + + func testDecodeUInt8FromNull() throws { + + struct TestValue: Codable { + var int: UInt8 + } + + let yaml = + """ + --- + int: null + ... + + """ + + XCTAssertThrowsError(try YAML.Decoder.default.decode(TestValue.self, from: yaml)) { error in + AssertDecodingValueNotFound(error) + } + } + + func testDecodeUInt8FromNullItem() throws { + + struct TestValue: Codable { + init(from decoder: Decoder) throws { + var container = try decoder.unkeyedContainer() + _ = try container.decode(UInt8.self) + } + func encode(to encoder: Encoder) throws { + } + } + + XCTAssertThrowsError(try YAML.Decoder.default.decodeTree(TestValue.self, from: .sequence([nil]))) { error in + AssertDecodingValueNotFound(error) + } + } + + func testDecodeIntFromPositiveInteger() throws { + + let integer = 1234567 + + struct TestValue: Codable { + var int: Int + } + + let yaml = + """ + --- + int: \(integer) + ... + + """ + + let testValue = try YAML.Decoder.default.decode(TestValue.self, from: yaml) + XCTAssertEqual(testValue.int, Int(integer)) + } + + func testDecodeIntFromNegativeInteger() throws { + + let integer = -1234567 + + struct TestValue: Codable { + var int: Int + } + + let yaml = + """ + --- + int: \(integer) + ... + + """ + + let testValue = try YAML.Decoder.default.decode(TestValue.self, from: yaml) + XCTAssertEqual(testValue.int, Int(integer)) + } + + func testDecodeIntFromOverflowFails() throws { + + struct TestValue: Codable { + var int: Int + } + + let yaml = + """ + --- + int: \(UInt(Int.max) + 1) + ... + + """ + + XCTAssertThrowsError(try YAML.Decoder.default.decode(TestValue.self, from: yaml)) { error in + AssertDecodingTypeMismatch(error) + } + } + + func testDecodeIntFromDecimalFails() throws { + + struct TestValue: Codable { + var int: Int + } + + let yaml = + """ + --- + int: 123.456 + ... + + """ + + XCTAssertThrowsError(try YAML.Decoder.default.decode(TestValue.self, from: yaml)) { error in + AssertDecodingTypeMismatch(error) + } + } + + func testDecodeIntFromNonNumber() throws { + + struct TestValue: Codable { + var int: Int + } + + let yaml = + """ + --- + int: Not a Number + ... + + """ + + XCTAssertThrowsError(try YAML.Decoder.default.decode(TestValue.self, from: yaml)) { error in + AssertDecodingTypeMismatch(error) + } + } + + func testDecodeIntFromNull() throws { + + struct TestValue: Codable { + var int: Int + } + + let yaml = + """ + --- + int: null + ... + + """ + + XCTAssertThrowsError(try YAML.Decoder.default.decode(TestValue.self, from: yaml)) { error in + AssertDecodingValueNotFound(error) + } + } + + func testDecodeIntFromNullItem() throws { + + struct TestValue: Codable { + init(from decoder: Decoder) throws { + var container = try decoder.unkeyedContainer() + _ = try container.decode(Int.self) + } + func encode(to encoder: Encoder) throws { + } + } + + XCTAssertThrowsError(try YAML.Decoder.default.decodeTree(TestValue.self, from: .sequence([nil]))) { error in + AssertDecodingValueNotFound(error) + } + } + + func testDecodeInt64FromPositiveInteger() throws { + + let integer = 1234567 + + struct TestValue: Codable { + var int: Int64 + } + + let yaml = + """ + --- + int: \(integer) + ... + + """ + + let testValue = try YAML.Decoder.default.decode(TestValue.self, from: yaml) + XCTAssertEqual(testValue.int, Int64(integer)) + } + + func testDecodeInt64FromNegativeIntegerFails() throws { + + let integer = -1234567 + + struct TestValue: Codable { + var int: Int64 + } + + let yaml = + """ + --- + int: \(integer) + ... + + """ + + let testValue = try YAML.Decoder.default.decode(TestValue.self, from: yaml) + XCTAssertEqual(testValue.int, Int64(integer)) + } + + func testDecodeInt64FromOverflowFails() throws { + + struct TestValue: Codable { + var int: Int64 + } + + let yaml = + """ + --- + int: \(UInt64(Int64.max) + 1) + ... + + """ + + XCTAssertThrowsError(try YAML.Decoder.default.decode(TestValue.self, from: yaml)) { error in + AssertDecodingTypeMismatch(error) + } + } + + func testDecodeInt64FromDecimalFails() throws { + + struct TestValue: Codable { + var int: Int64 + } + + let yaml = + """ + --- + int: 123.456 + ... + + """ + + XCTAssertThrowsError(try YAML.Decoder.default.decode(TestValue.self, from: yaml)) { error in + AssertDecodingTypeMismatch(error) + } + } + + func testDecodeInt64FromNonNumber() throws { + + struct TestValue: Codable { + var int: Int64 + } + + let yaml = + """ + --- + int: Not a Number + ... + + """ + + XCTAssertThrowsError(try YAML.Decoder.default.decode(TestValue.self, from: yaml)) { error in + AssertDecodingTypeMismatch(error) + } + } + + func testDecodeInt64FromNull() throws { + + struct TestValue: Codable { + var int: Int64 + } + + let yaml = + """ + --- + int: null + ... + + """ + + XCTAssertThrowsError(try YAML.Decoder.default.decode(TestValue.self, from: yaml)) { error in + AssertDecodingValueNotFound(error) + } + } + + func testDecodeInt64FromNullItem() throws { + + struct TestValue: Codable { + init(from decoder: Decoder) throws { + var container = try decoder.unkeyedContainer() + _ = try container.decode(Int64.self) + } + func encode(to encoder: Encoder) throws { + } + } + + XCTAssertThrowsError(try YAML.Decoder.default.decodeTree(TestValue.self, from: .sequence([nil]))) { error in + AssertDecodingValueNotFound(error) + } + } + + func testDecodeInt32FromPositiveInteger() throws { + + let integer = 1234567 + + struct TestValue: Codable { + var int: Int32 + } + + let yaml = + """ + --- + int: \(integer) + ... + + """ + + let testValue = try YAML.Decoder.default.decode(TestValue.self, from: yaml) + XCTAssertEqual(testValue.int, Int32(integer)) + } + + func testDecodeInt32FromNegativeInteger() throws { + + let integer = -1234567 + + struct TestValue: Codable { + var int: Int32 + } + + let yaml = + """ + --- + int: \(integer) + ... + + """ + + let testValue = try YAML.Decoder.default.decode(TestValue.self, from: yaml) + XCTAssertEqual(testValue.int, Int32(integer)) + } + + func testDecodeInt32FromOverflowFails() throws { + + struct TestValue: Codable { + var int: Int32 + } + + let yaml = + """ + --- + int: \(UInt32(Int32.max) + 1) + ... + + """ + + XCTAssertThrowsError(try YAML.Decoder.default.decode(TestValue.self, from: yaml)) { error in + AssertDecodingTypeMismatch(error) + } + } + + func testDecodeInt32FromDecimalFails() throws { + + struct TestValue: Codable { + var int: Int32 + } + + let yaml = + """ + --- + int: 123.456 + ... + + """ + + XCTAssertThrowsError(try YAML.Decoder.default.decode(TestValue.self, from: yaml)) { error in + AssertDecodingTypeMismatch(error) + } + } + + func testDecodeInt32FromNonNumber() throws { + + struct TestValue: Codable { + var int: Int32 + } + + let yaml = + """ + --- + int: Not a Number + ... + + """ + + XCTAssertThrowsError(try YAML.Decoder.default.decode(TestValue.self, from: yaml)) { error in + AssertDecodingTypeMismatch(error) + } + } + + func testDecodeInt32FromNull() throws { + + struct TestValue: Codable { + var int: Int32 + } + + let yaml = + """ + --- + int: null + ... + + """ + + XCTAssertThrowsError(try YAML.Decoder.default.decode(TestValue.self, from: yaml)) { error in + AssertDecodingValueNotFound(error) + } + } + + func testDecodeInt32FromNullItem() throws { + + struct TestValue: Codable { + init(from decoder: Decoder) throws { + var container = try decoder.unkeyedContainer() + _ = try container.decode(Int32.self) + } + func encode(to encoder: Encoder) throws { + } + } + + XCTAssertThrowsError(try YAML.Decoder.default.decodeTree(TestValue.self, from: .sequence([nil]))) { error in + AssertDecodingValueNotFound(error) + } + } + + func testDecodeInt16FromPositiveInteger() throws { + + let integer = 32101 + + struct TestValue: Codable { + var int: Int16 + } + + let yaml = + """ + --- + int: \(integer) + ... + + """ + + let testValue = try YAML.Decoder.default.decode(TestValue.self, from: yaml) + XCTAssertEqual(testValue.int, Int16(integer)) + } + + func testDecodeInt16FromNegativeInteger() throws { + + let integer = -32101 + + struct TestValue: Codable { + var int: Int16 + } + + let yaml = + """ + --- + int: \(integer) + ... + + """ + + let testValue = try YAML.Decoder.default.decode(TestValue.self, from: yaml) + XCTAssertEqual(testValue.int, Int16(integer)) + } + + func testDecodeInt16FromOverflowFails() throws { + + struct TestValue: Codable { + var int: Int16 + } + + let yaml = + """ + --- + int: \(UInt16(Int16.max) + 1) + ... + + """ + + XCTAssertThrowsError(try YAML.Decoder.default.decode(TestValue.self, from: yaml)) { error in + AssertDecodingTypeMismatch(error) + } + } + + func testDecodeInt16FromDecimalFails() throws { + + struct TestValue: Codable { + var int: Int16 + } + + let yaml = + """ + --- + int: 123.456 + ... + + """ + + XCTAssertThrowsError(try YAML.Decoder.default.decode(TestValue.self, from: yaml)) { error in + AssertDecodingTypeMismatch(error) + } + } + + func testDecodeInt16FromNonNumber() throws { + + struct TestValue: Codable { + var int: Int16 + } + + let yaml = + """ + --- + int: Not a Number + ... + + """ + + XCTAssertThrowsError(try YAML.Decoder.default.decode(TestValue.self, from: yaml)) { error in + AssertDecodingTypeMismatch(error) + } + } + + func testDecodeInt16FromNull() throws { + + struct TestValue: Codable { + var int: Int16 + } + + let yaml = + """ + --- + int: null + ... + + """ + + XCTAssertThrowsError(try YAML.Decoder.default.decode(TestValue.self, from: yaml)) { error in + AssertDecodingValueNotFound(error) + } + } + + func testDecodeInt16FromNullItem() throws { + + struct TestValue: Codable { + init(from decoder: Decoder) throws { + var container = try decoder.unkeyedContainer() + _ = try container.decode(Int16.self) + } + func encode(to encoder: Encoder) throws { + } + } + + XCTAssertThrowsError(try YAML.Decoder.default.decodeTree(TestValue.self, from: .sequence([nil]))) { error in + AssertDecodingValueNotFound(error) + } + } + + func testDecodeInt8FromPositiveInteger() throws { + + let integer = 123 + + struct TestValue: Codable { + var int: Int8 + } + + let yaml = + """ + --- + int: \(integer) + ... + + """ + + let testValue = try YAML.Decoder.default.decode(TestValue.self, from: yaml) + XCTAssertEqual(testValue.int, Int8(integer)) + } + + func testDecodeInt8FromNegativeInteger() throws { + + let integer = -123 + + struct TestValue: Codable { + var int: Int8 + } + + let yaml = + """ + --- + int: \(integer) + ... + + """ + + let testValue = try YAML.Decoder.default.decode(TestValue.self, from: yaml) + XCTAssertEqual(testValue.int, Int8(integer)) + } + + func testDecodeInt8FromOverflowFails() throws { + + struct TestValue: Codable { + var int: Int8 + } + + let yaml = + """ + --- + int: \(UInt8(Int8.max) + 1) + ... + + """ + + XCTAssertThrowsError(try YAML.Decoder.default.decode(TestValue.self, from: yaml)) { error in + AssertDecodingTypeMismatch(error) + } + } + + func testDecodeInt8FromDecimalFails() throws { + + struct TestValue: Codable { + var int: Int8 + } + + let yaml = + """ + --- + int: 123.456 + ... + + """ + + XCTAssertThrowsError(try YAML.Decoder.default.decode(TestValue.self, from: yaml)) { error in + AssertDecodingTypeMismatch(error) + } + } + + func testDecodeInt8FromNonNumber() throws { + + struct TestValue: Codable { + var int: Int8 + } + + let yaml = + """ + --- + int: Not a Number + ... + + """ + + XCTAssertThrowsError(try YAML.Decoder.default.decode(TestValue.self, from: yaml)) { error in + AssertDecodingTypeMismatch(error) + } + } + + func testDecodeInt8FromNull() throws { + + struct TestValue: Codable { + var int: Int8 + } + + let yaml = + """ + --- + int: null + ... + + """ + + XCTAssertThrowsError(try YAML.Decoder.default.decode(TestValue.self, from: yaml)) { error in + AssertDecodingValueNotFound(error) + } + } + + func testDecodeInt8FromNullItem() throws { + + struct TestValue: Codable { + init(from decoder: Decoder) throws { + var container = try decoder.unkeyedContainer() + _ = try container.decode(Int8.self) + } + func encode(to encoder: Encoder) throws { + } + } + + XCTAssertThrowsError(try YAML.Decoder.default.decodeTree(TestValue.self, from: .sequence([nil]))) { error in + AssertDecodingValueNotFound(error) + } + } + + func testDecodeBool() throws { + + struct TestValue: Codable { + var bool: Bool + } + + let yaml = + """ + --- + bool: true + ... + + """ + + let testValue = try YAML.Decoder.default.decode(TestValue.self, from: yaml) + XCTAssertEqual(testValue.bool, true) + } + + func testDecodeBoolFromNonBoolean() throws { + + struct TestValue: Codable { + var bool: Bool + } + + let yaml = + """ + --- + bool: 1 + ... + + """ + + XCTAssertThrowsError(try YAML.Decoder.default.decode(TestValue.self, from: yaml)) { error in + AssertDecodingTypeMismatch(error) + } + } + + func testDecodeBoolFromNull() throws { + + struct TestValue: Codable { + var bool: Bool + } + + let yaml = + """ + --- + bool: null + ... + + """ + + XCTAssertThrowsError(try YAML.Decoder.default.decode(TestValue.self, from: yaml)) { error in + AssertDecodingValueNotFound(error) + } + } + + func testDecodeBoolFromNullItem() throws { + + struct TestValue: Codable { + init(from decoder: Decoder) throws { + var container = try decoder.unkeyedContainer() + _ = try container.decode(Bool.self) + } + func encode(to encoder: Encoder) throws { + } + } + + XCTAssertThrowsError(try YAML.Decoder.default.decodeTree(TestValue.self, from: .sequence([nil]))) { error in + AssertDecodingValueNotFound(error) + } + } + + func testDecodeIntKeyedMap() throws { + + let yaml = + """ + 1: a + 2: b + """ + + XCTAssertEqual( + try YAML.Decoder.default.decode([String: String].self, from: yaml), + ["1": "a", "2": "b"] + ) + } + + func testDecodeInvalidKeyedMap() throws { + + let yaml = + """ + null: a + 2: b + """ + + XCTAssertThrowsError(try YAML.Decoder.default.decode([String: String].self, from: yaml)) { error in + AssertDecodingDataCorrupted(error) + } + } + +} diff --git a/Tests/YAMLEncoderTests.swift b/Tests/YAMLEncoderTests.swift new file mode 100644 index 000000000..5df03d358 --- /dev/null +++ b/Tests/YAMLEncoderTests.swift @@ -0,0 +1,1274 @@ +// +// YAMLEncoderTests.swift +// PotentCodables +// +// Copyright © 2021 Outfox, inc. +// +// +// Distributed under the MIT License, See LICENSE for details. +// + +import BigInt +import Foundation +import PotentCodables +import PotentYAML +import XCTest + + +class YAMLEncoderTests: XCTestCase { + + func testEncodeWithDefaultKeyStrategy() { + + struct TestValue: Codable { + var camelCased: String + } + + let yaml = + """ + --- + camelCased: Hello World! + ... + + """ + + let encoder = YAML.Encoder() + encoder.keyEncodingStrategy = .useDefaultKeys + + XCTAssertEqual(try encoder.encodeString(TestValue(camelCased: "Hello World!")), yaml) + } + + func testEncodeWithSnakeCaseKeyStrategy() { + + struct TestValue: Codable { + var snakeCased: String + } + + let yaml = + """ + --- + snake_cased: Hello World! + ... + + """ + + let encoder = YAML.Encoder() + encoder.keyEncodingStrategy = .convertToSnakeCase + + XCTAssertEqual(try encoder.encodeString(TestValue(snakeCased: "Hello World!")), yaml) + } + + func testEncodeWithCustomKeyStrategy() { + + struct TestValue: Codable { + var kebabCased: String + } + + let yaml = + """ + --- + kebab-cased: Hello World! + ... + + """ + + let encoder = YAML.Encoder() + encoder.keyEncodingStrategy = .custom { _ in + return AnyCodingKey(stringValue: "kebab-cased") + } + + XCTAssertEqual(try encoder.encodeString(TestValue(kebabCased: "Hello World!")), yaml) + } + + func testEncodeToString() throws { + + struct TestValue: Codable { + var int = 5 + var string = "Hello World!" + } + + let yaml = + """ + --- + int: 5 + string: Hello World! + ... + + """ + + let testYAML = try YAML.Encoder.default.encodeString(TestValue()) + XCTAssertEqual(testYAML, yaml) + } + + func testEncodeToStringWithSortedKeysOption() throws { + + struct TestValue: Codable { + var c = 1 + var a = 2 + var b = 3 + } + + let yaml = + """ + --- + a: 2 + b: 3 + c: 1 + ... + + """ + + let encoder = YAML.Encoder() + encoder.outputFormatting = .sortedKeys + + let testYAML = try encoder.encodeString(TestValue()) + XCTAssertEqual(testYAML, yaml) + } + + func testEncodeToData() throws { + + struct TestValue: Codable { + var int = 5 + var string = "Hello World!" + } + + let yaml = + """ + --- + int: 5 + string: Hello World! + ... + + """ + + let testYAMLData = try YAML.Encoder.default.encode(TestValue()) + XCTAssertEqual(testYAMLData, yaml.data(using: .utf8)!) + } + + func testEncodeToDataWithSortedKeysOption() throws { + + struct TestValue: Codable { + var c = 1 + var a = 2 + var b = 3 + } + + let yaml = + """ + --- + a: 2 + b: 3 + c: 1 + ... + + """ + + let encoder = YAML.Encoder() + encoder.outputFormatting = .sortedKeys + + let testYAMLData = try encoder.encode(TestValue()) + XCTAssertEqual(testYAMLData, yaml.data(using: .utf8)) + } + + func testEncodingChildContainers() throws { + + class TestValue: Codable { + var b: Int = 5 + var d: String = "Hello World!" + var a: Bool = true + var c: TestValue? + + init(c: TestValue? = nil) { + self.c = c + } + } + + let yaml = + """ + --- + b: 5 + d: Hello World! + a: true + c: + b: 5 + d: Hello World! + a: true + c: + b: 5 + d: Hello World! + a: true + ... + + """ + + let testValue = TestValue(c: TestValue(c: TestValue())) + + let testYAML = try YAML.Encoder.default.encodeString(testValue) + XCTAssertEqual(testYAML, yaml) + } + + func testEncodeDateAsISO8601() throws { + + let date = ZonedDate(date: Date(), timeZone: .utc) + let parsedDate = ZonedDate(iso8601Encoded: date.iso8601EncodedString())! + + struct TestValue: Codable { + var date: Date + } + + let yaml = + """ + --- + date: \(parsedDate.iso8601EncodedString()) + ... + + """ + + let encoder = YAML.Encoder() + encoder.dateEncodingStrategy = .iso8601 + + let testYAML = try encoder.encodeString(TestValue(date: parsedDate.utcDate)) + XCTAssertEqual(testYAML, yaml) + } + + func testEncodeDateAsEpochSeconds() throws { + + let date = Date() + + struct TestValue: Codable { + var date: Date + } + + let yaml = + """ + --- + date: \(date.timeIntervalSince1970) + ... + + """ + + let encoder = YAML.Encoder() + encoder.dateEncodingStrategy = .secondsSince1970 + + let testYAML = try encoder.encodeString(TestValue(date: date)) + XCTAssertEqual(testYAML, yaml) + } + + func testEncodeDateAsEpochMilliseconds() throws { + + let date = Date() + + struct TestValue: Codable { + var date: Date + } + + let yaml = + """ + --- + date: \(date.timeIntervalSince1970 * 1000.0) + ... + + """ + + let encoder = YAML.Encoder() + encoder.dateEncodingStrategy = .millisecondsSince1970 + + let testYAML = try encoder.encodeString(TestValue(date: date)) + XCTAssertEqual(testYAML, yaml) + } + + func testEncodeDateWithDeferredToDate() throws { + + let date = Date() + + struct TestValue: Codable { + var date: Date + } + + let yaml = + """ + --- + date: \(date.timeIntervalSinceReferenceDate) + ... + + """ + + let encoder = YAML.Encoder() + encoder.dateEncodingStrategy = .deferredToDate + + let testYAML = try encoder.encodeString(TestValue(date: date)) + XCTAssertEqual(testYAML, yaml) + } + + func testEncodeDateWithFormatter() throws { + + let formatter = DateFormatter() + formatter.dateFormat = "yyyy-MM-dd HH:mm:ss.SSS XXXX" + + let date = Date() + + struct TestValue: Codable { + var date: Date + } + + let yaml = + """ + --- + date: \(formatter.string(from: date)) + ... + + """ + + let encoder = YAML.Encoder() + encoder.dateEncodingStrategy = .formatted(formatter) + + let testYAML = try encoder.encodeString(TestValue(date: date)) + XCTAssertEqual(testYAML, yaml) + } + + func testEncodeDateWithCustomEncoder() throws { + + let formatter = DateFormatter() + formatter.dateFormat = "yyyy-MM-dd HH:mm:ss.SSS XXXX" + + let date = Date() + + struct TestValue: Codable { + var date: Date + } + + let yaml = + """ + --- + date: \(date.timeIntervalSinceReferenceDate) + ... + + """ + + let encoder = YAML.Encoder() + encoder.dateEncodingStrategy = .custom { date, encoder in + var container = encoder.singleValueContainer() + try container.encode(date.timeIntervalSinceReferenceDate) + } + + let testYAML = try encoder.encodeString(TestValue(date: date)) + XCTAssertEqual(testYAML, yaml) + } + + func testEncodeDateWithNothingCustomEncoder() throws { + + let formatter = DateFormatter() + formatter.dateFormat = "yyyy-MM-dd HH:mm:ss.SSS XXXX" + + let date = Date() + + struct TestValue: Codable { + var date: Date + } + + let yaml = + """ + --- + date: {} + ... + + """ + + let encoder = YAML.Encoder() + encoder.dateEncodingStrategy = .custom { _, _ in + // do nothing + } + + let testYAML = try encoder.encodeString(TestValue(date: date)) + XCTAssertEqual(testYAML, yaml) + } + + func testEncodeDataAsBase64() throws { + + let data = Data([1, 2, 3, 4, 5]) + + struct TestValue: Codable { + var data: Data + } + + let yaml = + """ + --- + data: \(data.base64EncodedString()) + ... + + """ + + let encoder = YAML.Encoder() + encoder.dataEncodingStrategy = .base64 + + let testYAML = try encoder.encodeString(TestValue(data: data)) + XCTAssertEqual(testYAML, yaml) + } + + func testEncodeDataWithDeferredToData() throws { + + let data = Data([1, 2, 3, 4, 5]) + + struct TestValue: Codable { + var data: Data + } + + let yaml = + """ + --- + data: + \(data.map { "- \($0)" }.joined(separator: "\n")) + ... + + """ + + let encoder = YAML.Encoder() + encoder.dataEncodingStrategy = .deferredToData + + let testYAML = try encoder.encodeString(TestValue(data: data)) + XCTAssertEqual(testYAML, yaml) + } + + func testEncodeDataWithCustomEncoder() throws { + + let data = Data([1, 2, 3, 4, 5]) + + struct TestValue: Codable { + var data: Data + } + + let yaml = + """ + --- + data: \(data.hexEncodedString()) + ... + + """ + + let encoder = YAML.Encoder() + encoder.dataEncodingStrategy = .custom { data, encoder in + var container = encoder.singleValueContainer() + try container.encode(data.hexEncodedString()) + } + + let testYAML = try encoder.encodeString(TestValue(data: data)) + XCTAssertEqual(testYAML, yaml) + } + + func testEncodeDataWithNothingCustomEncoder() throws { + + let data = Data([1, 2, 3, 4, 5]) + + struct TestValue: Codable { + var data: Data + } + + let yaml = + """ + --- + data: {} + ... + + """ + + let encoder = YAML.Encoder() + encoder.dataEncodingStrategy = .custom { _, _ in + } + + let testYAML = try encoder.encodeString(TestValue(data: data)) + XCTAssertEqual(testYAML, yaml) + } + + func testEncodePositiveBigInt() throws { + + let bigInt = BigInt(1234567) + + struct TestValue: Codable { + var bigInt: BigInt + } + + let yaml = + """ + --- + bigInt: \(bigInt) + ... + + """ + + let testYAML = try YAML.Encoder.default.encodeString(TestValue(bigInt: bigInt)) + XCTAssertEqual(testYAML, yaml) + } + + func testEncodeNegativeBigInt() throws { + + let bigInt = BigInt(-1234567) + + struct TestValue: Codable { + var bigInt: BigInt + } + + let yaml = + """ + --- + bigInt: \(bigInt) + ... + + """ + + let testYAML = try YAML.Encoder.default.encodeString(TestValue(bigInt: bigInt)) + XCTAssertEqual(testYAML, yaml) + } + + func testEncodeBigUInt() throws { + + let bigUInt = BigUInt(1234567) + + struct TestValue: Codable { + var bigUInt: BigUInt + } + + let yaml = + """ + --- + bigUInt: \(bigUInt) + ... + + """ + + let testYAML = try YAML.Encoder.default.encodeString(TestValue(bigUInt: bigUInt)) + XCTAssertEqual(testYAML, yaml) + } + + func testEncodePostiveDecimal() throws { + + let dec = Decimal(sign: .plus, exponent: -3, significand: 1234567) + + struct TestValue: Codable { + var dec: Decimal + } + + let yaml = + """ + --- + dec: \(dec) + ... + + """ + + let testYAML = try YAML.Encoder.default.encodeString(TestValue(dec: dec)) + XCTAssertEqual(testYAML, yaml) + } + + func testEncodeNegativeDecimal() throws { + + let dec = Decimal(sign: .minus, exponent: -3, significand: 1234567) + + struct TestValue: Codable { + var dec: Decimal + } + + let yaml = + """ + --- + dec: \(dec) + ... + + """ + + let testYAML = try YAML.Encoder.default.encodeString(TestValue(dec: dec)) + XCTAssertEqual(testYAML, yaml) + } + + func testEncodeDecimalNaN() throws { + + let dec = Decimal.nan + + struct TestValue: Codable { + var dec: Decimal + } + + let yaml = + """ + --- + dec: .nan + ... + + """ + + let testYAML = try YAMLEncoder.default.encodeString(TestValue(dec: dec)) + XCTAssertEqual(testYAML, yaml) + } + + func testEncodePostiveDouble() throws { + + let double = Double(sign: .plus, exponent: -3, significand: 1234567) + + struct TestValue: Codable { + var double: Double + } + + let yaml = + """ + --- + double: \(double) + ... + + """ + + let testYAML = try YAML.Encoder.default.encodeString(TestValue(double: double)) + XCTAssertEqual(testYAML, yaml) + } + + func testEncodeNegativeDouble() throws { + + let double = Double(sign: .minus, exponent: -3, significand: 1234567) + + struct TestValue: Codable { + var double: Double + } + + let yaml = + """ + --- + double: \(double) + ... + + """ + + let testYAML = try YAML.Encoder.default.encodeString(TestValue(double: double)) + XCTAssertEqual(testYAML, yaml) + } + + func testEncodeDoubleNaN() throws { + + let double = Double.nan + + struct TestValue: Codable { + var double: Double + } + + let yaml = + """ + --- + double: .nan + ... + + """ + + let testYAML = try YAMLEncoder.default.encodeString(TestValue(double: double)) + XCTAssertEqual(testYAML, yaml) + } + + func testEncodeDoublePositiveInfinity() throws { + + let double = Double.infinity + + struct TestValue: Codable { + var double: Double + } + + let yaml = + """ + --- + double: +.inf + ... + + """ + + let testYAML = try YAMLEncoder.default.encodeString(TestValue(double: double)) + XCTAssertEqual(testYAML, yaml) + } + + func testEncodeDoubleNegativeInfinity() throws { + + let double = -Double.infinity + + struct TestValue: Codable { + var double: Double + } + + let yaml = + """ + --- + double: -.inf + ... + + """ + + let testYAML = try YAMLEncoder.default.encodeString(TestValue(double: double)) + XCTAssertEqual(testYAML, yaml) + } + + func testEncodePostiveFloat() throws { + + let float = Float(sign: .plus, exponent: -3, significand: 1234567) + + struct TestValue: Codable { + var float: Float + } + + let yaml = + """ + --- + float: \(float) + ... + + """ + + let testYAML = try YAML.Encoder.default.encodeString(TestValue(float: float)) + XCTAssertEqual(testYAML, yaml) + } + + func testEncodeNegativeFloat() throws { + + let float = Float(sign: .minus, exponent: -3, significand: 1234567) + + struct TestValue: Codable { + var float: Float + } + + let yaml = + """ + --- + float: \(float) + ... + + """ + + let testYAML = try YAML.Encoder.default.encodeString(TestValue(float: float)) + XCTAssertEqual(testYAML, yaml) + } + + func testEncodeFloatNaN() throws { + + let float = Float.nan + + struct TestValue: Codable { + var float: Float + } + + let yaml = + """ + --- + float: .nan + ... + + """ + + let testYAML = try YAMLEncoder.default.encodeString(TestValue(float: float)) + XCTAssertEqual(testYAML, yaml) + } + + func testEncodeFloatPositiveInfinity() throws { + + let float = Float.infinity + + struct TestValue: Codable { + var float: Float + } + + let yaml = + """ + --- + float: +.inf + ... + + """ + + let testYAML = try YAMLEncoder.default.encodeString(TestValue(float: float)) + XCTAssertEqual(testYAML, yaml) + } + + func testEncodeFloatNegativeInfinity() throws { + + let float = -Float.infinity + + struct TestValue: Codable { + var float: Float + } + + let yaml = + """ + --- + float: -.inf + ... + + """ + + let testYAML = try YAMLEncoder.default.encodeString(TestValue(float: float)) + XCTAssertEqual(testYAML, yaml) + } + + func testEncodePostiveFloat16() throws { + + let float = Float16(sign: .plus, exponent: -3, significand: 1234) + + struct TestValue: Codable { + var float: Float16 + } + + let yaml = + """ + --- + float: \(float) + ... + + """ + + let testYAML = try YAML.Encoder.default.encodeString(TestValue(float: float)) + XCTAssertEqual(testYAML, yaml) + } + + func testEncodeNegativeFloat16() throws { + + let float = Float16(sign: .minus, exponent: -3, significand: 1234) + + struct TestValue: Codable { + var float: Float16 + } + + let yaml = + """ + --- + float: \(float) + ... + + """ + + let testYAML = try YAML.Encoder.default.encodeString(TestValue(float: float)) + XCTAssertEqual(testYAML, yaml) + } + + func testEncodeFloat16NaN() throws { + + let float = Float16.nan + + struct TestValue: Codable { + var float: Float16 + } + + let yaml = + """ + --- + float: .nan + ... + + """ + + let testYAML = try YAMLEncoder.default.encodeString(TestValue(float: float)) + XCTAssertEqual(testYAML, yaml) + } + + func testEncodeFloat16PositiveInfinity() throws { + + let float = Float16.infinity + + struct TestValue: Codable { + var float: Float16 + } + + let yaml = + """ + --- + float: +.inf + ... + + """ + + let testYAML = try YAMLEncoder.default.encodeString(TestValue(float: float)) + XCTAssertEqual(testYAML, yaml) + } + + func testEncodeFloat16NegativeInfinity() throws { + + let float = -Float16.infinity + + struct TestValue: Codable { + var float: Float16 + } + + let yaml = + """ + --- + float: -.inf + ... + + """ + + let testYAML = try YAMLEncoder.default.encodeString(TestValue(float: float)) + XCTAssertEqual(testYAML, yaml) + } + + func testEncodePositiveInt8() throws { + + let int = Int8.max + + struct TestValue: Codable { + var int: Int8 + } + + let yaml = + """ + --- + int: \(int) + ... + + """ + + let testYAML = try YAML.Encoder.default.encodeString(TestValue(int: int)) + XCTAssertEqual(testYAML, yaml) + } + + func testEncodeNegativeInt8() throws { + + let int = Int8.min + + struct TestValue: Codable { + var int: Int8 + } + + let yaml = + """ + --- + int: \(int) + ... + + """ + + let testYAML = try YAML.Encoder.default.encodeString(TestValue(int: int)) + XCTAssertEqual(testYAML, yaml) + } + + func testEncodePositiveInt16() throws { + + let int = Int16.max + + struct TestValue: Codable { + var int: Int16 + } + + let yaml = + """ + --- + int: \(int) + ... + + """ + + let testYAML = try YAML.Encoder.default.encodeString(TestValue(int: int)) + XCTAssertEqual(testYAML, yaml) + } + + func testEncodeNegativeInt16() throws { + + let int = Int16.min + + struct TestValue: Codable { + var int: Int16 + } + + let yaml = + """ + --- + int: \(int) + ... + + """ + + let testYAML = try YAML.Encoder.default.encodeString(TestValue(int: int)) + XCTAssertEqual(testYAML, yaml) + } + + func testEncodePositiveInt32() throws { + + let int = Int32.max + + struct TestValue: Codable { + var int: Int32 + } + + let yaml = + """ + --- + int: \(int) + ... + + """ + + let testYAML = try YAML.Encoder.default.encodeString(TestValue(int: int)) + XCTAssertEqual(testYAML, yaml) + } + + func testEncodeNegativeInt32() throws { + + let int = Int32.min + + struct TestValue: Codable { + var int: Int32 + } + + let yaml = + """ + --- + int: \(int) + ... + + """ + + let testYAML = try YAML.Encoder.default.encodeString(TestValue(int: int)) + XCTAssertEqual(testYAML, yaml) + } + + func testEncodePositiveInt64() throws { + + let int = Int64.max + + struct TestValue: Codable { + var int: Int64 + } + + let yaml = + """ + --- + int: \(int) + ... + + """ + + let testYAML = try YAML.Encoder.default.encodeString(TestValue(int: int)) + XCTAssertEqual(testYAML, yaml) + } + + func testEncodeNegativeInt64() throws { + + let int = Int64.min + + struct TestValue: Codable { + var int: Int64 + } + + let yaml = + """ + --- + int: \(int) + ... + + """ + + let testYAML = try YAML.Encoder.default.encodeString(TestValue(int: int)) + XCTAssertEqual(testYAML, yaml) + } + + func testEncodePositiveInt() throws { + + let int = Int.max + + struct TestValue: Codable { + var int: Int + } + + let yaml = + """ + --- + int: \(int) + ... + + """ + + let testYAML = try YAML.Encoder.default.encodeString(TestValue(int: int)) + XCTAssertEqual(testYAML, yaml) + } + + func testEncodeNegativeInt() throws { + + let int = Int.min + + struct TestValue: Codable { + var int: Int + } + + let yaml = + """ + --- + int: \(int) + ... + + """ + + let testYAML = try YAML.Encoder.default.encodeString(TestValue(int: int)) + XCTAssertEqual(testYAML, yaml) + } + + func testEncodeUInt8() throws { + + let uint = UInt8.max + + struct TestValue: Codable { + var uint: UInt8 + } + + let yaml = + """ + --- + uint: \(uint) + ... + + """ + + let testYAML = try YAML.Encoder.default.encodeString(TestValue(uint: uint)) + XCTAssertEqual(testYAML, yaml) + } + + func testEncodeUInt16() throws { + + let uint = UInt16.max + + struct TestValue: Codable { + var uint: UInt16 + } + + let yaml = + """ + --- + uint: \(uint) + ... + + """ + + let testYAML = try YAML.Encoder.default.encodeString(TestValue(uint: uint)) + XCTAssertEqual(testYAML, yaml) + } + + func testEncodeUInt32() throws { + + let uint = UInt32.max + + struct TestValue: Codable { + var uint: UInt32 + } + + let yaml = + """ + --- + uint: \(uint) + ... + + """ + + let testYAML = try YAML.Encoder.default.encodeString(TestValue(uint: uint)) + XCTAssertEqual(testYAML, yaml) + } + + func testEncodeUInt64() throws { + + let uint = UInt64.max + + struct TestValue: Codable { + var uint: UInt64 + } + + let yaml = + """ + --- + uint: \(uint) + ... + + """ + + let testYAML = try YAML.Encoder.default.encodeString(TestValue(uint: uint)) + XCTAssertEqual(testYAML, yaml) + } + + func testEncodeUInt() throws { + + let uint = UInt.max + + struct TestValue: Codable { + var uint: UInt + } + + let yaml = + """ + --- + uint: \(uint) + ... + + """ + + let testYAML = try YAML.Encoder.default.encodeString(TestValue(uint: uint)) + XCTAssertEqual(testYAML, yaml) + } + + func testEncodeURL() throws { + + let url = URL(string: "https://example.com/some/thing")! + + struct TestValue: Codable { + var url: URL + } + + let yaml = + """ + --- + url: \(url.absoluteString) + ... + + """ + + let testYAML = try YAML.Encoder.default.encodeString(TestValue(url: url)) + XCTAssertEqual(testYAML, yaml) + } + + func testEncodeUUID() throws { + + let uuid = UUID() + + struct TestValue: Codable { + var uuid: UUID + } + + let yaml = + """ + --- + uuid: \(uuid.uuidString) + ... + + """ + + let testYAML = try YAML.Encoder.default.encodeString(TestValue(uuid: uuid)) + XCTAssertEqual(testYAML, yaml) + } + + func testEncodeNil() throws { + + struct TestValue: Codable { + var null: Int? = .none + + func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encodeNil(forKey: .null) + } + } + + let yaml = + """ + --- + null: null + ... + + """ + + let testYAML = try YAML.Encoder.default.encodeString(TestValue(null: nil)) + XCTAssertEqual(testYAML, yaml) + } + + func testEncodeNonStringKeyedDictionary() throws { + + struct TestValue: Codable { + var dict: [Int: Int] + } + + XCTAssertNoThrow(try YAML.Encoder.default.encodeString(TestValue(dict: [1: 1]))) + } + +} diff --git a/Tests/YAMLOrderTests.swift b/Tests/YAMLOrderTests.swift new file mode 100644 index 000000000..695fe1e24 --- /dev/null +++ b/Tests/YAMLOrderTests.swift @@ -0,0 +1,106 @@ +// +// YAMLOrderTests.swift +// PotentCodables +// +// Copyright © 2021 Outfox, inc. +// +// +// Distributed under the MIT License, See LICENSE for details. +// + +import BigInt +import Foundation +import PotentCodables +import PotentYAML +import XCTest + + +class YAMLOrderTests: XCTestCase { + + func testObjectKeySerializationExplicitOrder() throws { + + let yaml = try YAMLSerialization.string(from: [ + "c": 1, + "a": 2, + "b": 3, + ]) + + XCTAssertEqual( + yaml.description, + """ + --- + c: 1 + a: 2 + b: 3 + ... + + """ + ) + } + + func testObjectKeySerializationSortedOrder() throws { + + let yaml = try YAMLSerialization.string(from: [ + "c": 1, + "a": 2, + "b": 3, + ], options: .sortedKeys) + + XCTAssertEqual( + yaml.description, + """ + --- + a: 2 + b: 3 + c: 1 + ... + + """ + ) + } + + func testObjectKeyDeserializationOrder() throws { + + let mapping: YAML = [ + "c": 1, + "a": 2, + "b": 3, + ] + + XCTAssertEqual( + mapping, + try YAMLSerialization.yaml(from: + """ + --- + c: 1 + a: 2 + b: 3 + ... + + """ + ) + ) + } + + func testDescriptionOrder() throws { + + let yaml: YAML = [ + "c": 1, + "a": 2, + "b": 3, + ] + + XCTAssertEqual( + yaml.description, + """ + --- + c: 1 + a: 2 + b: 3 + ... + + """ + ) + } + +} diff --git a/Tests/YAMLReaderTests.swift b/Tests/YAMLReaderTests.swift new file mode 100644 index 000000000..30db61f3e --- /dev/null +++ b/Tests/YAMLReaderTests.swift @@ -0,0 +1,150 @@ +// +// YAMLReaderTests.swift +// PotentCodables +// +// Copyright © 2021 Outfox, inc. +// +// +// Distributed under the MIT License, See LICENSE for details. +// + +import Foundation +import PotentYAML +import XCTest + + +class YAMLReaderTests: XCTestCase { + + func testReadMultipleDocuments() throws { + + let yaml = + """ + --- + a: 1 + --- + b: 2 + """ + + let values = try YAMLReader.read(data: yaml.data(using: .utf8)!) + + XCTAssertEqual(values.count, 2) + XCTAssertEqual(values[0], ["a": 1] as YAML) + XCTAssertEqual(values[1], ["b": 2] as YAML) + } + + func testReadTaggedNull() throws { + + let yaml = + """ + a: !!null null + b: !!null "literall anything" + """ + + let values = try YAMLReader.read(data: yaml.data(using: .utf8)!) + + XCTAssertEqual(values.count, 1) + XCTAssertEqual(values[0]["a"], YAML.null()) + XCTAssertEqual(values[0]["b"], YAML.null()) + } + + func testReadTaggedBool() throws { + + let yaml = + """ + a: !!bool true + b: !!bool false + c: !!bool "true" + d: !!bool "false" + e: !!bool 1 + f: !!bool 0 + """ + + let values = try YAMLReader.read(data: yaml.data(using: .utf8)!) + + XCTAssertEqual(values.count, 1) + XCTAssertEqual(values[0]["a"], .bool(true)) + XCTAssertEqual(values[0]["b"], .bool(false)) + XCTAssertEqual(values[0]["c"], .bool(true)) + XCTAssertEqual(values[0]["d"], .bool(false)) + XCTAssertEqual(values[0]["e"], .bool(true)) + XCTAssertEqual(values[0]["f"], .bool(false)) + } + + func testReadInvalidTaggedBool() throws { + + let yaml = + """ + a: !!bool something + """ + + XCTAssertThrowsError(try YAMLReader.read(data: yaml.data(using: .utf8)!)) + } + + func testReadTaggedInt() throws { + + let yaml = + """ + a: !!int 123 + b: !!int "123" + """ + + let values = try YAMLReader.read(data: yaml.data(using: .utf8)!) + + XCTAssertEqual(values.count, 1) + XCTAssertEqual(values[0]["a"]?.integerValue, 123) + XCTAssertEqual(values[0]["b"]?.integerValue, 123) + } + + func testReadTaggedFloat() throws { + + let yaml = + """ + a: !!float 123.456 + b: !!float "123.456" + """ + + let values = try YAMLReader.read(data: yaml.data(using: .utf8)!) + + XCTAssertEqual(values.count, 1) + XCTAssertEqual(values[0]["a"]?.doubleValue, 123.456) + XCTAssertEqual(values[0]["b"]?.doubleValue, 123.456) + } + + func testReadTaggedString() throws { + + let yaml = + """ + a: !!str 123 + """ + + let values = try YAMLReader.read(data: yaml.data(using: .utf8)!) + + XCTAssertEqual(values.count, 1) + XCTAssertEqual(values[0]["a"]?.stringValue, "123") + } + + func testReadAlias() throws { + + let yaml = + """ + a: *num + """ + + let values = try YAMLReader.read(data: yaml.data(using: .utf8)!) + + XCTAssertEqual(values.count, 1) + XCTAssertEqual(values[0]["a"], .alias("num")) + } + + func testReaderThrowsParserErrors() throws { + + let yaml = + """ + a: "a", + b: 2 + """ + + XCTAssertThrowsError(try YAMLReader.read(data: yaml.data(using: .utf8)!)) + } + +} diff --git a/Tests/YAMLTests.swift b/Tests/YAMLTests.swift index 56b397abe..253115ac1 100644 --- a/Tests/YAMLTests.swift +++ b/Tests/YAMLTests.swift @@ -150,8 +150,8 @@ class YAMLTests: XCTestCase { } func testEncodingChildContainers() throws { - let yamlText = try YAMLEncoder().encodeTree(objects).stableText - XCTAssertEqual(yamlText, values.stableText) + let yamlText = try YAMLEncoder().encodeTree(objects).description + XCTAssertEqual(yamlText, values.description) } func testObjectKeySerializationExplicitOrder() throws { diff --git a/Tests/YAMLValueTests.swift b/Tests/YAMLValueTests.swift new file mode 100644 index 000000000..24395f70a --- /dev/null +++ b/Tests/YAMLValueTests.swift @@ -0,0 +1,210 @@ +// +// YAMLValueTests.swift +// PotentCodables +// +// Copyright © 2021 Outfox, inc. +// +// +// Distributed under the MIT License, See LICENSE for details. +// + + +import BigInt +import Foundation +import PotentCodables +import PotentYAML +import XCTest + + +class YAMLValueTests: XCTestCase { + + func testNilLiteralInitializer() { + let json: YAML = nil + XCTAssertTrue(json.isNull) + } + + func testBoolLiteralInitializer() { + let json: YAML = true + XCTAssertEqual(json.boolValue, true) + } + + func testStringLiteralInitializer() { + let json: YAML = "Hello World!" + XCTAssertEqual(json.stringValue, "Hello World!") + } + + func testFloatLiteralInitializer() { + let json: YAML = 1.234 + XCTAssertEqual(json.doubleValue, 1.234) + } + + func testArrayLiteralInitializer() { + let json: YAML = [1, 2, 3] + XCTAssertEqual(json.sequenceValue, [1, 2, 3]) + } + + func testDictionaryLiteralInitializer() { + let json: YAML = ["b": 1, "a": 2, "c": 3] + XCTAssertEqual(json.mappingValue, (["b": 1, "a": 2, "c": 3] as YAML).mappingValue) + } + + func testYAMLNumberStringLiteralInitializer() { + let number: YAML.Number = "1.234" + XCTAssertEqual(number.doubleValue, 1.234) + } + + func testYAMLNumberFloatLiteralInitializer() { + let number: YAML.Number = 1.234 + XCTAssertEqual(number.doubleValue, 1.234) + } + + func testYAMLNumberIntegerLiteralInitializer() { + let number: YAML.Number = 1234 + XCTAssertEqual(number.integerValue, 1234) + } + + func testDynamicMemberSubscript() { + let object: YAML = [ + "aaa": 1, + ] + XCTAssertEqual(object.aaa, .integer(1)) + XCTAssertEqual(object.bbb, nil) + XCTAssertEqual(YAML.null().aaa, nil) + } + + func testIntSubscript() { + let array: YAML = [ + "a", "b", "c" + ] + XCTAssertEqual(array[1], .string("b")) + XCTAssertEqual(array[3], nil) + XCTAssertEqual(YAML.null()[1], nil) + } + + func testStringSubscript() { + let object: YAML = [ + "aaa": 1, + ] + XCTAssertEqual(object["aaa"], .integer(1)) + XCTAssertEqual(object["bbb"], nil) + XCTAssertEqual(YAML.null()["aaa"], nil) + } + + func testYAMLSubscript() { + let object: YAML = [ + "aaa": 1, + ] + XCTAssertEqual(object[.string("aaa")], .integer(1)) + XCTAssertEqual(object[.string("bbb")], nil) + XCTAssertEqual(YAML.null()[.string("aaa")], nil) + } + + func testNullAccessor() { + XCTAssertTrue(YAML.null().isNull) + XCTAssertFalse(YAML.integer(5).isNull) + } + + func testStringAccessor() { + XCTAssertEqual(YAML.string("Hello World!").stringValue, "Hello World!") + XCTAssertNil(YAML.integer(5).stringValue) + } + + func testBoolAccessor() { + XCTAssertEqual(YAML.bool(true).boolValue, true) + XCTAssertNil(YAML.integer(5).boolValue) + } + + func testNumberAccessor() { + XCTAssertEqual(YAML.integer(5).numberValue as? Int, 5) + XCTAssertEqual(YAML.float(5.5).numberValue as? Double, 5.5) + XCTAssertNil(YAML.bool(false).numberValue) + } + + func testIntegerAccessor() { + XCTAssertEqual(YAML.integer(5).integerValue, 5) + XCTAssertEqual(YAML.integer(-5).integerValue, -5) + XCTAssertNil(YAML.float(5.3).integerValue) + XCTAssertNil(YAML.bool(false).integerValue) + } + + func testUnsignedIntegerAccessor() { + XCTAssertEqual(YAML.integer(5).unsignedIntegerValue, 5) + XCTAssertNil(YAML.float(5.3).unsignedIntegerValue) + XCTAssertNil(YAML.bool(false).unsignedIntegerValue) + } + + func testFloatAccessor() { + XCTAssertEqual(YAML.float(5.3).floatValue, 5.3) + XCTAssertNil(YAML.bool(false).floatValue) + } + + func testDoubleAccessor() { + XCTAssertEqual(YAML.float(5.3).doubleValue, 5.3) + XCTAssertNil(YAML.bool(false).doubleValue) + } + + func testSequenceAccessor() { + XCTAssertEqual(YAML.sequence([1, 2, 3]).sequenceValue, [1, 2, 3] as YAML.Sequence) + XCTAssertNil(YAML.bool(false).sequenceValue) + } + + func testMappingAccessor() { + XCTAssertEqual( + (["a": 1, "b": 2, "c": 3] as YAML).mappingValue, + YAML.Mapping([.init(key: "a", value: 1), .init(key: "b", value: 2), .init(key: "c", value: 3)]) + ) + XCTAssertNil(YAML.bool(false).mappingValue) + } + + func testUnwrap() { + XCTAssertNil(YAML.null().unwrapped) + XCTAssertEqual(YAML.bool(true).unwrapped as? Bool, true) + XCTAssertEqual(YAML.string("Hello World!").unwrapped as? String, "Hello World!") + XCTAssertEqual(YAML.integer(5).unwrapped as? Int, 5) + XCTAssertEqual(YAML.float(5.5).unwrapped as? Double, 5.5) + XCTAssertEqual(YAML.sequence([1, 2, 3]).unwrapped as? [Int], [1, 2, 3]) + XCTAssertEqual((["a": 1, "b": 2, "c": 3] as YAML).unwrapped as? [String: Int], ["a": 1, "b": 2, "c": 3]) + } + + func testEquatable() { + XCTAssertEqual(YAML.null(anchor: "a"), YAML.null()) + XCTAssertEqual(YAML.bool(true, anchor: "a"), YAML.bool(true)) + XCTAssertEqual(YAML.string("aaa", anchor: "a"), YAML.string("aaa")) + XCTAssertEqual(YAML.integer(3, anchor: "a"), YAML.integer(3)) + XCTAssertEqual(YAML.float(1.23, anchor: "a"), YAML.float(1.23)) + XCTAssertEqual(YAML.sequence([1, 2, 3], anchor: "a"), YAML.sequence([1, 2, 3])) + XCTAssertEqual( + YAML.mapping(YAML.Mapping([.init(key: "a", value: 1), .init(key: "b", value: 2), .init(key: "c", value: 3)]), anchor: "a"), + YAML.mapping(YAML.Mapping([.init(key: "a", value: 1), .init(key: "b", value: 2), .init(key: "c", value: 3)])) + ) + XCTAssertEqual(YAML.alias("token"), YAML.alias("token")) + XCTAssertNotEqual(YAML.string("aaa"), YAML.null()) + } + + func testTags() { + XCTAssertEqual(YAML.Tag(rawValue: "test")?.rawValue, "test") + XCTAssertEqual(YAML.Tag("test").rawValue, "test") + XCTAssertEqual(YAML.Tag("test" as String?)?.rawValue, "test") + XCTAssertEqual(YAML.Tag(nil), nil) + } + + func testNumberAccessors() { + XCTAssertTrue(YAML.Number("123").isInteger) + XCTAssertTrue(YAML.Number("-123").isInteger) + XCTAssertFalse(YAML.Number("123.456").isInteger) + + XCTAssertEqual(YAML.Number("123").integerValue, 123) + XCTAssertEqual(YAML.Number("123").unsignedIntegerValue, 123) + XCTAssertEqual(YAML.Number("-123").integerValue, -123) + XCTAssertNil(YAML.Number("-123").unsignedIntegerValue) + XCTAssertNil(YAML.Number("123.456").integerValue) + XCTAssertNil(YAML.Number("123.456").unsignedIntegerValue) + + XCTAssertFalse(YAML.Number("123.456").isInteger) + + XCTAssertEqual(YAML.Number("123.456").floatValue, 123.456) + XCTAssertEqual(YAML.Number("-123.456").floatValue, -123.456) + XCTAssertTrue(YAML.Number("-123").isInteger) + } + +} diff --git a/Tests/YAMLWriterTests.swift b/Tests/YAMLWriterTests.swift new file mode 100644 index 000000000..2d2d5c06e --- /dev/null +++ b/Tests/YAMLWriterTests.swift @@ -0,0 +1,47 @@ +// +// YAMLWriterTests.swift +// PotentCodables +// +// Copyright © 2021 Outfox, inc. +// +// +// Distributed under the MIT License, See LICENSE for details. +// + +import Foundation +@testable import PotentYAML +import XCTest + + +class YAMLWriterTests: XCTestCase { + + func testWriteMultipleDocuments() throws { + + var output = "" + try YAMLWriter.write([.bool(true), .integer(123)]) { output += $0 ?? "" } + + XCTAssertEqual( + output, + """ + --- true + ... + --- 123 + ... + + """ + ) + } + + func testWriteAliases() throws { + + XCTAssertEqual( + try YAMLSerialization.string(from: .alias("num")), + """ + --- *num + ... + + """ + ) + } + +}