| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932 |
- // This file is derived from swift/test/stdlib/TestJSONEncoder.swift
- // Copyright (c) 2014 - 2017 Apple Inc. and the Swift project authors
- // Licensed under Apache License v2.0 with Runtime Library Exception
- //
- // See https://swift.org/LICENSE.txt for license information
- // See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
- //
- //===----------------------------------------------------------------------===//
- import FirebaseSharedSwift
- import Swift
- import Foundation
- // MARK: - Test Suite
- import XCTest
- class TestFirebaseDataEncoder: XCTestCase {
- // MARK: - Encoding Top-Level Empty Types
- func testEncodingTopLevelEmptyStruct() {
- let empty = EmptyStruct()
- _testRoundTrip(of: empty, expected: _emptyDictionary)
- }
- func testEncodingTopLevelEmptyClass() {
- let empty = EmptyClass()
- _testRoundTrip(of: empty, expected: _emptyDictionary)
- }
- // MARK: - Encoding Top-Level Single-Value Types
- func testEncodingTopLevelSingleValueEnum() {
- _testRoundTrip(of: Switch.off)
- _testRoundTrip(of: Switch.on)
- }
- func testEncodingTopLevelSingleValueStruct() {
- _testRoundTrip(of: Timestamp(3_141_592_653))
- }
- func testEncodingTopLevelSingleValueClass() {
- _testRoundTrip(of: Counter())
- }
- // MARK: - Encoding Top-Level Structured Types
- func testEncodingTopLevelStructuredStruct() {
- // Address is a struct type with multiple fields.
- let address = Address.testValue
- _testRoundTrip(of: address)
- }
- func testEncodingTopLevelStructuredClass() {
- // Person is a class with multiple fields.
- let expected = ["name": "Johnny Appleseed", "email": "appleseed@apple.com"]
- let person = Person.testValue
- _testRoundTrip(of: person, expected: expected)
- }
- func testEncodingTopLevelStructuredSingleStruct() {
- // Numbers is a struct which encodes as an array through a single value container.
- let numbers = Numbers.testValue
- _testRoundTrip(of: numbers)
- }
- func testEncodingTopLevelStructuredSingleClass() {
- // Mapping is a class which encodes as a dictionary through a single value container.
- let mapping = Mapping.testValue
- _testRoundTrip(of: mapping)
- }
- func testEncodingTopLevelDeepStructuredType() {
- // Company is a type with fields which are Codable themselves.
- let company = Company.testValue
- _testRoundTrip(of: company)
- }
- func testEncodingClassWhichSharesEncoderWithSuper() {
- // Employee is a type which shares its encoder & decoder with its superclass, Person.
- let employee = Employee.testValue
- _testRoundTrip(of: employee)
- }
- func testEncodingTopLevelNullableType() {
- // EnhancedBool is a type which encodes either as a Bool or as nil.
- _testRoundTrip(of: EnhancedBool.true, expected: true)
- _testRoundTrip(of: EnhancedBool.false, expected: false)
- _testRoundTrip(of: EnhancedBool.fileNotFound, expected: NSNull())
- }
- func testEncodingMultipleNestedContainersWithTheSameTopLevelKey() {
- struct Model: Codable, Equatable {
- let first: String
- let second: String
- init(from coder: Decoder) throws {
- let container = try coder.container(keyedBy: TopLevelCodingKeys.self)
- let firstNestedContainer = try container.nestedContainer(
- keyedBy: FirstNestedCodingKeys.self,
- forKey: .top
- )
- first = try firstNestedContainer.decode(String.self, forKey: .first)
- let secondNestedContainer = try container.nestedContainer(
- keyedBy: SecondNestedCodingKeys.self,
- forKey: .top
- )
- second = try secondNestedContainer.decode(String.self, forKey: .second)
- }
- func encode(to encoder: Encoder) throws {
- var container = encoder.container(keyedBy: TopLevelCodingKeys.self)
- var firstNestedContainer = container.nestedContainer(
- keyedBy: FirstNestedCodingKeys.self,
- forKey: .top
- )
- try firstNestedContainer.encode(first, forKey: .first)
- var secondNestedContainer = container.nestedContainer(
- keyedBy: SecondNestedCodingKeys.self,
- forKey: .top
- )
- try secondNestedContainer.encode(second, forKey: .second)
- }
- init(first: String, second: String) {
- self.first = first
- self.second = second
- }
- static var testValue: Model {
- return Model(first: "Johnny Appleseed",
- second: "appleseed@apple.com")
- }
- enum TopLevelCodingKeys: String, CodingKey {
- case top
- }
- enum FirstNestedCodingKeys: String, CodingKey {
- case first
- }
- enum SecondNestedCodingKeys: String, CodingKey {
- case second
- }
- }
- let model = Model.testValue
- if #available(OSX 10.13, iOS 11.0, watchOS 4.0, tvOS 11.0, *) {
- let expected = ["top": ["first": "Johnny Appleseed", "second": "appleseed@apple.com"]]
- _testRoundTrip(of: model, expected: expected)
- } else {
- _testRoundTrip(of: model)
- }
- }
- // NOTE: This test causes a precondition error, so I am uncertain about how it ought to be tested.
- // func testEncodingConflictedTypeNestedContainersWithTheSameTopLevelKey() {
- // struct Model : Encodable, Equatable {
- // let first: String
- //
- // func encode(to encoder: Encoder) throws {
- // var container = encoder.container(keyedBy: TopLevelCodingKeys.self)
- //
- // var firstNestedContainer = container.nestedContainer(keyedBy: FirstNestedCodingKeys.self, forKey: .top)
- // try firstNestedContainer.encode(self.first, forKey: .first)
- //
- // // The following line would fail as it attempts to re-encode into already encoded container is invalid. This will always fail
- // var secondNestedContainer = container.nestedUnkeyedContainer(forKey: .top)
- // try secondNestedContainer.encode("second")
- // }
- //
- // init(first: String) {
- // self.first = first
- // }
- //
- // static var testValue: Model {
- // return Model(first: "Johnny Appleseed")
- // }
- //
- // enum TopLevelCodingKeys : String, CodingKey {
- // case top
- // }
- // enum FirstNestedCodingKeys : String, CodingKey {
- // case first
- // }
- // }
- //
- // let model = Model.testValue
- // // This following test would fail as it attempts to re-encode into already encoded container is invalid. This will always fail
- // if #available(OSX 10.13, iOS 11.0, watchOS 4.0, tvOS 11.0, *) {
- // _testEncodeFailure(of: model)
- // } else {
- // _testEncodeFailure(of: model)
- // }
- // }
- // MARK: - Date Strategy Tests
- // Disabled for now till we resolve rdar://52618414
- func x_testEncodingDate() {
- func formattedLength(of value: Double) -> Int {
- let empty = UnsafeMutablePointer<Int8>.allocate(capacity: 0)
- defer { empty.deallocate() }
- let length = snprintf(ptr: empty, 0, "%0.*g", DBL_DECIMAL_DIG, value)
- return Int(length)
- }
- // Duplicated to handle a special case
- func localTestRoundTrip<T: Codable & Equatable>(of value: T) {
- var payload: Any! = nil
- do {
- let encoder = FirebaseDataEncoder()
- payload = try encoder.encode(value)
- } catch {
- XCTFail("Failed to encode \(T.self): \(error)")
- }
- do {
- let decoder = FirebaseDataDecoder()
- let decoded = try decoder.decode(T.self, from: payload!)
- /// `snprintf`'s `%g`, which `JSONSerialization` uses internally for double values, does not respect
- /// our precision requests in every case. This bug effects Darwin, FreeBSD, and Linux currently
- /// causing this test (which uses the current time) to fail occasionally.
- if formattedLength(of: (decoded as! Date).timeIntervalSinceReferenceDate) >
- DBL_DECIMAL_DIG +
- 2 {
- let adjustedTimeIntervalSinceReferenceDate: (Date) -> Double = { date in
- let adjustment = pow(10, Double(DBL_DECIMAL_DIG))
- return Double(floor(adjustment * date.timeIntervalSinceReferenceDate)
- .rounded() / adjustment)
- }
- let decodedAprox = adjustedTimeIntervalSinceReferenceDate(decoded as! Date)
- let valueAprox = adjustedTimeIntervalSinceReferenceDate(value as! Date)
- XCTAssertEqual(
- decodedAprox,
- valueAprox,
- "\(T.self) did not round-trip to an equal value after DBL_DECIMAL_DIG adjustment \(decodedAprox) != \(valueAprox)."
- )
- return
- }
- XCTAssertEqual(
- decoded,
- value,
- "\(T.self) did not round-trip to an equal value. \((decoded as! Date).timeIntervalSinceReferenceDate) != \((value as! Date).timeIntervalSinceReferenceDate)"
- )
- } catch {
- XCTFail("Failed to decode \(T.self): \(error)")
- }
- }
- // Test the above `snprintf` edge case evaluation with a known triggering case
- let knownBadDate = Date(timeIntervalSinceReferenceDate: 0.0021413276231263384)
- localTestRoundTrip(of: knownBadDate)
- localTestRoundTrip(of: Date())
- // Optional dates should encode the same way.
- localTestRoundTrip(of: Optional(Date()))
- }
- func testEncodingDateSecondsSince1970() {
- // Cannot encode an arbitrary number of seconds since we've lost precision since 1970.
- let seconds = 1000.0
- let expected = 1000
- _testRoundTrip(of: Date(timeIntervalSince1970: seconds),
- expected: expected,
- dateEncodingStrategy: .secondsSince1970,
- dateDecodingStrategy: .secondsSince1970)
- // Optional dates should encode the same way.
- _testRoundTrip(of: Optional(Date(timeIntervalSince1970: seconds)),
- expected: expected,
- dateEncodingStrategy: .secondsSince1970,
- dateDecodingStrategy: .secondsSince1970)
- }
- func testEncodingDateMillisecondsSince1970() {
- // Cannot encode an arbitrary number of seconds since we've lost precision since 1970.
- let seconds = 1000.0
- let expected = 1_000_000
- _testRoundTrip(of: Date(timeIntervalSince1970: seconds),
- expected: expected,
- dateEncodingStrategy: .millisecondsSince1970,
- dateDecodingStrategy: .millisecondsSince1970)
- // Optional dates should encode the same way.
- _testRoundTrip(of: Optional(Date(timeIntervalSince1970: seconds)),
- expected: expected,
- dateEncodingStrategy: .millisecondsSince1970,
- dateDecodingStrategy: .millisecondsSince1970)
- }
- func testEncodingDateISO8601() {
- if #available(OSX 10.12, iOS 10.0, watchOS 3.0, tvOS 10.0, *) {
- let formatter = ISO8601DateFormatter()
- formatter.formatOptions = .withInternetDateTime
- let timestamp = Date(timeIntervalSince1970: 1000)
- let expected = "\(formatter.string(from: timestamp))"
- _testRoundTrip(of: timestamp,
- expected: expected,
- dateEncodingStrategy: .iso8601,
- dateDecodingStrategy: .iso8601)
- // Optional dates should encode the same way.
- _testRoundTrip(of: Optional(timestamp),
- expected: expected,
- dateEncodingStrategy: .iso8601,
- dateDecodingStrategy: .iso8601)
- }
- }
- func testEncodingDateFormatted() {
- let formatter = DateFormatter()
- formatter.dateStyle = .full
- formatter.timeStyle = .full
- let timestamp = Date(timeIntervalSince1970: 1000)
- let expected = "\(formatter.string(from: timestamp))"
- _testRoundTrip(of: timestamp,
- expected: expected,
- dateEncodingStrategy: .formatted(formatter),
- dateDecodingStrategy: .formatted(formatter))
- // Optional dates should encode the same way.
- _testRoundTrip(of: Optional(timestamp),
- expected: expected,
- dateEncodingStrategy: .formatted(formatter),
- dateDecodingStrategy: .formatted(formatter))
- }
- func testEncodingDateCustom() {
- let timestamp = Date()
- // We'll encode a number instead of a date.
- let encode = { (_ data: Date, _ encoder: Encoder) throws -> Void in
- var container = encoder.singleValueContainer()
- try container.encode(42)
- }
- let decode = { (_: Decoder) throws -> Date in timestamp }
- let expected = 42
- _testRoundTrip(of: timestamp,
- expected: expected,
- dateEncodingStrategy: .custom(encode),
- dateDecodingStrategy: .custom(decode))
- // Optional dates should encode the same way.
- _testRoundTrip(of: Optional(timestamp),
- expected: expected,
- dateEncodingStrategy: .custom(encode),
- dateDecodingStrategy: .custom(decode))
- }
- func testEncodingDateCustomEmpty() {
- let timestamp = Date()
- // Encoding nothing should encode an empty keyed container ({}).
- let encode = { (_: Date, _: Encoder) throws -> Void in }
- let decode = { (_: Decoder) throws -> Date in timestamp }
- let expected: [String: String] = [:]
- _testRoundTrip(of: timestamp,
- expected: expected,
- dateEncodingStrategy: .custom(encode),
- dateDecodingStrategy: .custom(decode))
- // Optional dates should encode the same way.
- _testRoundTrip(of: Optional(timestamp),
- expected: expected,
- dateEncodingStrategy: .custom(encode),
- dateDecodingStrategy: .custom(decode))
- }
- // MARK: - Data Strategy Tests
- func testEncodingData() {
- let data = Data([0xDE, 0xAD, 0xBE, 0xEF])
- let expected = [222, 173, 190, 239]
- _testRoundTrip(of: data,
- expected: expected,
- dataEncodingStrategy: .deferredToData,
- dataDecodingStrategy: .deferredToData)
- // Optional data should encode the same way.
- _testRoundTrip(of: Optional(data),
- expected: expected,
- dataEncodingStrategy: .deferredToData,
- dataDecodingStrategy: .deferredToData)
- }
- func testEncodingDataBase64() {
- let data = Data([0xDE, 0xAD, 0xBE, 0xEF])
- let expected = "3q2+7w=="
- _testRoundTrip(of: data, expected: expected)
- // Optional data should encode the same way.
- _testRoundTrip(of: Optional(data), expected: expected)
- }
- func testEncodingDataBlob() {
- let data = Data([0xDE, 0xAD, 0xBE, 0xEF])
- _testRoundTrip(of: data,
- expected: data,
- dataEncodingStrategy: .blob,
- dataDecodingStrategy: .blob)
- // Optional data should encode the same way.
- _testRoundTrip(of: Optional(data),
- expected: data,
- dataEncodingStrategy: .blob,
- dataDecodingStrategy: .blob)
- }
- func testEncodingData2Blob() {
- let string = "abcdef"
- let data = string.data(using: .utf8)!
- _testRoundTrip(of: data,
- expected: data,
- dataEncodingStrategy: .blob,
- dataDecodingStrategy: .blob)
- // Optional data should encode the same way.
- _testRoundTrip(of: Optional(data),
- expected: data,
- dataEncodingStrategy: .blob,
- dataDecodingStrategy: .blob)
- }
- func testEncodingDataCustom() {
- // We'll encode a number instead of data.
- let encode = { (_ data: Data, _ encoder: Encoder) throws -> Void in
- var container = encoder.singleValueContainer()
- try container.encode(42)
- }
- let decode = { (_: Decoder) throws -> Data in Data() }
- let expected = 42
- _testRoundTrip(of: Data(),
- expected: expected,
- dataEncodingStrategy: .custom(encode),
- dataDecodingStrategy: .custom(decode))
- // Optional data should encode the same way.
- _testRoundTrip(of: Optional(Data()),
- expected: expected,
- dataEncodingStrategy: .custom(encode),
- dataDecodingStrategy: .custom(decode))
- }
- func testEncodingDataCustomEmpty() {
- // Encoding nothing should encode an empty keyed container ({}).
- let encode = { (_: Data, _: Encoder) throws -> Void in }
- let decode = { (_: Decoder) throws -> Data in Data() }
- _testRoundTrip(of: Data(),
- expected: [:] as [String: String],
- dataEncodingStrategy: .custom(encode),
- dataDecodingStrategy: .custom(decode))
- // Optional Data should encode the same way.
- _testRoundTrip(of: Optional(Data()),
- expected: [:] as [String: String],
- dataEncodingStrategy: .custom(encode),
- dataDecodingStrategy: .custom(decode))
- }
- // Tests implicit migration of data that was written with .base64 (String type) using Firestore
- // 10.0 through 10.3 (see PR #10604).
- func testDecodingBase64StringAsBlobData() throws {
- let inputString = "abcdef"
- let data = inputString.data(using: .utf8)!
- let base64String = "YWJjZGVm"
- let encoder = FirebaseDataEncoder()
- encoder.dataEncodingStrategy = .base64
- let payload = try encoder.encode(data)
- XCTAssertEqual(
- base64String,
- try XCTUnwrap(payload as? String, "Encoding did not produce a \(String.self)."),
- "Encoding \"\(inputString)\" did not produce expected the base64 string \"\(base64String)\"."
- )
- let decoder = FirebaseDataDecoder()
- decoder.dataDecodingStrategy = .blob
- let decoded = try decoder.decode(Data.self, from: payload)
- XCTAssertEqual(
- inputString,
- try XCTUnwrap(String(data: decoded, encoding: .utf8)),
- "Decoding base64-encoded payload did not produce original value \"\(inputString)\"."
- )
- }
- // MARK: - Non-Conforming Floating Point Strategy Tests
- func testEncodingNonConformingFloats() {
- _testEncodeFailure(of: Float.infinity)
- _testEncodeFailure(of: Float.infinity)
- _testEncodeFailure(of: -Float.infinity)
- _testEncodeFailure(of: Float.nan)
- _testEncodeFailure(of: Double.infinity)
- _testEncodeFailure(of: -Double.infinity)
- _testEncodeFailure(of: Double.nan)
- // Optional Floats/Doubles should encode the same way.
- _testEncodeFailure(of: Float.infinity)
- _testEncodeFailure(of: -Float.infinity)
- _testEncodeFailure(of: Float.nan)
- _testEncodeFailure(of: Double.infinity)
- _testEncodeFailure(of: -Double.infinity)
- _testEncodeFailure(of: Double.nan)
- }
- func testEncodingNonConformingFloatStrings() {
- let encodingStrategy: FirebaseDataEncoder.NonConformingFloatEncodingStrategy = .convertToString(
- positiveInfinity: "INF",
- negativeInfinity: "-INF",
- nan: "NaN"
- )
- let decodingStrategy: FirebaseDataDecoder.NonConformingFloatDecodingStrategy = .convertFromString(
- positiveInfinity: "INF",
- negativeInfinity: "-INF",
- nan: "NaN"
- )
- _testRoundTrip(of: Float.infinity,
- expected: "INF",
- nonConformingFloatEncodingStrategy: encodingStrategy,
- nonConformingFloatDecodingStrategy: decodingStrategy)
- _testRoundTrip(of: -Float.infinity,
- expected: "-INF",
- nonConformingFloatEncodingStrategy: encodingStrategy,
- nonConformingFloatDecodingStrategy: decodingStrategy)
- // Since Float.nan != Float.nan, we have to use a placeholder that'll encode NaN but actually round-trip.
- _testRoundTrip(of: FloatNaNPlaceholder(),
- expected: "NaN",
- nonConformingFloatEncodingStrategy: encodingStrategy,
- nonConformingFloatDecodingStrategy: decodingStrategy)
- _testRoundTrip(of: Double.infinity,
- expected: "INF",
- nonConformingFloatEncodingStrategy: encodingStrategy,
- nonConformingFloatDecodingStrategy: decodingStrategy)
- _testRoundTrip(of: -Double.infinity,
- expected: "-INF",
- nonConformingFloatEncodingStrategy: encodingStrategy,
- nonConformingFloatDecodingStrategy: decodingStrategy)
- // Since Double.nan != Double.nan, we have to use a placeholder that'll encode NaN but actually round-trip.
- _testRoundTrip(of: DoubleNaNPlaceholder(),
- expected: "NaN",
- nonConformingFloatEncodingStrategy: encodingStrategy,
- nonConformingFloatDecodingStrategy: decodingStrategy)
- // Optional Floats and Doubles should encode the same way.
- _testRoundTrip(of: Optional(Float.infinity),
- expected: "INF",
- nonConformingFloatEncodingStrategy: encodingStrategy,
- nonConformingFloatDecodingStrategy: decodingStrategy)
- _testRoundTrip(of: Optional(-Float.infinity),
- expected: "-INF",
- nonConformingFloatEncodingStrategy: encodingStrategy,
- nonConformingFloatDecodingStrategy: decodingStrategy)
- _testRoundTrip(of: Optional(Double.infinity),
- expected: "INF",
- nonConformingFloatEncodingStrategy: encodingStrategy,
- nonConformingFloatDecodingStrategy: decodingStrategy)
- _testRoundTrip(of: Optional(-Double.infinity),
- expected: "-INF",
- nonConformingFloatEncodingStrategy: encodingStrategy,
- nonConformingFloatDecodingStrategy: decodingStrategy)
- }
- // MARK: - Key Strategy Tests
- private struct EncodeMe: Encodable {
- var keyName: String
- func encode(to coder: Encoder) throws {
- var c = coder.container(keyedBy: _TestKey.self)
- try c.encode("test", forKey: _TestKey(stringValue: keyName)!)
- }
- }
- func testEncodingKeyStrategySnake() {
- let toSnakeCaseTests = [
- ("simpleOneTwo", "simple_one_two"),
- ("myURL", "my_url"),
- ("singleCharacterAtEndX", "single_character_at_end_x"),
- ("thisIsAnXMLProperty", "this_is_an_xml_property"),
- ("single", "single"), // no underscore
- ("", ""), // don't die on empty string
- ("a", "a"), // single character
- ("aA", "a_a"), // two characters
- ("version4Thing", "version4_thing"), // numerics
- ("partCAPS", "part_caps"), // only insert underscore before first all caps
- ("partCAPSLowerAGAIN", "part_caps_lower_again"), // switch back and forth caps.
- ("manyWordsInThisThing", "many_words_in_this_thing"), // simple lowercase + underscore + more
- ("asdfĆqer", "asdf_ćqer"),
- ("already_snake_case", "already_snake_case"),
- ("dataPoint22", "data_point22"),
- ("dataPoint22Word", "data_point22_word"),
- ("_oneTwoThree", "_one_two_three"),
- ("oneTwoThree_", "one_two_three_"),
- ("__oneTwoThree", "__one_two_three"),
- ("oneTwoThree__", "one_two_three__"),
- ("_oneTwoThree_", "_one_two_three_"),
- ("__oneTwoThree", "__one_two_three"),
- ("__oneTwoThree__", "__one_two_three__"),
- ("_test", "_test"),
- ("_test_", "_test_"),
- ("__test", "__test"),
- ("test__", "test__"),
- ("m͉̟̹y̦̳G͍͚͎̳r̤͉̤͕ͅea̲͕t͇̥̼͖U͇̝̠R͙̻̥͓̣L̥̖͎͓̪̫ͅR̩͖̩eq͈͓u̞e̱s̙t̤̺ͅ", "m͉̟̹y̦̳_g͍͚͎̳r̤͉̤͕ͅea̲͕t͇̥̼͖_u͇̝̠r͙̻̥͓̣l̥̖͎͓̪̫ͅ_r̩͖̩eq͈͓u̞e̱s̙t̤̺ͅ"), // because Itai wanted to test this
- ("🐧🐟", "🐧🐟"), // fishy emoji example?
- ]
- for test in toSnakeCaseTests {
- let expected = ["\(test.1)": "test"]
- let encoded = EncodeMe(keyName: test.0)
- let encoder = FirebaseDataEncoder()
- encoder.keyEncodingStrategy = .convertToSnakeCase
- let result = try! encoder.encode(encoded)
- XCTAssertEqual(expected, result as? [String: String])
- }
- }
- func testEncodingKeyStrategyCustom() {
- let expected = ["QQQhello": "test"]
- let encoded = EncodeMe(keyName: "hello")
- let encoder = FirebaseDataEncoder()
- let customKeyConversion = { (_ path: [CodingKey]) -> CodingKey in
- let key = _TestKey(stringValue: "QQQ" + path.last!.stringValue)!
- return key
- }
- encoder.keyEncodingStrategy = .custom(customKeyConversion)
- let result = try! encoder.encode(encoded)
- XCTAssertEqual(expected, result as? [String: String])
- }
- func testEncodingDictionaryStringKeyConversionUntouched() {
- let toEncode = ["leaveMeAlone": "test"]
- let encoder = FirebaseDataEncoder()
- encoder.keyEncodingStrategy = .convertToSnakeCase
- let result = try! encoder.encode(toEncode)
- XCTAssertEqual(toEncode, result as? [String: String])
- }
- private struct EncodeFailure: Encodable {
- var someValue: Double
- }
- private struct EncodeFailureNested: Encodable {
- var nestedValue: EncodeFailure
- }
- func testEncodingDictionaryFailureKeyPath() {
- let toEncode: [String: EncodeFailure] = ["key": EncodeFailure(someValue: Double.nan)]
- let encoder = FirebaseDataEncoder()
- encoder.keyEncodingStrategy = .convertToSnakeCase
- do {
- _ = try encoder.encode(toEncode)
- } catch let EncodingError.invalidValue(_, context) {
- XCTAssertEqual(2, context.codingPath.count)
- XCTAssertEqual("key", context.codingPath[0].stringValue)
- XCTAssertEqual("someValue", context.codingPath[1].stringValue)
- } catch {
- XCTFail("Unexpected error: \(String(describing: error))")
- }
- }
- func testEncodingDictionaryFailureKeyPathNested() {
- let toEncode: [String: [String: EncodeFailureNested]] =
- ["key": ["sub_key": EncodeFailureNested(nestedValue: EncodeFailure(someValue: Double.nan))]]
- let encoder = FirebaseDataEncoder()
- encoder.keyEncodingStrategy = .convertToSnakeCase
- do {
- _ = try encoder.encode(toEncode)
- } catch let EncodingError.invalidValue(_, context) {
- XCTAssertEqual(4, context.codingPath.count)
- XCTAssertEqual("key", context.codingPath[0].stringValue)
- XCTAssertEqual("sub_key", context.codingPath[1].stringValue)
- XCTAssertEqual("nestedValue", context.codingPath[2].stringValue)
- XCTAssertEqual("someValue", context.codingPath[3].stringValue)
- } catch {
- XCTFail("Unexpected error: \(String(describing: error))")
- }
- }
- private struct EncodeNested: Encodable {
- let nestedValue: EncodeMe
- }
- private struct EncodeNestedNested: Encodable {
- let outerValue: EncodeNested
- }
- func testEncodingKeyStrategyPath() {
- // Make sure a more complex path shows up the way we want
- // Make sure the path reflects keys in the Swift, not the resulting ones in the structure
- let expected = ["QQQouterValue": ["QQQnestedValue": ["QQQhelloWorld": "test"]]]
- let encoded =
- EncodeNestedNested(outerValue: EncodeNested(nestedValue: EncodeMe(keyName: "helloWorld")))
- let encoder = FirebaseDataEncoder()
- var callCount = 0
- let customKeyConversion = { (_ path: [CodingKey]) -> CodingKey in
- // This should be called three times:
- // 1. to convert 'outerValue' to something
- // 2. to convert 'nestedValue' to something
- // 3. to convert 'helloWorld' to something
- callCount = callCount + 1
- if path.count == 0 {
- XCTFail("The path should always have at least one entry")
- } else if path.count == 1 {
- XCTAssertEqual(["outerValue"], path.map { $0.stringValue })
- } else if path.count == 2 {
- XCTAssertEqual(["outerValue", "nestedValue"], path.map { $0.stringValue })
- } else if path.count == 3 {
- XCTAssertEqual(["outerValue", "nestedValue", "helloWorld"], path.map { $0.stringValue })
- } else {
- XCTFail("The path mysteriously had more entries")
- }
- let key = _TestKey(stringValue: "QQQ" + path.last!.stringValue)!
- return key
- }
- encoder.keyEncodingStrategy = .custom(customKeyConversion)
- let result = try! encoder.encode(encoded)
- XCTAssertEqual(expected, result as? [String: [String: [String: String]]])
- XCTAssertEqual(3, callCount)
- }
- private struct DecodeMe: Decodable {
- let found: Bool
- init(from coder: Decoder) throws {
- let c = try coder.container(keyedBy: _TestKey.self)
- // Get the key that we expect to be passed in (camel case)
- let camelCaseKey = try c.decode(String.self, forKey: _TestKey(stringValue: "camelCaseKey")!)
- // Use the camel case key to decode from the structure. The decoder should convert it to snake case to find it.
- found = try c.decode(Bool.self, forKey: _TestKey(stringValue: camelCaseKey)!)
- }
- }
- func testDecodingKeyStrategyCamel() {
- let fromSnakeCaseTests = [
- ("", ""), // don't die on empty string
- ("a", "a"), // single character
- ("ALLCAPS", "ALLCAPS"), // If no underscores, we leave the word as-is
- ("ALL_CAPS", "allCaps"), // Conversion from screaming snake case
- ("single", "single"), // do not capitalize anything with no underscore
- ("snake_case", "snakeCase"), // capitalize a character
- ("one_two_three", "oneTwoThree"), // more than one word
- ("one_2_three", "one2Three"), // numerics
- ("one2_three", "one2Three"), // numerics, part 2
- ("snake_Ćase", "snakeĆase"), // do not further modify a capitalized diacritic
- ("snake_ćase", "snakeĆase"), // capitalize a diacritic
- ("alreadyCamelCase", "alreadyCamelCase"), // do not modify already camel case
- ("__this_and_that", "__thisAndThat"),
- ("_this_and_that", "_thisAndThat"),
- ("this__and__that", "thisAndThat"),
- ("this_and_that__", "thisAndThat__"),
- ("this_aNd_that", "thisAndThat"),
- ("_one_two_three", "_oneTwoThree"),
- ("one_two_three_", "oneTwoThree_"),
- ("__one_two_three", "__oneTwoThree"),
- ("one_two_three__", "oneTwoThree__"),
- ("_one_two_three_", "_oneTwoThree_"),
- ("__one_two_three", "__oneTwoThree"),
- ("__one_two_three__", "__oneTwoThree__"),
- ("_test", "_test"),
- ("_test_", "_test_"),
- ("__test", "__test"),
- ("test__", "test__"),
- ("_", "_"),
- ("__", "__"),
- ("___", "___"),
- ("m͉̟̹y̦̳G͍͚͎̳r̤͉̤͕ͅea̲͕t͇̥̼͖U͇̝̠R͙̻̥͓̣L̥̖͎͓̪̫ͅR̩͖̩eq͈͓u̞e̱s̙t̤̺ͅ", "m͉̟̹y̦̳G͍͚͎̳r̤͉̤͕ͅea̲͕t͇̥̼͖U͇̝̠R͙̻̥͓̣L̥̖͎͓̪̫ͅR̩͖̩eq͈͓u̞e̱s̙t̤̺ͅ"), // because Itai wanted to test this
- ("🐧_🐟", "🐧🐟"), // fishy emoji example?
- ]
- for test in fromSnakeCaseTests {
- // This structure contains the camel case key that the test object should decode with, then it uses the snake case key (test.0) as the actual key for the boolean value.
- let input = ["camelCaseKey": "\(test.1)", "\(test.0)": true] as [String: Any]
- let decoder = FirebaseDataDecoder()
- decoder.keyDecodingStrategy = .convertFromSnakeCase
- let result = try! decoder.decode(DecodeMe.self, from: input)
- XCTAssertTrue(result.found)
- }
- }
- private struct DecodeMe2: Decodable { var hello: String }
- func testDecodingKeyStrategyCustom() {
- let input = ["----hello": "test"]
- let decoder = FirebaseDataDecoder()
- let customKeyConversion = { (_ path: [CodingKey]) -> CodingKey in
- // This converter removes the first 4 characters from the start of all string keys, if it has more than 4 characters
- let string = path.last!.stringValue
- guard string.count > 4 else { return path.last! }
- let newString = String(string.dropFirst(4))
- return _TestKey(stringValue: newString)!
- }
- decoder.keyDecodingStrategy = .custom(customKeyConversion)
- let result = try! decoder.decode(DecodeMe2.self, from: input)
- XCTAssertEqual("test", result.hello)
- }
- func testDecodingDictionaryStringKeyConversionUntouched() {
- let input = ["leave_me_alone": "test"]
- let decoder = FirebaseDataDecoder()
- decoder.keyDecodingStrategy = .convertFromSnakeCase
- let result = try! decoder.decode([String: String].self, from: input)
- XCTAssertEqual(["leave_me_alone": "test"], result)
- }
- func testDecodingDictionaryFailureKeyPath() {
- let input = ["leave_me_alone": "test"]
- let decoder = FirebaseDataDecoder()
- decoder.keyDecodingStrategy = .convertFromSnakeCase
- do {
- _ = try decoder.decode([String: Int].self, from: input)
- } catch let DecodingError.typeMismatch(_, context) {
- XCTAssertEqual(1, context.codingPath.count)
- XCTAssertEqual("leave_me_alone", context.codingPath[0].stringValue)
- } catch {
- XCTFail("Unexpected error: \(String(describing: error))")
- }
- }
- private struct DecodeFailure: Decodable {
- var intValue: Int
- }
- private struct DecodeFailureNested: Decodable {
- var nestedValue: DecodeFailure
- }
- func testDecodingDictionaryFailureKeyPathNested() {
- let input = ["top_level": ["sub_level": ["nested_value": ["int_value": "not_an_int"]]]]
- let decoder = FirebaseDataDecoder()
- decoder.keyDecodingStrategy = .convertFromSnakeCase
- do {
- _ = try decoder.decode([String: [String: DecodeFailureNested]].self, from: input)
- } catch let DecodingError.typeMismatch(_, context) {
- XCTAssertEqual(4, context.codingPath.count)
- XCTAssertEqual("top_level", context.codingPath[0].stringValue)
- XCTAssertEqual("sub_level", context.codingPath[1].stringValue)
- XCTAssertEqual("nestedValue", context.codingPath[2].stringValue)
- XCTAssertEqual("intValue", context.codingPath[3].stringValue)
- } catch {
- XCTFail("Unexpected error: \(String(describing: error))")
- }
- }
- private struct DecodeMe3: Codable {
- var thisIsCamelCase: String
- }
- func testEncodingKeyStrategySnakeGenerated() {
- // Test that this works with a struct that has automatically generated keys
- let input = ["this_is_camel_case": "test"]
- let decoder = FirebaseDataDecoder()
- decoder.keyDecodingStrategy = .convertFromSnakeCase
- let result = try! decoder.decode(DecodeMe3.self, from: input)
- XCTAssertEqual("test", result.thisIsCamelCase)
- }
- func testDecodingKeyStrategyCamelGenerated() {
- let encoded = DecodeMe3(thisIsCamelCase: "test")
- let encoder = FirebaseDataEncoder()
- encoder.keyEncodingStrategy = .convertToSnakeCase
- let result = try! encoder.encode(encoded)
- XCTAssertEqual(["this_is_camel_case": "test"], result as? [String: String])
- }
- func testKeyStrategySnakeGeneratedAndCustom() {
- // Test that this works with a struct that has automatically generated keys
- struct DecodeMe4: Codable {
- var thisIsCamelCase: String
- var thisIsCamelCaseToo: String
- private enum CodingKeys: String, CodingKey {
- case thisIsCamelCase = "fooBar"
- case thisIsCamelCaseToo
- }
- }
- // Decoding
- let input = ["foo_bar": "test", "this_is_camel_case_too": "test2"]
- let decoder = FirebaseDataDecoder()
- decoder.keyDecodingStrategy = .convertFromSnakeCase
- let decodingResult = try! decoder.decode(DecodeMe4.self, from: input)
- XCTAssertEqual("test", decodingResult.thisIsCamelCase)
- XCTAssertEqual("test2", decodingResult.thisIsCamelCaseToo)
- // Encoding
- let encoded = DecodeMe4(thisIsCamelCase: "test", thisIsCamelCaseToo: "test2")
- let encoder = FirebaseDataEncoder()
- encoder.keyEncodingStrategy = .convertToSnakeCase
- let encodingResult = try! encoder.encode(encoded)
- XCTAssertEqual(
- ["foo_bar": "test", "this_is_camel_case_too": "test2"],
- encodingResult as? [String: String]
- )
- }
- func testKeyStrategyDuplicateKeys() {
- // This test is mostly to make sure we don't assert on duplicate keys
- struct DecodeMe5: Codable {
- var oneTwo: String
- var numberOfKeys: Int
- enum CodingKeys: String, CodingKey {
- case oneTwo
- case oneTwoThree
- }
- init() {
- oneTwo = "test"
- numberOfKeys = 0
- }
- init(from decoder: Decoder) throws {
- let container = try decoder.container(keyedBy: CodingKeys.self)
- oneTwo = try container.decode(String.self, forKey: .oneTwo)
- numberOfKeys = container.allKeys.count
- }
- func encode(to encoder: Encoder) throws {
- var container = encoder.container(keyedBy: CodingKeys.self)
- try container.encode(oneTwo, forKey: .oneTwo)
- try container.encode("test2", forKey: .oneTwoThree)
- }
- }
- let customKeyConversion = { (_ path: [CodingKey]) -> CodingKey in
- // All keys are the same!
- _TestKey(stringValue: "oneTwo")!
- }
- // Decoding
- // This input has a dictionary with two keys, but only one will end up in the container
- let input = ["unused key 1": "test1", "unused key 2": "test2"]
- let decoder = FirebaseDataDecoder()
- decoder.keyDecodingStrategy = .custom(customKeyConversion)
- let decodingResult = try! decoder.decode(DecodeMe5.self, from: input)
- // There will be only one result for oneTwo (the second one in the structure)
- XCTAssertEqual(1, decodingResult.numberOfKeys)
- // Encoding
- let encoded = DecodeMe5()
- let encoder = FirebaseDataEncoder()
- encoder.keyEncodingStrategy = .custom(customKeyConversion)
- let decodingResult2 = try! encoder.encode(encoded)
- // There will be only one value in the result (the second one encoded)
- XCTAssertEqual(["oneTwo": "test2"], decodingResult2 as? [String: String])
- }
- // MARK: - Encoder Features
- func testNestedContainerCodingPaths() {
- let encoder = FirebaseDataEncoder()
- do {
- _ = try encoder.encode(NestedContainersTestType())
- } catch let error as NSError {
- XCTFail("Caught error during encoding nested container types: \(error)")
- }
- }
- func testSuperEncoderCodingPaths() {
- let encoder = FirebaseDataEncoder()
- do {
- _ = try encoder.encode(NestedContainersTestType(testSuperEncoder: true))
- } catch let error as NSError {
- XCTFail("Caught error during encoding nested container types: \(error)")
- }
- }
- func testInterceptDecimal() {
- let expected =
- NSDecimalNumber(
- string: "10000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"
- )
- // Want to make sure we write out a number, not the keyed encoding here.
- // 1e127 is too big to fit natively in a Double, too, so want to make sure it's encoded as a Decimal.
- let decimal = Decimal(sign: .plus, exponent: 127, significand: Decimal(1))
- _testRoundTrip(of: decimal, expected: expected)
- // Optional Decimals should encode the same way.
- _testRoundTrip(of: Optional(decimal), expected: expected)
- }
- func testInterceptURL() {
- // Want to make sure FirebaseDataEncoder writes out single-value URLs, not the keyed encoding.
- let expected = "http://swift.org"
- let url = URL(string: "http://swift.org")!
- _testRoundTrip(of: url, expected: expected)
- // Optional URLs should encode the same way.
- _testRoundTrip(of: Optional(url), expected: expected)
- }
- // MARK: - Type coercion
- func testTypeCoercion() {
- _testRoundTripTypeCoercionFailure(of: [false, true], as: [Int].self)
- _testRoundTripTypeCoercionFailure(of: [false, true], as: [Int8].self)
- _testRoundTripTypeCoercionFailure(of: [false, true], as: [Int16].self)
- _testRoundTripTypeCoercionFailure(of: [false, true], as: [Int32].self)
- _testRoundTripTypeCoercionFailure(of: [false, true], as: [Int64].self)
- _testRoundTripTypeCoercionFailure(of: [false, true], as: [UInt].self)
- _testRoundTripTypeCoercionFailure(of: [false, true], as: [UInt8].self)
- _testRoundTripTypeCoercionFailure(of: [false, true], as: [UInt16].self)
- _testRoundTripTypeCoercionFailure(of: [false, true], as: [UInt32].self)
- _testRoundTripTypeCoercionFailure(of: [false, true], as: [UInt64].self)
- _testRoundTripTypeCoercionFailure(of: [false, true], as: [Float].self)
- _testRoundTripTypeCoercionFailure(of: [false, true], as: [Double].self)
- _testRoundTripTypeCoercionFailure(of: [0, 1] as [Int], as: [Bool].self)
- _testRoundTripTypeCoercionFailure(of: [0, 1] as [Int8], as: [Bool].self)
- _testRoundTripTypeCoercionFailure(of: [0, 1] as [Int16], as: [Bool].self)
- _testRoundTripTypeCoercionFailure(of: [0, 1] as [Int32], as: [Bool].self)
- _testRoundTripTypeCoercionFailure(of: [0, 1] as [Int64], as: [Bool].self)
- _testRoundTripTypeCoercionFailure(of: [0, 1] as [UInt], as: [Bool].self)
- _testRoundTripTypeCoercionFailure(of: [0, 1] as [UInt8], as: [Bool].self)
- _testRoundTripTypeCoercionFailure(of: [0, 1] as [UInt16], as: [Bool].self)
- _testRoundTripTypeCoercionFailure(of: [0, 1] as [UInt32], as: [Bool].self)
- _testRoundTripTypeCoercionFailure(of: [0, 1] as [UInt64], as: [Bool].self)
- _testRoundTripTypeCoercionFailure(of: [0.0, 1.0] as [Float], as: [Bool].self)
- _testRoundTripTypeCoercionFailure(of: [0.0, 1.0] as [Double], as: [Bool].self)
- }
- func testDecodingConcreteTypeParameter() {
- let encoder = FirebaseDataEncoder()
- guard let value = try? encoder.encode(Employee.testValue) else {
- XCTFail("Unable to encode Employee.")
- return
- }
- let decoder = FirebaseDataDecoder()
- guard let decoded = try? decoder.decode(Employee.self as Person.Type, from: value) else {
- XCTFail("Failed to decode Employee as Person.")
- return
- }
- XCTAssert(
- type(of: decoded) == Employee.self,
- "Expected decoded value to be of type Employee; got \(type(of: decoded)) instead."
- )
- }
- // MARK: - Encoder State
- // SR-6078
- func testEncoderStateThrowOnEncode() {
- struct ReferencingEncoderWrapper<T: Encodable>: Encodable {
- let value: T
- init(_ value: T) { self.value = value }
- func encode(to encoder: Encoder) throws {
- // This approximates a subclass calling into its superclass, where the superclass encodes a value that might throw.
- // The key here is that getting the superEncoder creates a referencing encoder.
- var container = encoder.unkeyedContainer()
- let superEncoder = container.superEncoder()
- // Pushing a nested container on leaves the referencing encoder with multiple containers.
- var nestedContainer = superEncoder.unkeyedContainer()
- try nestedContainer.encode(value)
- }
- }
- // The structure that would be encoded here looks like
- //
- // [[[Float.infinity]]]
- //
- // The wrapper asks for an unkeyed container ([^]), gets a super encoder, and creates a nested container into that ([[^]]).
- // We then encode an array into that ([[[^]]]), which happens to be a value that causes us to throw an error.
- //
- // The issue at hand reproduces when you have a referencing encoder (superEncoder() creates one) that has a container on the stack (unkeyedContainer() adds one) that encodes a value going through box_() (Array does that) that encodes something which throws (Float.infinity does that).
- // When reproducing, this will cause a test failure via fatalError().
- _ = try? FirebaseDataEncoder().encode(ReferencingEncoderWrapper([Float.infinity]))
- }
- func testEncoderStateThrowOnEncodeCustomDate() {
- // This test is identical to testEncoderStateThrowOnEncode, except throwing via a custom Date closure.
- struct ReferencingEncoderWrapper<T: Encodable>: Encodable {
- let value: T
- init(_ value: T) { self.value = value }
- func encode(to encoder: Encoder) throws {
- var container = encoder.unkeyedContainer()
- let superEncoder = container.superEncoder()
- var nestedContainer = superEncoder.unkeyedContainer()
- try nestedContainer.encode(value)
- }
- }
- // The closure needs to push a container before throwing an error to trigger.
- let encoder = FirebaseDataEncoder()
- encoder.dateEncodingStrategy = .custom { _, encoder in
- _ = encoder.unkeyedContainer()
- enum CustomError: Error { case foo }
- throw CustomError.foo
- }
- _ = try? encoder.encode(ReferencingEncoderWrapper(Date()))
- }
- func testEncoderStateThrowOnEncodeCustomData() {
- // This test is identical to testEncoderStateThrowOnEncode, except throwing via a custom Data closure.
- struct ReferencingEncoderWrapper<T: Encodable>: Encodable {
- let value: T
- init(_ value: T) { self.value = value }
- func encode(to encoder: Encoder) throws {
- var container = encoder.unkeyedContainer()
- let superEncoder = container.superEncoder()
- var nestedContainer = superEncoder.unkeyedContainer()
- try nestedContainer.encode(value)
- }
- }
- // The closure needs to push a container before throwing an error to trigger.
- let encoder = FirebaseDataEncoder()
- encoder.dataEncodingStrategy = .custom { _, encoder in
- _ = encoder.unkeyedContainer()
- enum CustomError: Error { case foo }
- throw CustomError.foo
- }
- _ = try? encoder.encode(ReferencingEncoderWrapper(Data()))
- }
- // MARK: - Decoder State
- // SR-6048
- func testDecoderStateThrowOnDecode() {
- // The container stack here starts as [[1,2,3]]. Attempting to decode as [String] matches the outer layer (Array), and begins decoding the array.
- // Once Array decoding begins, 1 is pushed onto the container stack ([[1,2,3], 1]), and 1 is attempted to be decoded as String. This throws a .typeMismatch, but the container is not popped off the stack.
- // When attempting to decode [Int], the container stack is still ([[1,2,3], 1]), and 1 fails to decode as [Int].
- let input = [1, 2, 3]
- _ = try! FirebaseDataDecoder().decode(EitherDecodable<[String], [Int]>.self, from: input)
- }
- func testDecoderStateThrowOnDecodeCustomDate() {
- // This test is identical to testDecoderStateThrowOnDecode, except we're going to fail because our closure throws an error, not because we hit a type mismatch.
- let decoder = FirebaseDataDecoder()
- decoder.dateDecodingStrategy = .custom { decoder in
- enum CustomError: Error { case foo }
- throw CustomError.foo
- }
- let input = 1
- _ = try! decoder.decode(EitherDecodable<Date, Int>.self, from: input)
- }
- func testDecoderStateThrowOnDecodeCustomData() {
- // This test is identical to testDecoderStateThrowOnDecode, except we're going to fail because our closure throws an error, not because we hit a type mismatch.
- let decoder = FirebaseDataDecoder()
- decoder.dataDecodingStrategy = .custom { decoder in
- enum CustomError: Error { case foo }
- throw CustomError.foo
- }
- let input = 1
- _ = try! decoder.decode(EitherDecodable<Data, Int>.self, from: input)
- }
- // MARK: - Helper Functions
- private var _emptyDictionary: [String: String] {
- return [:]
- }
- private func _testEncodeFailure<T: Encodable>(of value: T) {
- do {
- _ = try FirebaseDataEncoder().encode(value)
- XCTFail("Encode of top-level \(T.self) was expected to fail.")
- } catch {}
- }
- private func _testRoundTrip<T, U>(of value: T,
- expected: U,
- dateEncodingStrategy: FirebaseDataEncoder
- .DateEncodingStrategy = .deferredToDate,
- dateDecodingStrategy: FirebaseDataDecoder
- .DateDecodingStrategy = .deferredToDate,
- dataEncodingStrategy: FirebaseDataEncoder
- .DataEncodingStrategy = .base64,
- dataDecodingStrategy: FirebaseDataDecoder
- .DataDecodingStrategy = .base64,
- keyEncodingStrategy: FirebaseDataEncoder
- .KeyEncodingStrategy = .useDefaultKeys,
- keyDecodingStrategy: FirebaseDataDecoder
- .KeyDecodingStrategy = .useDefaultKeys,
- nonConformingFloatEncodingStrategy: FirebaseDataEncoder
- .NonConformingFloatEncodingStrategy = .throw,
- nonConformingFloatDecodingStrategy: FirebaseDataDecoder
- .NonConformingFloatDecodingStrategy = .throw)
- where T: Codable,
- T: Equatable, U: Equatable {
- var payload: Any! = nil
- do {
- let encoder = FirebaseDataEncoder()
- encoder.dateEncodingStrategy = dateEncodingStrategy
- encoder.dataEncodingStrategy = dataEncodingStrategy
- encoder.nonConformingFloatEncodingStrategy = nonConformingFloatEncodingStrategy
- encoder.keyEncodingStrategy = keyEncodingStrategy
- payload = try encoder.encode(value)
- } catch {
- XCTFail("Failed to encode \(T.self): \(error)")
- }
- XCTAssertEqual(
- expected,
- payload as? U,
- "Produced structure not identical to expected structure."
- )
- do {
- let decoder = FirebaseDataDecoder()
- decoder.dateDecodingStrategy = dateDecodingStrategy
- decoder.dataDecodingStrategy = dataDecodingStrategy
- decoder.nonConformingFloatDecodingStrategy = nonConformingFloatDecodingStrategy
- decoder.keyDecodingStrategy = keyDecodingStrategy
- let decoded = try decoder.decode(T.self, from: payload!)
- XCTAssertEqual(decoded, value, "\(T.self) did not round-trip to an equal value.")
- } catch {
- XCTFail("Failed to decode \(T.self): \(error)")
- }
- }
- private func _testRoundTrip<T>(of value: T,
- dateEncodingStrategy: FirebaseDataEncoder
- .DateEncodingStrategy = .deferredToDate,
- dateDecodingStrategy: FirebaseDataDecoder
- .DateDecodingStrategy = .deferredToDate,
- dataEncodingStrategy: FirebaseDataEncoder
- .DataEncodingStrategy = .base64,
- dataDecodingStrategy: FirebaseDataDecoder
- .DataDecodingStrategy = .base64,
- keyEncodingStrategy: FirebaseDataEncoder
- .KeyEncodingStrategy = .useDefaultKeys,
- keyDecodingStrategy: FirebaseDataDecoder
- .KeyDecodingStrategy = .useDefaultKeys,
- nonConformingFloatEncodingStrategy: FirebaseDataEncoder
- .NonConformingFloatEncodingStrategy = .throw,
- nonConformingFloatDecodingStrategy: FirebaseDataDecoder
- .NonConformingFloatDecodingStrategy = .throw) where T: Codable,
- T: Equatable {
- var payload: Any! = nil
- do {
- let encoder = FirebaseDataEncoder()
- encoder.dateEncodingStrategy = dateEncodingStrategy
- encoder.dataEncodingStrategy = dataEncodingStrategy
- encoder.nonConformingFloatEncodingStrategy = nonConformingFloatEncodingStrategy
- encoder.keyEncodingStrategy = keyEncodingStrategy
- payload = try encoder.encode(value)
- } catch {
- XCTFail("Failed to encode \(T.self): \(error)")
- }
- do {
- let decoder = FirebaseDataDecoder()
- decoder.dateDecodingStrategy = dateDecodingStrategy
- decoder.dataDecodingStrategy = dataDecodingStrategy
- decoder.nonConformingFloatDecodingStrategy = nonConformingFloatDecodingStrategy
- decoder.keyDecodingStrategy = keyDecodingStrategy
- let decoded = try decoder.decode(T.self, from: payload!)
- XCTAssertEqual(decoded, value, "\(T.self) did not round-trip to an equal value.")
- } catch {
- XCTFail("Failed to decode \(T.self): \(error)")
- }
- }
- private func _testRoundTripTypeCoercionFailure<T, U>(of value: T, as type: U.Type)
- where T: Codable, U: Codable {
- do {
- let data = try FirebaseDataEncoder().encode(value)
- _ = try FirebaseDataDecoder().decode(U.self, from: data)
- XCTFail("Coercion from \(T.self) to \(U.self) was expected to fail.")
- } catch {}
- }
- }
- // MARK: - Helper Global Functions
- func expectEqualPaths(_ lhs: [CodingKey], _ rhs: [CodingKey], _ prefix: String) {
- if lhs.count != rhs.count {
- XCTFail("\(prefix) [CodingKey].count mismatch: \(lhs.count) != \(rhs.count)")
- return
- }
- for (key1, key2) in zip(lhs, rhs) {
- switch (key1.intValue, key2.intValue) {
- case (.none, .none): break
- case let (.some(i1), .none):
- XCTFail("\(prefix) CodingKey.intValue mismatch: \(type(of: key1))(\(i1)) != nil")
- return
- case let (.none, .some(i2)):
- XCTFail("\(prefix) CodingKey.intValue mismatch: nil != \(type(of: key2))(\(i2))")
- return
- case let (.some(i1), .some(i2)):
- guard i1 == i2 else {
- XCTFail(
- "\(prefix) CodingKey.intValue mismatch: \(type(of: key1))(\(i1)) != \(type(of: key2))(\(i2))"
- )
- return
- }
- }
- XCTAssertEqual(
- key1.stringValue,
- key2.stringValue,
- "\(prefix) CodingKey.stringValue mismatch: \(type(of: key1))('\(key1.stringValue)') != \(type(of: key2))('\(key2.stringValue)')"
- )
- }
- }
- // MARK: - Test Types
- /* FIXME: Import from %S/Inputs/Coding/SharedTypes.swift somehow. */
- // MARK: - Empty Types
- private struct EmptyStruct: Codable, Equatable {
- static func == (_ lhs: EmptyStruct, _ rhs: EmptyStruct) -> Bool {
- return true
- }
- }
- private class EmptyClass: Codable, Equatable {
- static func == (_ lhs: EmptyClass, _ rhs: EmptyClass) -> Bool {
- return true
- }
- }
- // MARK: - Single-Value Types
- /// A simple on-off switch type that encodes as a single Bool value.
- private enum Switch: Codable {
- case off
- case on
- init(from decoder: Decoder) throws {
- let container = try decoder.singleValueContainer()
- switch try container.decode(Bool.self) {
- case false: self = .off
- case true: self = .on
- }
- }
- func encode(to encoder: Encoder) throws {
- var container = encoder.singleValueContainer()
- switch self {
- case .off: try container.encode(false)
- case .on: try container.encode(true)
- }
- }
- }
- /// A simple timestamp type that encodes as a single Double value.
- private struct Timestamp: Codable, Equatable {
- let value: Double
- init(_ value: Double) {
- self.value = value
- }
- init(from decoder: Decoder) throws {
- let container = try decoder.singleValueContainer()
- value = try container.decode(Double.self)
- }
- func encode(to encoder: Encoder) throws {
- var container = encoder.singleValueContainer()
- try container.encode(value)
- }
- static func == (_ lhs: Timestamp, _ rhs: Timestamp) -> Bool {
- return lhs.value == rhs.value
- }
- }
- /// A simple referential counter type that encodes as a single Int value.
- private final class Counter: Codable, Equatable {
- var count: Int = 0
- init() {}
- init(from decoder: Decoder) throws {
- let container = try decoder.singleValueContainer()
- count = try container.decode(Int.self)
- }
- func encode(to encoder: Encoder) throws {
- var container = encoder.singleValueContainer()
- try container.encode(count)
- }
- static func == (_ lhs: Counter, _ rhs: Counter) -> Bool {
- return lhs === rhs || lhs.count == rhs.count
- }
- }
- // MARK: - Structured Types
- /// A simple address type that encodes as a dictionary of values.
- private struct Address: Codable, Equatable {
- let street: String
- let city: String
- let state: String
- let zipCode: Int
- let country: String
- init(street: String, city: String, state: String, zipCode: Int, country: String) {
- self.street = street
- self.city = city
- self.state = state
- self.zipCode = zipCode
- self.country = country
- }
- static func == (_ lhs: Address, _ rhs: Address) -> Bool {
- return lhs.street == rhs.street &&
- lhs.city == rhs.city &&
- lhs.state == rhs.state &&
- lhs.zipCode == rhs.zipCode &&
- lhs.country == rhs.country
- }
- static var testValue: Address {
- return Address(street: "1 Infinite Loop",
- city: "Cupertino",
- state: "CA",
- zipCode: 95014,
- country: "United States")
- }
- }
- /// A simple person class that encodes as a dictionary of values.
- private class Person: Codable, Equatable {
- let name: String
- let email: String
- let website: URL?
- init(name: String, email: String, website: URL? = nil) {
- self.name = name
- self.email = email
- self.website = website
- }
- func isEqual(_ other: Person) -> Bool {
- return name == other.name &&
- email == other.email &&
- website == other.website
- }
- static func == (_ lhs: Person, _ rhs: Person) -> Bool {
- return lhs.isEqual(rhs)
- }
- class var testValue: Person {
- return Person(name: "Johnny Appleseed", email: "appleseed@apple.com")
- }
- }
- /// A class which shares its encoder and decoder with its superclass.
- private class Employee: Person {
- let id: Int
- init(name: String, email: String, website: URL? = nil, id: Int) {
- self.id = id
- super.init(name: name, email: email, website: website)
- }
- enum CodingKeys: String, CodingKey {
- case id
- }
- required init(from decoder: Decoder) throws {
- let container = try decoder.container(keyedBy: CodingKeys.self)
- id = try container.decode(Int.self, forKey: .id)
- try super.init(from: decoder)
- }
- override func encode(to encoder: Encoder) throws {
- var container = encoder.container(keyedBy: CodingKeys.self)
- try container.encode(id, forKey: .id)
- try super.encode(to: encoder)
- }
- override func isEqual(_ other: Person) -> Bool {
- if let employee = other as? Employee {
- guard id == employee.id else { return false }
- }
- return super.isEqual(other)
- }
- override class var testValue: Employee {
- return Employee(name: "Johnny Appleseed", email: "appleseed@apple.com", id: 42)
- }
- }
- /// A simple company struct which encodes as a dictionary of nested values.
- private struct Company: Codable, Equatable {
- let address: Address
- var employees: [Employee]
- init(address: Address, employees: [Employee]) {
- self.address = address
- self.employees = employees
- }
- static func == (_ lhs: Company, _ rhs: Company) -> Bool {
- return lhs.address == rhs.address && lhs.employees == rhs.employees
- }
- static var testValue: Company {
- return Company(address: Address.testValue, employees: [Employee.testValue])
- }
- }
- /// An enum type which decodes from Bool?.
- private enum EnhancedBool: Codable {
- case `true`
- case `false`
- case fileNotFound
- init(from decoder: Decoder) throws {
- let container = try decoder.singleValueContainer()
- if container.decodeNil() {
- self = .fileNotFound
- } else {
- let value = try container.decode(Bool.self)
- self = value ? .true : .false
- }
- }
- func encode(to encoder: Encoder) throws {
- var container = encoder.singleValueContainer()
- switch self {
- case .true: try container.encode(true)
- case .false: try container.encode(false)
- case .fileNotFound: try container.encodeNil()
- }
- }
- }
- /// A type which encodes as an array directly through a single value container.
- struct Numbers: Codable, Equatable {
- let values = [4, 8, 15, 16, 23, 42]
- init() {}
- init(from decoder: Decoder) throws {
- let container = try decoder.singleValueContainer()
- let decodedValues = try container.decode([Int].self)
- guard decodedValues == values else {
- throw DecodingError
- .dataCorrupted(DecodingError
- .Context(codingPath: decoder.codingPath, debugDescription: "The Numbers are wrong!"))
- }
- }
- func encode(to encoder: Encoder) throws {
- var container = encoder.singleValueContainer()
- try container.encode(values)
- }
- static func == (_ lhs: Numbers, _ rhs: Numbers) -> Bool {
- return lhs.values == rhs.values
- }
- static var testValue: Numbers {
- return Numbers()
- }
- }
- /// A type which encodes as a dictionary directly through a single value container.
- private final class Mapping: Codable, Equatable {
- let values: [String: URL]
- init(values: [String: URL]) {
- self.values = values
- }
- init(from decoder: Decoder) throws {
- let container = try decoder.singleValueContainer()
- values = try container.decode([String: URL].self)
- }
- func encode(to encoder: Encoder) throws {
- var container = encoder.singleValueContainer()
- try container.encode(values)
- }
- static func == (_ lhs: Mapping, _ rhs: Mapping) -> Bool {
- return lhs === rhs || lhs.values == rhs.values
- }
- static var testValue: Mapping {
- return Mapping(values: ["Apple": URL(string: "http://apple.com")!,
- "localhost": URL(string: "http://127.0.0.1")!])
- }
- }
- struct NestedContainersTestType: Encodable {
- let testSuperEncoder: Bool
- init(testSuperEncoder: Bool = false) {
- self.testSuperEncoder = testSuperEncoder
- }
- enum TopLevelCodingKeys: Int, CodingKey {
- case a
- case b
- case c
- }
- enum IntermediateCodingKeys: Int, CodingKey {
- case one
- case two
- }
- func encode(to encoder: Encoder) throws {
- if testSuperEncoder {
- var topLevelContainer = encoder.container(keyedBy: TopLevelCodingKeys.self)
- expectEqualPaths(encoder.codingPath, [], "Top-level Encoder's codingPath changed.")
- expectEqualPaths(
- topLevelContainer.codingPath,
- [],
- "New first-level keyed container has non-empty codingPath."
- )
- let superEncoder = topLevelContainer.superEncoder(forKey: .a)
- expectEqualPaths(encoder.codingPath, [], "Top-level Encoder's codingPath changed.")
- expectEqualPaths(
- topLevelContainer.codingPath,
- [],
- "First-level keyed container's codingPath changed."
- )
- expectEqualPaths(
- superEncoder.codingPath,
- [TopLevelCodingKeys.a],
- "New superEncoder had unexpected codingPath."
- )
- _testNestedContainers(in: superEncoder, baseCodingPath: [TopLevelCodingKeys.a])
- } else {
- _testNestedContainers(in: encoder, baseCodingPath: [])
- }
- }
- func _testNestedContainers(in encoder: Encoder, baseCodingPath: [CodingKey]) {
- expectEqualPaths(encoder.codingPath, baseCodingPath, "New encoder has non-empty codingPath.")
- // codingPath should not change upon fetching a non-nested container.
- var firstLevelContainer = encoder.container(keyedBy: TopLevelCodingKeys.self)
- expectEqualPaths(encoder.codingPath, baseCodingPath, "Top-level Encoder's codingPath changed.")
- expectEqualPaths(
- firstLevelContainer.codingPath,
- baseCodingPath,
- "New first-level keyed container has non-empty codingPath."
- )
- // Nested Keyed Container
- do {
- // Nested container for key should have a new key pushed on.
- var secondLevelContainer = firstLevelContainer.nestedContainer(
- keyedBy: IntermediateCodingKeys.self,
- forKey: .a
- )
- expectEqualPaths(
- encoder.codingPath,
- baseCodingPath,
- "Top-level Encoder's codingPath changed."
- )
- expectEqualPaths(
- firstLevelContainer.codingPath,
- baseCodingPath,
- "First-level keyed container's codingPath changed."
- )
- expectEqualPaths(
- secondLevelContainer.codingPath,
- baseCodingPath + [TopLevelCodingKeys.a],
- "New second-level keyed container had unexpected codingPath."
- )
- // Inserting a keyed container should not change existing coding paths.
- let thirdLevelContainerKeyed = secondLevelContainer.nestedContainer(
- keyedBy: IntermediateCodingKeys.self,
- forKey: .one
- )
- expectEqualPaths(
- encoder.codingPath,
- baseCodingPath,
- "Top-level Encoder's codingPath changed."
- )
- expectEqualPaths(
- firstLevelContainer.codingPath,
- baseCodingPath,
- "First-level keyed container's codingPath changed."
- )
- expectEqualPaths(
- secondLevelContainer.codingPath,
- baseCodingPath + [TopLevelCodingKeys.a],
- "Second-level keyed container's codingPath changed."
- )
- expectEqualPaths(
- thirdLevelContainerKeyed.codingPath,
- baseCodingPath + [TopLevelCodingKeys.a, IntermediateCodingKeys.one],
- "New third-level keyed container had unexpected codingPath."
- )
- // Inserting an unkeyed container should not change existing coding paths.
- let thirdLevelContainerUnkeyed = secondLevelContainer.nestedUnkeyedContainer(forKey: .two)
- expectEqualPaths(
- encoder.codingPath,
- baseCodingPath + [],
- "Top-level Encoder's codingPath changed."
- )
- expectEqualPaths(
- firstLevelContainer.codingPath,
- baseCodingPath + [],
- "First-level keyed container's codingPath changed."
- )
- expectEqualPaths(
- secondLevelContainer.codingPath,
- baseCodingPath + [TopLevelCodingKeys.a],
- "Second-level keyed container's codingPath changed."
- )
- expectEqualPaths(
- thirdLevelContainerUnkeyed.codingPath,
- baseCodingPath + [TopLevelCodingKeys.a, IntermediateCodingKeys.two],
- "New third-level unkeyed container had unexpected codingPath."
- )
- }
- // Nested Unkeyed Container
- do {
- // Nested container for key should have a new key pushed on.
- var secondLevelContainer = firstLevelContainer.nestedUnkeyedContainer(forKey: .b)
- expectEqualPaths(
- encoder.codingPath,
- baseCodingPath,
- "Top-level Encoder's codingPath changed."
- )
- expectEqualPaths(
- firstLevelContainer.codingPath,
- baseCodingPath,
- "First-level keyed container's codingPath changed."
- )
- expectEqualPaths(
- secondLevelContainer.codingPath,
- baseCodingPath + [TopLevelCodingKeys.b],
- "New second-level keyed container had unexpected codingPath."
- )
- // Appending a keyed container should not change existing coding paths.
- let thirdLevelContainerKeyed = secondLevelContainer
- .nestedContainer(keyedBy: IntermediateCodingKeys.self)
- expectEqualPaths(
- encoder.codingPath,
- baseCodingPath,
- "Top-level Encoder's codingPath changed."
- )
- expectEqualPaths(
- firstLevelContainer.codingPath,
- baseCodingPath,
- "First-level keyed container's codingPath changed."
- )
- expectEqualPaths(
- secondLevelContainer.codingPath,
- baseCodingPath + [TopLevelCodingKeys.b],
- "Second-level unkeyed container's codingPath changed."
- )
- expectEqualPaths(
- thirdLevelContainerKeyed.codingPath,
- baseCodingPath + [TopLevelCodingKeys.b, _TestKey(index: 0)],
- "New third-level keyed container had unexpected codingPath."
- )
- // Appending an unkeyed container should not change existing coding paths.
- let thirdLevelContainerUnkeyed = secondLevelContainer.nestedUnkeyedContainer()
- expectEqualPaths(
- encoder.codingPath,
- baseCodingPath,
- "Top-level Encoder's codingPath changed."
- )
- expectEqualPaths(
- firstLevelContainer.codingPath,
- baseCodingPath,
- "First-level keyed container's codingPath changed."
- )
- expectEqualPaths(
- secondLevelContainer.codingPath,
- baseCodingPath + [TopLevelCodingKeys.b],
- "Second-level unkeyed container's codingPath changed."
- )
- expectEqualPaths(
- thirdLevelContainerUnkeyed.codingPath,
- baseCodingPath + [TopLevelCodingKeys.b, _TestKey(index: 1)],
- "New third-level unkeyed container had unexpected codingPath."
- )
- }
- }
- }
- // MARK: - Helper Types
- /// A key type which can take on any string or integer value.
- /// This needs to mirror _JSONKey.
- private struct _TestKey: CodingKey {
- var stringValue: String
- var intValue: Int?
- init?(stringValue: String) {
- self.stringValue = stringValue
- intValue = nil
- }
- init?(intValue: Int) {
- stringValue = "\(intValue)"
- self.intValue = intValue
- }
- init(index: Int) {
- stringValue = "Index \(index)"
- intValue = index
- }
- }
- private struct FloatNaNPlaceholder: Codable, Equatable {
- init() {}
- func encode(to encoder: Encoder) throws {
- var container = encoder.singleValueContainer()
- try container.encode(Float.nan)
- }
- init(from decoder: Decoder) throws {
- let container = try decoder.singleValueContainer()
- let float = try container.decode(Float.self)
- if !float.isNaN {
- throw DecodingError
- .dataCorrupted(DecodingError
- .Context(codingPath: decoder.codingPath, debugDescription: "Couldn't decode NaN."))
- }
- }
- static func == (_ lhs: FloatNaNPlaceholder, _ rhs: FloatNaNPlaceholder) -> Bool {
- return true
- }
- }
- private struct DoubleNaNPlaceholder: Codable, Equatable {
- init() {}
- func encode(to encoder: Encoder) throws {
- var container = encoder.singleValueContainer()
- try container.encode(Double.nan)
- }
- init(from decoder: Decoder) throws {
- let container = try decoder.singleValueContainer()
- let double = try container.decode(Double.self)
- if !double.isNaN {
- throw DecodingError
- .dataCorrupted(DecodingError
- .Context(codingPath: decoder.codingPath, debugDescription: "Couldn't decode NaN."))
- }
- }
- static func == (_ lhs: DoubleNaNPlaceholder, _ rhs: DoubleNaNPlaceholder) -> Bool {
- return true
- }
- }
- private enum EitherDecodable<T: Decodable, U: Decodable>: Decodable {
- case t(T)
- case u(U)
- init(from decoder: Decoder) throws {
- let container = try decoder.singleValueContainer()
- do {
- self = .t(try container.decode(T.self))
- } catch {
- self = .u(try container.decode(U.self))
- }
- }
- }
|