Skip to content

Commit 526f8ca

Browse files
committed
Merge branch 'codable-refactor3'
2 parents 944416b + cdab605 commit 526f8ca

File tree

15 files changed

+1488
-395
lines changed

15 files changed

+1488
-395
lines changed

.github/workflows/functions.yml

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -49,11 +49,13 @@ jobs:
4949
- name: Initialize xcodebuild
5050
run: scripts/setup_spm_tests.sh
5151
- name: iOS Unit Tests
52-
run: scripts/third_party/travis/retry.sh ./scripts/build.sh FirebaseFunctions iOS spmbuildonly
52+
run: scripts/third_party/travis/retry.sh ./scripts/build.sh FunctionsUnit iOS spm
5353
- name: Integration Test Server
5454
run: FirebaseFunctions/Backend/start.sh synchronous
55-
- name: iOS Swift Integration Tests
55+
- name: iOS Swift Integration Tests (Objective C library)
5656
run: scripts/third_party/travis/retry.sh ./scripts/build.sh FunctionsSwiftIntegration iOS spm
57+
- name: iOS Swift Integration Tests (including Swift library)
58+
run: scripts/third_party/travis/retry.sh ./scripts/build.sh FirebaseFunctionsSwiftUnit iOS spm
5759
- name: iOS Objective C Integration Tests
5860
run: scripts/third_party/travis/retry.sh ./scripts/build.sh FunctionsIntegration iOS spm
5961
- name: Combine Unit Tests
@@ -74,7 +76,7 @@ jobs:
7476
- name: Initialize xcodebuild
7577
run: scripts/setup_spm_tests.sh
7678
- name: Unit Tests
77-
run: scripts/third_party/travis/retry.sh ./scripts/build.sh FirebaseFunctions ${{ matrix.target }} spmbuildonly
79+
run: scripts/third_party/travis/retry.sh ./scripts/build.sh FunctionsUnit ${{ matrix.target }} spm
7880

