Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
7fe5306
Moved Database.Encoder/Decoder to a new target and rebranded as Struc…
mortenbekditlevsen Oct 23, 2021
d806bf1
Import FirebaseSharedSwift and add test helpers
mortenbekditlevsen Oct 23, 2021
6df778d
Call a callable with encoded entity and decode the response. Also add…
mortenbekditlevsen Oct 23, 2021
47ca483
Fix header for EncoderDecoder.swift
mortenbekditlevsen Oct 27, 2021
9b87b59
Add scheme for running FirebaseSharedSwiftTests. Adding blindfolded a…
mortenbekditlevsen Oct 28, 2021
b900e9c
Merge remote-tracking branch 'morten/firebaseswiftshared' into codabl…
paulb777 Oct 29, 2021
9f27bce
Merge branch 'codable-refactor' into firebasefunctionsswift
mortenbekditlevsen Oct 30, 2021
ec86d61
Style
mortenbekditlevsen Oct 30, 2021
bfb1353
Style
mortenbekditlevsen Oct 30, 2021
c44c314
Implemented suggestion from Sebastian Schmidt
mortenbekditlevsen Nov 10, 2021
36d8d89
Moved Database.Encoder/Decoder to a new target and rebranded as Struc…
mortenbekditlevsen Oct 23, 2021
c6c8136
Import FirebaseSharedSwift and add test helpers
mortenbekditlevsen Oct 23, 2021
4a57a11
Fix header for EncoderDecoder.swift
mortenbekditlevsen Oct 27, 2021
fdf2355
Add scheme for running FirebaseSharedSwiftTests. Adding blindfolded a…
mortenbekditlevsen Oct 28, 2021
5d8275a
Merge branch 'codable-refactor2' into firebasefunctionsswift
mortenbekditlevsen Nov 17, 2021
2f1c7ae
Added tests for Firebase Functions Swift overlay. Added doc comments.…
mortenbekditlevsen Nov 18, 2021
8cb165b
Added tests for Firebase Functions Swift overlay. Added doc comments.…
mortenbekditlevsen Nov 18, 2021
3205e85
Move encoder and decoder to httpsCallable function
mortenbekditlevsen Nov 20, 2021
603c3e2
style.sh
mortenbekditlevsen Nov 23, 2021
381cf5d
Merge branch 'master' into firebasefunctionsswift
mortenbekditlevsen Nov 23, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 23 additions & 0 deletions FirebaseDatabaseSwift/Sources/Codable/EncoderDecoder.swift
Original file line number Diff line number Diff line change
@@ -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
}
111 changes: 111 additions & 0 deletions FirebaseDatabaseSwift/Tests/Codable/ServerValueCodingTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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<X: Equatable & Codable>(_ model: X, file: StaticString = #file,
line: UInt = #line) -> CodableSubject<X> {
return CodableSubject(model, file: file, line: line)
}

func assertThat<X: Equatable & Encodable>(_ model: X, file: StaticString = #file,
line: UInt = #line) -> EncodableSubject<X> {
return EncodableSubject(model, file: file, line: line)
}

class EncodableSubject<X: Equatable & Encodable> {
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<X: Equatable & Codable>: EncodableSubject<X> {
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<X: Equatable & Codable>(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<X: Equatable & Codable>(to _: X.Type,
using decoder: Database.Decoder = .init()) -> Void {
XCTAssertThrowsError(
try decoder.decode(X.self, from: subject),
file: file,
line: line
)
}
}
142 changes: 142 additions & 0 deletions FirebaseFunctionsSwift/Sources/Codable/Callable+Codable.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
/*
* Copyright 2020 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 Foundation
import FirebaseFunctions
import FirebaseSharedSwift

public extension Functions {
/**
* Creates a reference to the Callable HTTPS trigger with the given name, the type of an `Encodable`
* request and the type of a `Decodable` response.
*
* - Parameter name: The name of the Callable HTTPS trigger
* - Parameter requestType: The type of the `Encodable` entity to use for requests to this `Callable`
* - Parameter responseType: The type of the `Decodable` entity to use for responses from this `Callable`
*/
func httpsCallable<Request: Encodable,
Response: Decodable>(_ name: String,
requestType: Request.Type,
responseType: Response.Type,
encoder: StructureEncoder = StructureEncoder(),
decoder: StructureDecoder = StructureDecoder())
-> Callable<Request, Response> {
return Callable(callable: httpsCallable(name), encoder: encoder, decoder: decoder)
}
}

/**
* A `Callable` is reference to a particular Callable HTTPS trigger in Cloud Functions.
*/
public struct Callable<Request: Encodable, Response: Decodable> {
/**
* The timeout to use when calling the function. Defaults to 60 seconds.
*/
public var timeoutInterval: TimeInterval {
get {
callable.timeoutInterval
}
set {
callable.timeoutInterval = newValue
}
}

enum CallableError: Error {
case internalError
}

private let callable: HTTPSCallable
private let encoder: StructureEncoder
private let decoder: StructureDecoder

init(callable: HTTPSCallable, encoder: StructureEncoder, decoder: StructureDecoder) {
self.callable = callable
self.encoder = encoder
self.decoder = decoder
}

/**
* Executes this Callable HTTPS trigger asynchronously.
*
* The data passed into the trigger must be of the generic `Request` type:
*
* The request to the Cloud Functions backend made by this method automatically includes a
* Firebase Instance ID token to identify the app instance. If a user is logged in with Firebase
* Auth, an auth ID token for the user is also automatically included.
*
* Firebase Instance ID sends data to the Firebase backend periodically to collect information
* regarding the app instance. To stop this, see `[FIRInstanceID deleteIDWithHandler:]`. It
* resumes with a new Instance ID the next time you call this method.
*
* - Parameter data: Parameters to pass to the trigger.
* - Parameter completion: The block to call when the HTTPS request has completed.
*
* - Throws: An error if any value throws an error during encoding.
*/
public func call(_ data: Request,
completion: @escaping (Result<Response, Error>)
-> Void) throws {
let encoded = try encoder.encode(data)

callable.call(encoded) { result, error in
do {
if let result = result {
let decoded = try decoder.decode(Response.self, from: result.data)
completion(.success(decoded))
} else if let error = error {
completion(.failure(error))
} else {
completion(.failure(CallableError.internalError))
}
} catch {
completion(.failure(error))
}
}
}

#if compiler(>=5.5) && canImport(_Concurrency)
/**
* Executes this Callable HTTPS trigger asynchronously.
*
* The data passed into the trigger must be of the generic `Request` type:
*
* The request to the Cloud Functions backend made by this method automatically includes a
* Firebase Instance ID token to identify the app instance. If a user is logged in with Firebase
* Auth, an auth ID token for the user is also automatically included.
*
* Firebase Instance ID sends data to the Firebase backend periodically to collect information
* regarding the app instance. To stop this, see `[FIRInstanceID deleteIDWithHandler:]`. It
* resumes with a new Instance ID the next time you call this method.
*
* - Parameter data: The `Request` representing the data to pass to the trigger.
*
* - Throws: An error if any value throws an error during encoding.
* - Throws: An error if any value throws an error during decoding.
* - Throws: An error if the callable fails to complete
*
* - Returns: The decoded `Response` value
*/
@available(iOS 15, tvOS 15, macOS 12, watchOS 8, *)
public func call(_ data: Request,
encoder: StructureEncoder = StructureEncoder(),
decoder: StructureDecoder =
StructureDecoder()) async throws -> Response {
let encoded = try encoder.encode(data)
let result = try await callable.call(encoded)
return try decoder.decode(Response.self, from: result.data)
}
#endif
}
Loading