diff --git a/FirebaseDatabaseSwift/Sources/Codable/EncoderDecoder.swift b/FirebaseDatabaseSwift/Sources/Codable/EncoderDecoder.swift new file mode 100644 index 0000000000..03bf696c4e --- /dev/null +++ b/FirebaseDatabaseSwift/Sources/Codable/EncoderDecoder.swift @@ -0,0 +1,23 @@ +/* + * Copyright 2021 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import FirebaseDatabase +import FirebaseSharedSwift + +extension Database { + public typealias Encoder = StructureEncoder + public typealias Decoder = StructureDecoder +} diff --git a/FirebaseDatabaseSwift/Tests/Codable/ServerValueCodingTests.swift b/FirebaseDatabaseSwift/Tests/Codable/ServerValueCodingTests.swift index 90567772ef..259f1eee3e 100644 --- a/FirebaseDatabaseSwift/Tests/Codable/ServerValueCodingTests.swift +++ b/FirebaseDatabaseSwift/Tests/Codable/ServerValueCodingTests.swift @@ -87,3 +87,114 @@ extension CurrencyAmount: ExpressibleByFloatLiteral { self.value = Decimal(value) } } + +private func assertThat(_ dictionary: [String: Any], + file: StaticString = #file, + line: UInt = #line) -> DictionarySubject { + return DictionarySubject(dictionary, file: file, line: line) +} + +func assertThat(_ model: X, file: StaticString = #file, + line: UInt = #line) -> CodableSubject { + return CodableSubject(model, file: file, line: line) +} + +func assertThat(_ model: X, file: StaticString = #file, + line: UInt = #line) -> EncodableSubject { + return EncodableSubject(model, file: file, line: line) +} + +class EncodableSubject { + var subject: X + var file: StaticString + var line: UInt + + init(_ subject: X, file: StaticString, line: UInt) { + self.subject = subject + self.file = file + self.line = line + } + + @discardableResult + func encodes(to expected: [String: Any], + using encoder: Database.Encoder = .init()) -> DictionarySubject { + let encoded = assertEncodes(to: expected, using: encoder) + return DictionarySubject(encoded, file: file, line: line) + } + + func failsToEncode() { + do { + let encoder = Database.Encoder() + encoder.keyEncodingStrategy = .convertToSnakeCase + _ = try encoder.encode(subject) + } catch { + return + } + XCTFail("Failed to throw") + } + + func failsEncodingAtTopLevel() { + do { + let encoder = Database.Encoder() + encoder.keyEncodingStrategy = .convertToSnakeCase + _ = try encoder.encode(subject) + XCTFail("Failed to throw", file: file, line: line) + } catch EncodingError.invalidValue(_, _) { + return + } catch { + XCTFail("Unrecognized error: \(error)", file: file, line: line) + } + } + + private func assertEncodes(to expected: [String: Any], + using encoder: Database.Encoder = .init()) -> [String: Any] { + do { + let enc = try encoder.encode(subject) + XCTAssertEqual(enc as? NSDictionary, expected as NSDictionary, file: file, line: line) + return (enc as! NSDictionary) as! [String: Any] + } catch { + XCTFail("Failed to encode \(X.self): error: \(error)") + return ["": -1] + } + } +} + +class CodableSubject: EncodableSubject { + func roundTrips(to expected: [String: Any], + using encoder: Database.Encoder = .init(), + decoder: Database.Decoder = .init()) { + let reverseSubject = encodes(to: expected, using: encoder) + reverseSubject.decodes(to: subject, using: decoder) + } +} + +class DictionarySubject { + var subject: [String: Any] + var file: StaticString + var line: UInt + + init(_ subject: [String: Any], file: StaticString, line: UInt) { + self.subject = subject + self.file = file + self.line = line + } + + func decodes(to expected: X, + using decoder: Database.Decoder = .init()) -> Void { + do { + let decoded = try decoder.decode(X.self, from: subject) + XCTAssertEqual(decoded, expected) + } catch { + XCTFail("Failed to decode \(X.self): \(error)", file: file, line: line) + } + } + + func failsDecoding(to _: X.Type, + using decoder: Database.Decoder = .init()) -> Void { + XCTAssertThrowsError( + try decoder.decode(X.self, from: subject), + file: file, + line: line + ) + } +} diff --git a/FirebaseSharedSwift/CHANGELOG.md b/FirebaseSharedSwift/CHANGELOG.md new file mode 100644 index 0000000000..8b13789179 --- /dev/null +++ b/FirebaseSharedSwift/CHANGELOG.md @@ -0,0 +1 @@ + diff --git a/FirebaseDatabaseSwift/Sources/third_party/RTDBEncoder/LICENSE b/FirebaseSharedSwift/Sources/third_party/StructureEncoder/LICENSE similarity index 100% rename from FirebaseDatabaseSwift/Sources/third_party/RTDBEncoder/LICENSE rename to FirebaseSharedSwift/Sources/third_party/StructureEncoder/LICENSE diff --git a/FirebaseDatabaseSwift/Sources/third_party/RTDBEncoder/METADATA b/FirebaseSharedSwift/Sources/third_party/StructureEncoder/METADATA similarity index 100% rename from FirebaseDatabaseSwift/Sources/third_party/RTDBEncoder/METADATA rename to FirebaseSharedSwift/Sources/third_party/StructureEncoder/METADATA diff --git a/FirebaseDatabaseSwift/Sources/third_party/RTDBEncoder/RTDBEncoder.swift b/FirebaseSharedSwift/Sources/third_party/StructureEncoder/StructureEncoder.swift similarity index 83% rename from FirebaseDatabaseSwift/Sources/third_party/RTDBEncoder/RTDBEncoder.swift rename to FirebaseSharedSwift/Sources/third_party/StructureEncoder/StructureEncoder.swift index c6533eb7db..c6aec8351b 100644 --- a/FirebaseDatabaseSwift/Sources/third_party/RTDBEncoder/RTDBEncoder.swift +++ b/FirebaseSharedSwift/Sources/third_party/StructureEncoder/StructureEncoder.swift @@ -12,7 +12,6 @@ // //===----------------------------------------------------------------------===// -import FirebaseDatabase import Foundation extension DecodingError { @@ -96,189 +95,187 @@ extension Dictionary : _JSONStringDictionaryDecodableMarker where Key == String, // used in the new runtime. _TtC10Foundation13__JSONEncoder is the // mangled name for Foundation.__JSONEncoder. -extension Database { - public class Encoder { - // MARK: Options +public class StructureEncoder { + // MARK: Options - /// The strategy to use for encoding `Date` values. - public enum DateEncodingStrategy { - /// Defer to `Date` for choosing an encoding. This is the default strategy. - case deferredToDate + /// The strategy to use for encoding `Date` values. + public enum DateEncodingStrategy { + /// Defer to `Date` for choosing an encoding. This is the default strategy. + case deferredToDate - /// Encode the `Date` as a UNIX timestamp (as a JSON number). - case secondsSince1970 + /// Encode the `Date` as a UNIX timestamp (as a JSON number). + case secondsSince1970 - /// Encode the `Date` as UNIX millisecond timestamp (as a JSON number). - case millisecondsSince1970 + /// Encode the `Date` as UNIX millisecond timestamp (as a JSON number). + case millisecondsSince1970 - /// Encode the `Date` as an ISO-8601-formatted string (in RFC 3339 format). - @available(macOS 10.12, iOS 10.0, watchOS 3.0, tvOS 10.0, *) - case iso8601 + /// Encode the `Date` as an ISO-8601-formatted string (in RFC 3339 format). + @available(macOS 10.12, iOS 10.0, watchOS 3.0, tvOS 10.0, *) + case iso8601 - /// Encode the `Date` as a string formatted by the given formatter. - case formatted(DateFormatter) + /// Encode the `Date` as a string formatted by the given formatter. + case formatted(DateFormatter) - /// Encode the `Date` as a custom value encoded by the given closure. - /// - /// If the closure fails to encode a value into the given encoder, the encoder will encode an empty automatic container in its place. - case custom((Date, Swift.Encoder) throws -> Void) - } - - /// The strategy to use for encoding `Data` values. - public enum DataEncodingStrategy { - /// Defer to `Data` for choosing an encoding. - case deferredToData - - /// Encoded the `Data` as a Base64-encoded string. This is the default strategy. - case base64 - - /// Encode the `Data` as a custom value encoded by the given closure. - /// - /// If the closure fails to encode a value into the given encoder, the encoder will encode an empty automatic container in its place. - case custom((Data, Swift.Encoder) throws -> Void) - } - - /// The strategy to use for non-JSON-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 strategy to use for automatically changing the value of keys before encoding. - public enum KeyEncodingStrategy { - /// Use the keys specified by each type. This is the default strategy. - case useDefaultKeys - - /// Convert from "camelCaseKeys" to "snake_case_keys" before writing a key to JSON payload. - /// - /// Capital characters are determined by testing membership in `CharacterSet.uppercaseLetters` and `CharacterSet.lowercaseLetters` (Unicode General Categories Lu and Lt). - /// The conversion to lower case uses `Locale.system`, also known as the ICU "root" locale. This means the result is consistent regardless of the current user's locale and language preferences. - /// - /// Converting from camel case to snake case: - /// 1. Splits words at the boundary of lower-case to upper-case - /// 2. Inserts `_` between words - /// 3. Lowercases the entire string - /// 4. Preserves starting and ending `_`. - /// - /// For example, `oneTwoThree` becomes `one_two_three`. `_oneTwoThree_` becomes `_one_two_three_`. - /// - /// - Note: Using a key encoding strategy has a nominal performance cost, as each string key has to be converted. - case convertToSnakeCase - - /// Provide a custom conversion to the key in the encoded JSON from the keys specified by the encoded types. - /// The full path to the current encoding position is provided for context (in case you need to locate this key within the payload). The returned key is used in place of the last component in the coding path before encoding. - /// If the result of the conversion is a duplicate key, then only one value will be present in the result. - case custom((_ codingPath: [CodingKey]) -> CodingKey) - - fileprivate static func _convertToSnakeCase(_ stringKey: String) -> String { - guard !stringKey.isEmpty else { return stringKey } - - var words : [Range] = [] - // The general idea of this algorithm is to split words on transition from lower to upper case, then on transition of >1 upper case characters to lowercase - // - // myProperty -> my_property - // myURLProperty -> my_url_property - // - // We assume, per Swift naming conventions, that the first character of the key is lowercase. - var wordStart = stringKey.startIndex - var searchRange = stringKey.index(after: wordStart)..1 capital letters. Turn those into a word, stopping at the capital before the lower case character. - let beforeLowerIndex = stringKey.index(before: lowerCaseRange.lowerBound) - words.append(upperCaseRange.lowerBound..(_ value: T) throws -> Any { - let encoder = __JSONEncoder(options: self.options) + /// If the closure fails to encode a value into the given encoder, the encoder will encode an empty automatic container in its place. + case custom((Date, Swift.Encoder) throws -> Void) + } - guard let topLevel = try encoder.box_(value) else { - throw Swift.EncodingError.invalidValue(value, - Swift.EncodingError.Context(codingPath: [], debugDescription: "Top-level \(T.self) did not encode any values.")) + /// The strategy to use for encoding `Data` values. + public enum DataEncodingStrategy { + /// Defer to `Data` for choosing an encoding. + case deferredToData + + /// Encoded the `Data` as a Base64-encoded string. This is the default strategy. + case base64 + + /// Encode the `Data` as a custom value encoded by the given closure. + /// + /// If the closure fails to encode a value into the given encoder, the encoder will encode an empty automatic container in its place. + case custom((Data, Swift.Encoder) throws -> Void) + } + + /// The strategy to use for non-JSON-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 strategy to use for automatically changing the value of keys before encoding. + public enum KeyEncodingStrategy { + /// Use the keys specified by each type. This is the default strategy. + case useDefaultKeys + + /// Convert from "camelCaseKeys" to "snake_case_keys" before writing a key to JSON payload. + /// + /// Capital characters are determined by testing membership in `CharacterSet.uppercaseLetters` and `CharacterSet.lowercaseLetters` (Unicode General Categories Lu and Lt). + /// The conversion to lower case uses `Locale.system`, also known as the ICU "root" locale. This means the result is consistent regardless of the current user's locale and language preferences. + /// + /// Converting from camel case to snake case: + /// 1. Splits words at the boundary of lower-case to upper-case + /// 2. Inserts `_` between words + /// 3. Lowercases the entire string + /// 4. Preserves starting and ending `_`. + /// + /// For example, `oneTwoThree` becomes `one_two_three`. `_oneTwoThree_` becomes `_one_two_three_`. + /// + /// - Note: Using a key encoding strategy has a nominal performance cost, as each string key has to be converted. + case convertToSnakeCase + + /// Provide a custom conversion to the key in the encoded JSON from the keys specified by the encoded types. + /// The full path to the current encoding position is provided for context (in case you need to locate this key within the payload). The returned key is used in place of the last component in the coding path before encoding. + /// If the result of the conversion is a duplicate key, then only one value will be present in the result. + case custom((_ codingPath: [CodingKey]) -> CodingKey) + + fileprivate static func _convertToSnakeCase(_ stringKey: String) -> String { + guard !stringKey.isEmpty else { return stringKey } + + var words : [Range] = [] + // The general idea of this algorithm is to split words on transition from lower to upper case, then on transition of >1 upper case characters to lowercase + // + // myProperty -> my_property + // myURLProperty -> my_url_property + // + // We assume, per Swift naming conventions, that the first character of the key is lowercase. + var wordStart = stringKey.startIndex + var searchRange = stringKey.index(after: wordStart)..1 capital letters. Turn those into a word, stopping at the capital before the lower case character. + let beforeLowerIndex = stringKey.index(before: lowerCaseRange.lowerBound) + words.append(upperCaseRange.lowerBound..(_ value: T) throws -> Any { + let encoder = __JSONEncoder(options: self.options) + + guard let topLevel = try encoder.box_(value) else { + throw Swift.EncodingError.invalidValue(value, + Swift.EncodingError.Context(codingPath: [], debugDescription: "Top-level \(T.self) did not encode any values.")) + } + return topLevel + } } // MARK: - __JSONEncoder @@ -293,7 +290,7 @@ fileprivate class __JSONEncoder : Encoder { fileprivate var storage: _JSONEncodingStorage /// Options set on the top-level encoder. - fileprivate let options: Database.Encoder._Options + fileprivate let options: StructureEncoder._Options /// The path to the current point in encoding. public var codingPath: [CodingKey] @@ -306,7 +303,7 @@ fileprivate class __JSONEncoder : Encoder { // MARK: - Initialization /// Initializes `self` with the given top-level encoder options. - fileprivate init(options: Database.Encoder._Options, codingPath: [CodingKey] = []) { + fileprivate init(options: StructureEncoder._Options, codingPath: [CodingKey] = []) { self.options = options self.storage = _JSONEncodingStorage() self.codingPath = codingPath @@ -440,7 +437,7 @@ fileprivate struct _JSONKeyedEncodingContainer : KeyedEncodingCon case .useDefaultKeys: return key case .convertToSnakeCase: - let newKeyString = Database.Encoder.KeyEncodingStrategy._convertToSnakeCase(key.stringValue) + let newKeyString = StructureEncoder.KeyEncodingStrategy._convertToSnakeCase(key.stringValue) return _JSONKey(stringValue: newKeyString, intValue: key.intValue) case .custom(let converter): return converter(codingPath + [key]) @@ -1042,178 +1039,176 @@ fileprivate class __JSONReferencingEncoder : __JSONEncoder { // The two must coexist, so it was renamed. The old name must not be // used in the new runtime. _TtC10Foundation13__JSONDecoder is the // mangled name for Foundation.__JSONDecoder. -extension Database { - public class Decoder { - // MARK: Options +public class StructureDecoder { + // MARK: Options - /// The strategy to use for decoding `Date` values. - public enum DateDecodingStrategy { - /// Defer to `Date` for decoding. This is the default strategy. - case deferredToDate + /// The strategy to use for decoding `Date` values. + public enum DateDecodingStrategy { + /// Defer to `Date` for decoding. This is the default strategy. + case deferredToDate - /// Decode the `Date` as a UNIX timestamp from a JSON number. - case secondsSince1970 + /// Decode the `Date` as a UNIX timestamp from a JSON number. + case secondsSince1970 - /// Decode the `Date` as UNIX millisecond timestamp from a JSON number. - case millisecondsSince1970 + /// Decode the `Date` as UNIX millisecond timestamp from a JSON number. + case millisecondsSince1970 - /// Decode the `Date` as an ISO-8601-formatted string (in RFC 3339 format). - @available(macOS 10.12, iOS 10.0, watchOS 3.0, tvOS 10.0, *) - case iso8601 + /// Decode the `Date` as an ISO-8601-formatted string (in RFC 3339 format). + @available(macOS 10.12, iOS 10.0, watchOS 3.0, tvOS 10.0, *) + case iso8601 - /// Decode the `Date` as a string parsed by the given formatter. - case formatted(DateFormatter) + /// Decode the `Date` as a string parsed by the given formatter. + case formatted(DateFormatter) - /// Decode the `Date` as a custom value decoded by the given closure. - case custom((_ decoder: Swift.Decoder) throws -> Date) - } + /// Decode the `Date` as a custom value decoded by the given closure. + case custom((_ decoder: Swift.Decoder) throws -> Date) + } - /// The strategy to use for decoding `Data` values. - public enum DataDecodingStrategy { - /// Defer to `Data` for decoding. - case deferredToData + /// The strategy to use for decoding `Data` values. + public enum DataDecodingStrategy { + /// Defer to `Data` for decoding. + case deferredToData - /// Decode the `Data` from a Base64-encoded string. This is the default strategy. - case base64 + /// Decode the `Data` from a Base64-encoded string. This is the default strategy. + case base64 - /// Decode the `Data` as a custom value decoded by the given closure. - case custom((_ decoder: Swift.Decoder) throws -> Data) - } + /// Decode the `Data` as a custom value decoded by the given closure. + case custom((_ decoder: Swift.Decoder) throws -> Data) + } - /// The strategy to use for non-JSON-conforming floating-point values (IEEE 754 infinity and NaN). - public enum NonConformingFloatDecodingStrategy { - /// Throw upon encountering non-conforming values. This is the default strategy. - case `throw` + /// The strategy to use for non-JSON-conforming floating-point values (IEEE 754 infinity and NaN). + public enum NonConformingFloatDecodingStrategy { + /// 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) - } + /// Decode the values from the given representation strings. + case convertFromString(positiveInfinity: String, negativeInfinity: String, nan: String) + } - /// The strategy to use for automatically changing the value of keys before decoding. - public enum KeyDecodingStrategy { - /// Use the keys specified by each type. This is the default strategy. - case useDefaultKeys + /// The strategy to use for automatically changing the value of keys before decoding. + public enum KeyDecodingStrategy { + /// Use the keys specified by each type. This is the default strategy. + case useDefaultKeys - /// Convert from "snake_case_keys" to "camelCaseKeys" before attempting to match a key with the one specified by each type. - /// - /// The conversion to upper case uses `Locale.system`, also known as the ICU "root" locale. This means the result is consistent regardless of the current user's locale and language preferences. - /// - /// Converting from snake case to camel case: - /// 1. Capitalizes the word starting after each `_` - /// 2. Removes all `_` - /// 3. Preserves starting and ending `_` (as these are often used to indicate private variables or other metadata). - /// For example, `one_two_three` becomes `oneTwoThree`. `_one_two_three_` becomes `_oneTwoThree_`. - /// - /// - Note: Using a key decoding strategy has a nominal performance cost, as each string key has to be inspected for the `_` character. - case convertFromSnakeCase - - /// Provide a custom conversion from the key in the encoded JSON to the keys specified by the decoded types. - /// The full path to the current decoding position is provided for context (in case you need to locate this key within the payload). The returned key is used in place of the last component in the coding path before decoding. - /// If the result of the conversion is a duplicate key, then only one value will be present in the container for the type to decode from. - case custom((_ codingPath: [CodingKey]) -> CodingKey) - - fileprivate static func _convertFromSnakeCase(_ stringKey: String) -> String { - guard !stringKey.isEmpty else { return stringKey } - - // Find the first non-underscore character - guard let firstNonUnderscore = stringKey.firstIndex(where: { $0 != "_" }) else { - // Reached the end without finding an _ - return stringKey - } - - // Find the last non-underscore character - var lastNonUnderscore = stringKey.index(before: stringKey.endIndex) - while lastNonUnderscore > firstNonUnderscore && stringKey[lastNonUnderscore] == "_" { - stringKey.formIndex(before: &lastNonUnderscore) - } - - let keyRange = firstNonUnderscore...lastNonUnderscore - let leadingUnderscoreRange = stringKey.startIndex..(_ type: T.Type, from structure: Any) throws -> T { - let decoder = __JSONDecoder(referencing: structure, options: self.options) - guard let value = try decoder.unbox(structure, as: type) else { - throw Swift.DecodingError.valueNotFound(type, Swift.DecodingError.Context(codingPath: [], debugDescription: "The given data did not contain a top-level value.")) + /// The conversion to upper case uses `Locale.system`, also known as the ICU "root" locale. This means the result is consistent regardless of the current user's locale and language preferences. + /// + /// Converting from snake case to camel case: + /// 1. Capitalizes the word starting after each `_` + /// 2. Removes all `_` + /// 3. Preserves starting and ending `_` (as these are often used to indicate private variables or other metadata). + /// For example, `one_two_three` becomes `oneTwoThree`. `_one_two_three_` becomes `_oneTwoThree_`. + /// + /// - Note: Using a key decoding strategy has a nominal performance cost, as each string key has to be inspected for the `_` character. + case convertFromSnakeCase + + /// Provide a custom conversion from the key in the encoded JSON to the keys specified by the decoded types. + /// The full path to the current decoding position is provided for context (in case you need to locate this key within the payload). The returned key is used in place of the last component in the coding path before decoding. + /// If the result of the conversion is a duplicate key, then only one value will be present in the container for the type to decode from. + case custom((_ codingPath: [CodingKey]) -> CodingKey) + + fileprivate static func _convertFromSnakeCase(_ stringKey: String) -> String { + guard !stringKey.isEmpty else { return stringKey } + + // Find the first non-underscore character + guard let firstNonUnderscore = stringKey.firstIndex(where: { $0 != "_" }) else { + // Reached the end without finding an _ + return stringKey } - return value + // Find the last non-underscore character + var lastNonUnderscore = stringKey.index(before: stringKey.endIndex) + while lastNonUnderscore > firstNonUnderscore && stringKey[lastNonUnderscore] == "_" { + stringKey.formIndex(before: &lastNonUnderscore) + } + + let keyRange = firstNonUnderscore...lastNonUnderscore + let leadingUnderscoreRange = stringKey.startIndex..(_ type: T.Type, from structure: Any) throws -> T { + let decoder = __JSONDecoder(referencing: structure, options: self.options) + guard let value = try decoder.unbox(structure, as: type) else { + throw Swift.DecodingError.valueNotFound(type, Swift.DecodingError.Context(codingPath: [], debugDescription: "The given data did not contain a top-level value.")) + } + + return value + } } // MARK: - __JSONDecoder @@ -1228,7 +1223,7 @@ fileprivate class __JSONDecoder : Decoder { fileprivate var storage: _JSONDecodingStorage /// Options set on the top-level decoder. - fileprivate let options: Database.Decoder._Options + fileprivate let options: StructureDecoder._Options /// The path to the current point in encoding. fileprivate(set) public var codingPath: [CodingKey] @@ -1241,7 +1236,7 @@ fileprivate class __JSONDecoder : Decoder { // MARK: - Initialization /// Initializes `self` with the given top-level container and options. - fileprivate init(referencing container: Any, at codingPath: [CodingKey] = [], options: Database.Decoder._Options) { + fileprivate init(referencing container: Any, at codingPath: [CodingKey] = [], options: StructureDecoder._Options) { self.storage = _JSONDecodingStorage() self.storage.push(container: container) self.codingPath = codingPath @@ -1347,7 +1342,7 @@ fileprivate struct _JSONKeyedDecodingContainer : KeyedDecodingCon // 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 JSON dictionaries. self.container = Dictionary(container.map { - key, value in (Database.Decoder.KeyDecodingStrategy._convertFromSnakeCase(key), value) + key, value in (StructureDecoder.KeyDecodingStrategy._convertFromSnakeCase(key), value) }, uniquingKeysWith: { (first, _) in first }) case .custom(let converter): self.container = Dictionary(container.map { @@ -1372,8 +1367,8 @@ fileprivate struct _JSONKeyedDecodingContainer : KeyedDecodingCon case .convertFromSnakeCase: // In this case we can attempt to recover the original value by reversing the transform let original = key.stringValue - let converted = Database.Encoder.KeyEncodingStrategy._convertToSnakeCase(original) - let roundtrip = Database.Decoder.KeyDecodingStrategy._convertFromSnakeCase(converted) + let converted = StructureEncoder.KeyEncodingStrategy._convertToSnakeCase(original) + let roundtrip = StructureDecoder.KeyDecodingStrategy._convertFromSnakeCase(converted) if converted == original { return "\(key) (\"\(original)\")" } else if roundtrip == original { diff --git a/FirebaseDatabaseSwift/Tests/Codable/RTDBEncoderTests.swift b/FirebaseSharedSwift/Tests/Codable/StructureEncoderTests.swift similarity index 95% rename from FirebaseDatabaseSwift/Tests/Codable/RTDBEncoderTests.swift rename to FirebaseSharedSwift/Tests/Codable/StructureEncoderTests.swift index 50000146b7..cecf1d1c1d 100644 --- a/FirebaseDatabaseSwift/Tests/Codable/RTDBEncoderTests.swift +++ b/FirebaseSharedSwift/Tests/Codable/StructureEncoderTests.swift @@ -15,11 +15,10 @@ */ import Foundation -import FirebaseDatabase -import FirebaseDatabaseSwift +import FirebaseSharedSwift import XCTest -class FirebaseDatabaseEncoderTests: XCTestCase { +class FirebaseStructureEncoderTests: XCTestCase { func testInt() { struct Model: Codable, Equatable { let x: Int @@ -30,7 +29,7 @@ class FirebaseDatabaseEncoderTests: XCTestCase { } func testNullDecodesAsNil() throws { - let decoder = Database.Decoder() + let decoder = StructureDecoder() let opt = try decoder.decode(Int?.self, from: NSNull()) XCTAssertNil(opt) } @@ -53,9 +52,9 @@ class FirebaseDatabaseEncoderTests: XCTestCase { } let model = Model(snakeCase: 42) let dict = ["snake_case": 42] - let encoder = Database.Encoder() + let encoder = StructureEncoder() encoder.keyEncodingStrategy = .convertToSnakeCase - let decoder = Database.Decoder() + let decoder = StructureDecoder() decoder.keyDecodingStrategy = .convertFromSnakeCase assertThat(model).roundTrips(to: dict, using: encoder, decoder: decoder) } @@ -419,14 +418,14 @@ class EncodableSubject { @discardableResult func encodes(to expected: [String: Any], - using encoder: Database.Encoder = .init()) -> DictionarySubject { + using encoder: StructureEncoder = .init()) -> DictionarySubject { let encoded = assertEncodes(to: expected, using: encoder) return DictionarySubject(encoded, file: file, line: line) } func failsToEncode() { do { - let encoder = Database.Encoder() + let encoder = StructureEncoder() encoder.keyEncodingStrategy = .convertToSnakeCase _ = try encoder.encode(subject) } catch { @@ -437,7 +436,7 @@ class EncodableSubject { func failsEncodingAtTopLevel() { do { - let encoder = Database.Encoder() + let encoder = StructureEncoder() encoder.keyEncodingStrategy = .convertToSnakeCase _ = try encoder.encode(subject) XCTFail("Failed to throw", file: file, line: line) @@ -449,7 +448,7 @@ class EncodableSubject { } private func assertEncodes(to expected: [String: Any], - using encoder: Database.Encoder = .init()) -> [String: Any] { + using encoder: StructureEncoder = .init()) -> [String: Any] { do { let enc = try encoder.encode(subject) XCTAssertEqual(enc as? NSDictionary, expected as NSDictionary, file: file, line: line) @@ -463,8 +462,8 @@ class EncodableSubject { class CodableSubject: EncodableSubject { func roundTrips(to expected: [String: Any], - using encoder: Database.Encoder = .init(), - decoder: Database.Decoder = .init()) { + using encoder: StructureEncoder = .init(), + decoder: StructureDecoder = .init()) { let reverseSubject = encodes(to: expected, using: encoder) reverseSubject.decodes(to: subject, using: decoder) } @@ -482,7 +481,7 @@ class DictionarySubject { } func decodes(to expected: X, - using decoder: Database.Decoder = .init()) -> Void { + using decoder: StructureDecoder = .init()) -> Void { do { let decoded = try decoder.decode(X.self, from: subject) XCTAssertEqual(decoded, expected) @@ -492,7 +491,7 @@ class DictionarySubject { } func failsDecoding(to _: X.Type, - using decoder: Database.Decoder = .init()) -> Void { + using decoder: StructureDecoder = .init()) -> Void { XCTAssertThrowsError( try decoder.decode(X.self, from: subject), file: file, diff --git a/FirebaseDatabaseSwift/Tests/third_party/EncoderTests.swift b/FirebaseSharedSwift/Tests/third_party/EncoderTests.swift similarity index 96% rename from FirebaseDatabaseSwift/Tests/third_party/EncoderTests.swift rename to FirebaseSharedSwift/Tests/third_party/EncoderTests.swift index 71960fed4e..e088baab3d 100644 --- a/FirebaseDatabaseSwift/Tests/third_party/EncoderTests.swift +++ b/FirebaseSharedSwift/Tests/third_party/EncoderTests.swift @@ -8,8 +8,7 @@ // //===----------------------------------------------------------------------===// -import FirebaseDatabase -import FirebaseDatabaseSwift +import FirebaseSharedSwift import Swift import Foundation @@ -17,7 +16,7 @@ import Foundation import XCTest -class TestDatabaseEncoder: XCTestCase { +class TestStructureEncoder: XCTestCase { // MARK: - Encoding Top-Level Empty Types func testEncodingTopLevelEmptyStruct() { @@ -216,14 +215,14 @@ class TestDatabaseEncoder: XCTestCase { func localTestRoundTrip(of value: T) { var payload: Any! = nil do { - let encoder = Database.Encoder() + let encoder = StructureEncoder() payload = try encoder.encode(value) } catch { XCTFail("Failed to encode \(T.self): \(error)") } do { - let decoder = Database.Decoder() + let decoder = StructureDecoder() let decoded = try decoder.decode(T.self, from: payload!) /// `snprintf`'s `%g`, which `JSONSerialization` uses internally for double values, does not respect @@ -475,12 +474,12 @@ class TestDatabaseEncoder: XCTestCase { } func testEncodingNonConformingFloatStrings() { - let encodingStrategy: Database.Encoder.NonConformingFloatEncodingStrategy = .convertToString( + let encodingStrategy: StructureEncoder.NonConformingFloatEncodingStrategy = .convertToString( positiveInfinity: "INF", negativeInfinity: "-INF", nan: "NaN" ) - let decodingStrategy: Database.Decoder.NonConformingFloatDecodingStrategy = .convertFromString( + let decodingStrategy: StructureDecoder.NonConformingFloatDecodingStrategy = .convertFromString( positiveInfinity: "INF", negativeInfinity: "-INF", nan: "NaN" @@ -582,7 +581,7 @@ class TestDatabaseEncoder: XCTestCase { let expected = ["\(test.1)": "test"] let encoded = EncodeMe(keyName: test.0) - let encoder = Database.Encoder() + let encoder = StructureEncoder() encoder.keyEncodingStrategy = .convertToSnakeCase let result = try! encoder.encode(encoded) @@ -594,7 +593,7 @@ class TestDatabaseEncoder: XCTestCase { let expected = ["QQQhello": "test"] let encoded = EncodeMe(keyName: "hello") - let encoder = Database.Encoder() + let encoder = StructureEncoder() let customKeyConversion = { (_ path: [CodingKey]) -> CodingKey in let key = _TestKey(stringValue: "QQQ" + path.last!.stringValue)! return key @@ -608,7 +607,7 @@ class TestDatabaseEncoder: XCTestCase { func testEncodingDictionaryStringKeyConversionUntouched() { let toEncode = ["leaveMeAlone": "test"] - let encoder = Database.Encoder() + let encoder = StructureEncoder() encoder.keyEncodingStrategy = .convertToSnakeCase let result = try! encoder.encode(toEncode) @@ -626,7 +625,7 @@ class TestDatabaseEncoder: XCTestCase { func testEncodingDictionaryFailureKeyPath() { let toEncode: [String: EncodeFailure] = ["key": EncodeFailure(someValue: Double.nan)] - let encoder = Database.Encoder() + let encoder = StructureEncoder() encoder.keyEncodingStrategy = .convertToSnakeCase do { _ = try encoder.encode(toEncode) @@ -643,7 +642,7 @@ class TestDatabaseEncoder: XCTestCase { let toEncode: [String: [String: EncodeFailureNested]] = ["key": ["sub_key": EncodeFailureNested(nestedValue: EncodeFailure(someValue: Double.nan))]] - let encoder = Database.Encoder() + let encoder = StructureEncoder() encoder.keyEncodingStrategy = .convertToSnakeCase do { _ = try encoder.encode(toEncode) @@ -673,7 +672,7 @@ class TestDatabaseEncoder: XCTestCase { let encoded = EncodeNestedNested(outerValue: EncodeNested(nestedValue: EncodeMe(keyName: "helloWorld"))) - let encoder = Database.Encoder() + let encoder = StructureEncoder() var callCount = 0 let customKeyConversion = { (_ path: [CodingKey]) -> CodingKey in @@ -758,7 +757,7 @@ class TestDatabaseEncoder: XCTestCase { // 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 = Database.Decoder() + let decoder = StructureDecoder() decoder.keyDecodingStrategy = .convertFromSnakeCase let result = try! decoder.decode(DecodeMe.self, from: input) @@ -771,7 +770,7 @@ class TestDatabaseEncoder: XCTestCase { func testDecodingKeyStrategyCustom() { let input = ["----hello": "test"] - let decoder = Database.Decoder() + let decoder = StructureDecoder() 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 @@ -787,7 +786,7 @@ class TestDatabaseEncoder: XCTestCase { func testDecodingDictionaryStringKeyConversionUntouched() { let input = ["leave_me_alone": "test"] - let decoder = Database.Decoder() + let decoder = StructureDecoder() decoder.keyDecodingStrategy = .convertFromSnakeCase let result = try! decoder.decode([String: String].self, from: input) @@ -796,7 +795,7 @@ class TestDatabaseEncoder: XCTestCase { func testDecodingDictionaryFailureKeyPath() { let input = ["leave_me_alone": "test"] - let decoder = Database.Decoder() + let decoder = StructureDecoder() decoder.keyDecodingStrategy = .convertFromSnakeCase do { _ = try decoder.decode([String: Int].self, from: input) @@ -818,7 +817,7 @@ class TestDatabaseEncoder: XCTestCase { func testDecodingDictionaryFailureKeyPathNested() { let input = ["top_level": ["sub_level": ["nested_value": ["int_value": "not_an_int"]]]] - let decoder = Database.Decoder() + let decoder = StructureDecoder() decoder.keyDecodingStrategy = .convertFromSnakeCase do { _ = try decoder.decode([String: [String: DecodeFailureNested]].self, from: input) @@ -840,7 +839,7 @@ class TestDatabaseEncoder: XCTestCase { func testEncodingKeyStrategySnakeGenerated() { // Test that this works with a struct that has automatically generated keys let input = ["this_is_camel_case": "test"] - let decoder = Database.Decoder() + let decoder = StructureDecoder() decoder.keyDecodingStrategy = .convertFromSnakeCase let result = try! decoder.decode(DecodeMe3.self, from: input) @@ -849,7 +848,7 @@ class TestDatabaseEncoder: XCTestCase { func testDecodingKeyStrategyCamelGenerated() { let encoded = DecodeMe3(thisIsCamelCase: "test") - let encoder = Database.Encoder() + let encoder = StructureEncoder() encoder.keyEncodingStrategy = .convertToSnakeCase let result = try! encoder.encode(encoded) XCTAssertEqual(["this_is_camel_case": "test"], result as? [String: String]) @@ -868,7 +867,7 @@ class TestDatabaseEncoder: XCTestCase { // Decoding let input = ["foo_bar": "test", "this_is_camel_case_too": "test2"] - let decoder = Database.Decoder() + let decoder = StructureDecoder() decoder.keyDecodingStrategy = .convertFromSnakeCase let decodingResult = try! decoder.decode(DecodeMe4.self, from: input) @@ -877,7 +876,7 @@ class TestDatabaseEncoder: XCTestCase { // Encoding let encoded = DecodeMe4(thisIsCamelCase: "test", thisIsCamelCaseToo: "test2") - let encoder = Database.Encoder() + let encoder = StructureEncoder() encoder.keyEncodingStrategy = .convertToSnakeCase let encodingResult = try! encoder.encode(encoded) XCTAssertEqual( @@ -923,7 +922,7 @@ class TestDatabaseEncoder: XCTestCase { // 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 = Database.Decoder() + let decoder = StructureDecoder() decoder.keyDecodingStrategy = .custom(customKeyConversion) let decodingResult = try! decoder.decode(DecodeMe5.self, from: input) @@ -932,7 +931,7 @@ class TestDatabaseEncoder: XCTestCase { // Encoding let encoded = DecodeMe5() - let encoder = Database.Encoder() + let encoder = StructureEncoder() encoder.keyEncodingStrategy = .custom(customKeyConversion) let decodingResult2 = try! encoder.encode(encoded) @@ -943,7 +942,7 @@ class TestDatabaseEncoder: XCTestCase { // MARK: - Encoder Features func testNestedContainerCodingPaths() { - let encoder = Database.Encoder() + let encoder = StructureEncoder() do { _ = try encoder.encode(NestedContainersTestType()) } catch let error as NSError { @@ -952,7 +951,7 @@ class TestDatabaseEncoder: XCTestCase { } func testSuperEncoderCodingPaths() { - let encoder = Database.Encoder() + let encoder = StructureEncoder() do { _ = try encoder.encode(NestedContainersTestType(testSuperEncoder: true)) } catch let error as NSError { @@ -976,7 +975,7 @@ class TestDatabaseEncoder: XCTestCase { } func testInterceptURL() { - // Want to make sure Database.Encoder writes out single-value URLs, not the keyed encoding. + // Want to make sure StructureEncoder 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) @@ -1015,13 +1014,13 @@ class TestDatabaseEncoder: XCTestCase { } func testDecodingConcreteTypeParameter() { - let encoder = Database.Encoder() + let encoder = StructureEncoder() guard let value = try? encoder.encode(Employee.testValue) else { XCTFail("Unable to encode Employee.") return } - let decoder = Database.Decoder() + let decoder = StructureDecoder() guard let decoded = try? decoder.decode(Employee.self as Person.Type, from: value) else { XCTFail("Failed to decode Employee as Person.") return @@ -1062,7 +1061,7 @@ class TestDatabaseEncoder: XCTestCase { // // 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? Database.Encoder().encode(ReferencingEncoderWrapper([Float.infinity])) + _ = try? StructureEncoder().encode(ReferencingEncoderWrapper([Float.infinity])) } func testEncoderStateThrowOnEncodeCustomDate() { @@ -1079,7 +1078,7 @@ class TestDatabaseEncoder: XCTestCase { } // The closure needs to push a container before throwing an error to trigger. - let encoder = Database.Encoder() + let encoder = StructureEncoder() encoder.dateEncodingStrategy = .custom { _, encoder in _ = encoder.unkeyedContainer() enum CustomError: Error { case foo } @@ -1103,7 +1102,7 @@ class TestDatabaseEncoder: XCTestCase { } // The closure needs to push a container before throwing an error to trigger. - let encoder = Database.Encoder() + let encoder = StructureEncoder() encoder.dataEncodingStrategy = .custom { _, encoder in _ = encoder.unkeyedContainer() enum CustomError: Error { case foo } @@ -1121,12 +1120,12 @@ class TestDatabaseEncoder: XCTestCase { // 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! Database.Decoder().decode(EitherDecodable<[String], [Int]>.self, from: input) + _ = try! StructureDecoder().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 = Database.Decoder() + let decoder = StructureDecoder() decoder.dateDecodingStrategy = .custom { decoder in enum CustomError: Error { case foo } throw CustomError.foo @@ -1138,7 +1137,7 @@ class TestDatabaseEncoder: XCTestCase { 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 = Database.Decoder() + let decoder = StructureDecoder() decoder.dataDecodingStrategy = .custom { decoder in enum CustomError: Error { case foo } throw CustomError.foo @@ -1156,34 +1155,34 @@ class TestDatabaseEncoder: XCTestCase { private func _testEncodeFailure(of value: T) { do { - _ = try Database.Encoder().encode(value) + _ = try StructureEncoder().encode(value) XCTFail("Encode of top-level \(T.self) was expected to fail.") } catch {} } private func _testRoundTrip(of value: T, expected: U, - dateEncodingStrategy: Database.Encoder + dateEncodingStrategy: StructureEncoder .DateEncodingStrategy = .deferredToDate, - dateDecodingStrategy: Database.Decoder + dateDecodingStrategy: StructureDecoder .DateDecodingStrategy = .deferredToDate, - dataEncodingStrategy: Database.Encoder + dataEncodingStrategy: StructureEncoder .DataEncodingStrategy = .base64, - dataDecodingStrategy: Database.Decoder + dataDecodingStrategy: StructureDecoder .DataDecodingStrategy = .base64, - keyEncodingStrategy: Database.Encoder + keyEncodingStrategy: StructureEncoder .KeyEncodingStrategy = .useDefaultKeys, - keyDecodingStrategy: Database.Decoder + keyDecodingStrategy: StructureDecoder .KeyDecodingStrategy = .useDefaultKeys, - nonConformingFloatEncodingStrategy: Database.Encoder + nonConformingFloatEncodingStrategy: StructureEncoder .NonConformingFloatEncodingStrategy = .throw, - nonConformingFloatDecodingStrategy: Database.Decoder + nonConformingFloatDecodingStrategy: StructureDecoder .NonConformingFloatDecodingStrategy = .throw) where T: Codable, T: Equatable, U: Equatable { var payload: Any! = nil do { - let encoder = Database.Encoder() + let encoder = StructureEncoder() encoder.dateEncodingStrategy = dateEncodingStrategy encoder.dataEncodingStrategy = dataEncodingStrategy encoder.nonConformingFloatEncodingStrategy = nonConformingFloatEncodingStrategy @@ -1200,7 +1199,7 @@ class TestDatabaseEncoder: XCTestCase { ) do { - let decoder = Database.Decoder() + let decoder = StructureDecoder() decoder.dateDecodingStrategy = dateDecodingStrategy decoder.dataDecodingStrategy = dataDecodingStrategy decoder.nonConformingFloatDecodingStrategy = nonConformingFloatDecodingStrategy @@ -1213,26 +1212,26 @@ class TestDatabaseEncoder: XCTestCase { } private func _testRoundTrip(of value: T, - dateEncodingStrategy: Database.Encoder + dateEncodingStrategy: StructureEncoder .DateEncodingStrategy = .deferredToDate, - dateDecodingStrategy: Database.Decoder + dateDecodingStrategy: StructureDecoder .DateDecodingStrategy = .deferredToDate, - dataEncodingStrategy: Database.Encoder + dataEncodingStrategy: StructureEncoder .DataEncodingStrategy = .base64, - dataDecodingStrategy: Database.Decoder + dataDecodingStrategy: StructureDecoder .DataDecodingStrategy = .base64, - keyEncodingStrategy: Database.Encoder + keyEncodingStrategy: StructureEncoder .KeyEncodingStrategy = .useDefaultKeys, - keyDecodingStrategy: Database.Decoder + keyDecodingStrategy: StructureDecoder .KeyDecodingStrategy = .useDefaultKeys, - nonConformingFloatEncodingStrategy: Database.Encoder + nonConformingFloatEncodingStrategy: StructureEncoder .NonConformingFloatEncodingStrategy = .throw, - nonConformingFloatDecodingStrategy: Database.Decoder + nonConformingFloatDecodingStrategy: StructureDecoder .NonConformingFloatDecodingStrategy = .throw) where T: Codable, T: Equatable { var payload: Any! = nil do { - let encoder = Database.Encoder() + let encoder = StructureEncoder() encoder.dateEncodingStrategy = dateEncodingStrategy encoder.dataEncodingStrategy = dataEncodingStrategy encoder.nonConformingFloatEncodingStrategy = nonConformingFloatEncodingStrategy @@ -1243,7 +1242,7 @@ class TestDatabaseEncoder: XCTestCase { } do { - let decoder = Database.Decoder() + let decoder = StructureDecoder() decoder.dateDecodingStrategy = dateDecodingStrategy decoder.dataDecodingStrategy = dataDecodingStrategy decoder.nonConformingFloatDecodingStrategy = nonConformingFloatDecodingStrategy @@ -1258,8 +1257,8 @@ class TestDatabaseEncoder: XCTestCase { private func _testRoundTripTypeCoercionFailure(of value: T, as type: U.Type) where T: Codable, U: Codable { do { - let data = try Database.Encoder().encode(value) - _ = try Database.Decoder().decode(U.self, from: data) + let data = try StructureEncoder().encode(value) + _ = try StructureDecoder().decode(U.self, from: data) XCTFail("Coercion from \(T.self) to \(U.self) was expected to fail.") } catch {} } diff --git a/Package.swift b/Package.swift index c5c34012dc..f58be9fb10 100644 --- a/Package.swift +++ b/Package.swift @@ -516,18 +516,27 @@ let package = Package( ), .target( name: "FirebaseDatabaseSwift", - dependencies: ["FirebaseDatabase"], - path: "FirebaseDatabaseSwift/Sources", - exclude: [ - "third_party/RTDBEncoder/LICENSE", - "third_party/RTDBEncoder/METADATA", - ] + dependencies: ["FirebaseDatabase", "FirebaseSharedSwift"], + path: "FirebaseDatabaseSwift/Sources" ), .testTarget( name: "FirebaseDatabaseSwiftTests", dependencies: ["FirebaseDatabase", "FirebaseDatabaseSwift"], path: "FirebaseDatabaseSwift/Tests/" ), + .target( + name: "FirebaseSharedSwift", + path: "FirebaseSharedSwift/Sources", + exclude: [ + "third_party/StructureEncoder/LICENSE", + "third_party/StructureEncoder/METADATA", + ] + ), + .testTarget( + name: "FirebaseSharedSwiftTests", + dependencies: ["FirebaseSharedSwift"], + path: "FirebaseSharedSwift/Tests/" + ), .target( name: "FirebaseDynamicLinksTarget", dependencies: [.target(name: "FirebaseDynamicLinks", diff --git a/scripts/spm_test_schemes/FirebaseSharedSwiftTests.xcscheme b/scripts/spm_test_schemes/FirebaseSharedSwiftTests.xcscheme new file mode 100644 index 0000000000..631477559f --- /dev/null +++ b/scripts/spm_test_schemes/FirebaseSharedSwiftTests.xcscheme @@ -0,0 +1,52 @@ + + + + + + + + + + + + + + + + + + + + +