7981
catalyst:
8082
# Don't run on private repo unless it is a PR.
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
/*
2+
* Copyright 2021 Google LLC
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
import FirebaseDatabase
18+
import FirebaseSharedSwift
19+
20+
extension Database {
21+
public typealias Encoder = FirebaseDataEncoder
22+
public typealias Decoder = FirebaseDataDecoder
23+
}

FirebaseDatabaseSwift/Tests/Codable/ServerValueCodingTests.swift

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,3 +87,105 @@ extension CurrencyAmount: ExpressibleByFloatLiteral {
8787
self.value = Decimal(value)
8888
}
8989
}
90+
91+
private func assertThat(_ dictionary: [String: Any],
92+
file: StaticString = #file,
93+
line: UInt = #line) -> DictionarySubject {
94+
return DictionarySubject(dictionary, file: file, line: line)
95+
}
96+
97+
func assertThat<X: Equatable & Codable>(_ model: X, file: StaticString = #file,
98+
line: UInt = #line) -> CodableSubject<X> {
99+
return CodableSubject(model, file: file, line: line)
100+
}
101+
102+
func assertThat<X: Equatable & Encodable>(_ model: X, file: StaticString = #file,
103+
line: UInt = #line) -> EncodableSubject<X> {
104+
return EncodableSubject(model, file: file, line: line)
105+
}
106+
107+
class EncodableSubject<X: Equatable & Encodable> {
108+
var subject: X
109+
var file: StaticString
110+
var line: UInt
111+
112+
init(_ subject: X, file: StaticString, line: UInt) {
113+
self.subject = subject
114+
self.file = file
115+
self.line = line
116+
}
117+
118+
@discardableResult
119+
func encodes(to expected: [String: Any],
120+
using encoder: Database.Encoder = .init()) -> DictionarySubject {
121+
let encoded = assertEncodes(to: expected, using: encoder)
122+
return DictionarySubject(encoded, file: file, line: line)
123+
}
124+
125+
func failsToEncode() {
126+
do {
127+
let encoder = Database.Encoder()
128+
encoder.keyEncodingStrategy = .convertToSnakeCase
129+
_ = try encoder.encode(subject)
130+
} catch {
131+
return
132+
}
133+
XCTFail("Failed to throw")
134+
}
135+
136+
func failsEncodingAtTopLevel() {
137+
do {
138+
let encoder = Database.Encoder()
139+
encoder.keyEncodingStrategy = .convertToSnakeCase
140+
_ = try encoder.encode(subject)
141+
XCTFail("Failed to throw", file: file, line: line)
142+
} catch EncodingError.invalidValue(_, _) {
143+
return
144+
} catch {
145+
XCTFail("Unrecognized error: \(error)", file: file, line: line)
146+
}
147+
}
148+
149+
private func assertEncodes(to expected: [String: Any],
150+
using encoder: Database.Encoder = .init()) -> [String: Any] {
151+
do {
152+
let enc = try encoder.encode(subject)
153+
XCTAssertEqual(enc as? NSDictionary, expected as NSDictionary, file: file, line: line)
154+
return (enc as! NSDictionary) as! [String: Any]
155+
} catch {
156+
XCTFail("Failed to encode \(X.self): error: \(error)")
157+
return ["": -1]
158+
}
159+
}
160+
}
161+
162+
class CodableSubject<X: Equatable & Codable>: EncodableSubject<X> {
163+
func roundTrips(to expected: [String: Any],
164+
using encoder: Database.Encoder = .init(),
165+
decoder: Database.Decoder = .init()) {
166+
let reverseSubject = encodes(to: expected, using: encoder)
167+
reverseSubject.decodes(to: subject, using: decoder)
168+
}
169+
}
170+
171+
class DictionarySubject {
172+
var subject: [String: Any]
173+
var file: StaticString
174+
var line: UInt
175+
176+
init(_ subject: [String: Any], file: StaticString, line: UInt) {
177+
self.subject = subject
178+
self.file = file
179+
self.line = line
180+
}
181+
182+
func decodes<X: Equatable & Codable>(to expected: X,
183+
using decoder: Database.Decoder = .init()) -> Void {
184+
do {
185+
let decoded = try decoder.decode(X.self, from: subject)
186+
XCTAssertEqual(decoded, expected)
187+
} catch {
188+
XCTFail("Failed to decode \(X.self): \(error)", file: file, line: line)
189+
}
190+
}
191+
}

FirebaseFunctions/Tests/SwiftIntegration/IntegrationTests.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ import FirebaseFunctions
1818
import FirebaseFunctionsTestingSupport
1919
import XCTest
2020

21-
/// This file was intitialized as a direct port of the Objective C
21+
/// This file was initialized as a direct port of the Objective C
2222
/// FirebaseFunctions/Tests/Integration/FIRIntegrationTests.m
2323
///
2424
/// The tests require the emulator to be running with `FirebaseFunctions/Backend/start.sh synchronous`
Lines changed: 182 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,182 @@
1+
// Copyright 2021 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
import Foundation
16+
import FirebaseFunctions
17+
import FirebaseSharedSwift
18+
19+
public extension Functions {
20+
/// Creates a reference to the Callable HTTPS trigger with the given name, the type of an `Encodable`
21+
/// request and the type of a `Decodable` response.
22+
/// - Parameter name: The name of the Callable HTTPS trigger
23+
/// - Parameter requestType: The type of the `Encodable` entity to use for requests to this `Callable`
24+
/// - Parameter responseType: The type of the `Decodable` entity to use for responses from this `Callable`
25+
func httpsCallable<Request: Encodable,
26+
Response: Decodable>(_ name: String,
27+
requestAs requestType: Request.Type = Request.self,
28+
responseAs responseType: Response.Type = Response.self,
29+
encoder: FirebaseDataEncoder = FirebaseDataEncoder(),
30+
decoder: FirebaseDataDecoder = FirebaseDataDecoder())
31+
-> Callable<Request, Response> {
32+
return Callable(callable: httpsCallable(name), encoder: encoder, decoder: decoder)
33+
}
34+
}
35+
36+
// A `Callable` is reference to a particular Callable HTTPS trigger in Cloud Functions.
37+
public struct Callable<Request: Encodable, Response: Decodable> {
38+
/// The timeout to use when calling the function. Defaults to 60 seconds.
39+
public var timeoutInterval: TimeInterval {
40+
get {
41+
callable.timeoutInterval
42+
}
43+
set {
44+
callable.timeoutInterval = newValue
45+
}
46+
}
47+
48+
enum CallableError: Error {
49+
case internalError
50+
}
51+
52+
private let callable: HTTPSCallable
53+
private let encoder: FirebaseDataEncoder
54+
private let decoder: FirebaseDataDecoder
55+
56+
init(callable: HTTPSCallable, encoder: FirebaseDataEncoder, decoder: FirebaseDataDecoder) {
57+
self.callable = callable
58+
self.encoder = encoder
59+
self.decoder = decoder
60+
}
61+
62+
/// Executes this Callable HTTPS trigger asynchronously.
63+
///
64+
/// The data passed into the trigger must be of the generic `Request` type:
65+
///
66+
/// The request to the Cloud Functions backend made by this method automatically includes a
67+
/// FCM token to identify the app instance. If a user is logged in with Firebase
68+
/// Auth, an auth ID token for the user is also automatically included.
69+
///
70+
/// Firebase Cloud Messaging sends data to the Firebase backend periodically to collect information
71+
/// regarding the app instance. To stop this, see `Messaging.deleteData()`. It
72+
/// resumes with a new FCM Token the next time you call this method.
73+
///
74+
/// - Parameter data: Parameters to pass to the trigger.
75+
/// - Parameter completion: The block to call when the HTTPS request has completed.
76+
public func call(_ data: Request,
77+
completion: @escaping (Result<Response, Error>)
78+
-> Void) {
79+
do {
80+
let encoded = try encoder.encode(data)
81+
82+
callable.call(encoded) { result, error in
83+
do {
84+
if let result = result {
85+
let decoded = try decoder.decode(Response.self, from: result.data)
86+
completion(.success(decoded))
87+
} else if let error = error {
88+
completion(.failure(error))
89+
} else {
90+
completion(.failure(CallableError.internalError))
91+
}
92+
} catch {
93+
completion(.failure(error))
94+
}
95+
}
96+
} catch {
97+
completion(.failure(error))
98+
}
99+
}
100+
101+
/// Creates a directly callable function.
102+
///
103+
/// This allows users to call a HTTPS Callable Function like a normal Swift function:
104+
/// ```swift
105+
/// let greeter = functions.httpsCallable("greeter",
106+
/// requestType: GreetingRequest.self,
107+
/// responseType: GreetingResponse.self)
108+
/// greeter(data) { result in
109+
/// print(result.greeting)
110+
/// }
111+
/// ```
112+
/// You can also call a HTTPS Callable function using the following syntax:
113+
/// ```swift
114+
/// let greeter: Callable<GreetingRequest, GreetingResponse> = functions.httpsCallable("greeter")
115+
/// greeter(data) { result in
116+
/// print(result.greeting)
117+
/// }
118+
/// ```
119+
/// - Parameters:
120+
/// - data: Parameters to pass to the trigger.
121+
/// - completion: The block to call when the HTTPS request has completed.
122+
public func callAsFunction(_ data: Request,
123+
completion: @escaping (Result<Response, Error>)
124+
-> Void) {
125+
call(data, completion: completion)
126+
}
127+
128+
#if compiler(>=5.5) && canImport(_Concurrency)
129+
/// Executes this Callable HTTPS trigger asynchronously.
130+
///
131+
/// The data passed into the trigger must be of the generic `Request` type:
132+
///
133+
/// The request to the Cloud Functions backend made by this method automatically includes a
134+
/// FCM token to identify the app instance. If a user is logged in with Firebase
135+
/// Auth, an auth ID token for the user is also automatically included.
136+
///
137+
/// Firebase Cloud Messaging sends data to the Firebase backend periodically to collect information
138+
/// regarding the app instance. To stop this, see `Messaging.deleteData()`. It
139+
/// resumes with a new FCM Token the next time you call this method.
140+
///
141+
/// - Parameter data: The `Request` representing the data to pass to the trigger.
142+
///
143+
/// - Throws: An error if any value throws an error during encoding.
144+
/// - Throws: An error if any value throws an error during decoding.
145+
/// - Throws: An error if the callable fails to complete
146+
///
147+
/// - Returns: The decoded `Response` value
148+
@available(iOS 15, tvOS 15, macOS 12, watchOS 8, *)
149+
public func call(_ data: Request,
150+
encoder: FirebaseDataEncoder = FirebaseDataEncoder(),
151+
decoder: FirebaseDataDecoder =
152+
FirebaseDataDecoder()) async throws -> Response {
153+
let encoded = try encoder.encode(data)
154+
let result = try await callable.call(encoded)
155+
return try decoder.decode(Response.self, from: result.data)
156+
}
157+
158+
/// Creates a directly callable function.
159+
///
160+
/// This allows users to call a HTTPS Callable Function like a normal Swift function:
161+
/// ```swift
162+
/// let greeter = functions.httpsCallable("greeter",
163+
/// requestType: GreetingRequest.self,
164+
/// responseType: GreetingResponse.self)
165+
/// let result = try await greeter(data)
166+
/// print(result.greeting)
167+
/// ```
168+
/// You can also call a HTTPS Callable function using the following syntax:
169+
/// ```swift
170+
/// let greeter: Callable<GreetingRequest, GreetingResponse> = functions.httpsCallable("greeter")
171+
/// let result = try await greeter(data)
172+
/// print(result.greeting)
173+
/// ```
174+
/// - Parameters:
175+
/// - data: Parameters to pass to the trigger.
176+
/// - Returns: The decoded `Response` value
177+
@available(iOS 15, tvOS 15, macOS 12, watchOS 8, *)
178+
public func callAsFunction(_ data: Request) async throws -> Response {
179+
return try await call(data)
180+
}
181+
#endif
182+
}

0 commit comments

Comments
 (0)