Skip to content

Commit 02dea1c

Browse files
committed
機能追加: FirestoreClientType プロトコルを追加
FirestoreClient (actor) のモック可能な抽象化レイヤーとして FirestoreClientType プロトコルを導入。 - 全パブリックメソッド(21メソッド)をプロトコル要件として定義 - デフォルト引数を提供する convenience extension を追加 - FirestoreClient の既存APIに変更なし(後方互換性100%) - writeTransaction は複雑なシグネチャのため除外 これにより、依存先プロジェクトで Mockolo 等を使用した ユニットテスト用モックの生成が可能になる。
1 parent b6f64ef commit 02dea1c

2 files changed

Lines changed: 395 additions & 0 deletions

File tree

Sources/Firestore/FirestoreClient.swift

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -930,3 +930,6 @@ extension FirestoreClient {
930930
}
931931
}
932932
}
933+
934+
// MARK: - FirestoreClientType Conformance
935+
extension FirestoreClient: FirestoreClientType {}
Lines changed: 392 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,392 @@
1+
//
2+
// FirestoreClientType.swift
3+
// EasyFirebaseFirestore
4+
//
5+
// Created by Fumiya Tanaka on 2025/02/11.
6+
//
7+
8+
import FirebaseFirestore
9+
10+
/// A protocol abstracting FirestoreClient for dependency injection and testing.
11+
///
12+
/// All methods are marked `async` so that both actor-isolated implementations
13+
/// (where actor isolation satisfies the async requirement per SE-0306)
14+
/// and regular class-based mocks can conform.
15+
///
16+
/// `writeTransaction` is intentionally excluded due to its complex signature
17+
/// (`WritableKeyPath` + tuple closure). It can be added later if needed.
18+
///
19+
/// @mockable
20+
public protocol FirestoreClientType {
21+
22+
// MARK: - FirestoreModel - Write
23+
24+
@discardableResult
25+
func write<Model: FirestoreModel>(
26+
_ model: Model,
27+
newDocumentIdIfNotExists: String?
28+
) async throws -> DocumentReference
29+
30+
// MARK: - FirestoreModel - Get
31+
32+
func get<Model: FirestoreModel>(
33+
documentId: String,
34+
includeCache: Bool
35+
) async throws -> Model
36+
37+
func get<Model: FirestoreModel>(
38+
filter: [FirestoreQueryFilter],
39+
includeCache: Bool,
40+
order: [FirestoreQueryOrder],
41+
limit: Int?
42+
) async throws -> [Model]
43+
44+
// MARK: - FirestoreModel - Listen
45+
46+
func listen<Model: FirestoreModel>(
47+
documentId: String,
48+
includeCache: Bool
49+
) async -> AsyncThrowingStream<Model, Error>
50+
51+
func listen<Model: FirestoreModel>(
52+
filter: [FirestoreQueryFilter],
53+
includeCache: Bool,
54+
order: [FirestoreQueryOrder],
55+
limit: Int?
56+
) async -> AsyncThrowingStream<[Model], Error>
57+
58+
func listenChanges<Model: FirestoreModel>(
59+
filter: [FirestoreQueryFilter],
60+
includeCache: Bool,
61+
order: [FirestoreQueryOrder],
62+
limit: Int?
63+
) async -> AsyncThrowingStream<[FirestoreDocumentChange<Model>], Error>
64+
65+
// MARK: - FirestoreModel - Delete
66+
67+
func delete<Model: FirestoreModel>(_ model: Model) async throws
68+
69+
// MARK: - SubCollectionModel - Create/Update
70+
71+
func create<Model: SubCollectionModel>(_ model: Model) async throws
72+
73+
func update<Model: FirestoreModel & SubCollectionModel>(
74+
_ model: Model
75+
) async throws
76+
77+
// MARK: - SubCollectionModel - Get
78+
79+
func get<Model: SubCollectionModel>(
80+
parent parentDocumentId: String,
81+
filter: [FirestoreQueryFilter],
82+
includeCache: Bool,
83+
order: [FirestoreQueryOrder],
84+
limit: Int?
85+
) async throws -> [Model]
86+
87+
func get<Model: SubCollectionModel>(
88+
documentId: String,
89+
parent parentUid: String,
90+
includeCache: Bool
91+
) async throws -> Model
92+
93+
// MARK: - SubCollectionModel - Listen
94+
95+
func listen<Model: FirestoreModel & SubCollectionModel>(
96+
parentDocumentId: String,
97+
documentId: String,
98+
filter: [FirestoreQueryFilter],
99+
includeCache: Bool,
100+
order: [FirestoreQueryOrder],
101+
limit: Int?
102+
) async -> AsyncThrowingStream<Model, Error>
103+
104+
func listen<Model: FirestoreModel & SubCollectionModel>(
105+
parent: String,
106+
superParent: String?,
107+
filter: [FirestoreQueryFilter],
108+
includeCache: Bool,
109+
order: [FirestoreQueryOrder],
110+
limit: Int?
111+
) async -> AsyncThrowingStream<[Model], Error>
112+
113+
func listenChanges<Model: FirestoreModel & SubCollectionModel>(
114+
parent: String,
115+
superParent: String?,
116+
filter: [FirestoreQueryFilter],
117+
includeCache: Bool,
118+
order: [FirestoreQueryOrder],
119+
limit: Int?
120+
) async -> AsyncThrowingStream<[FirestoreDocumentChange<Model>], Error>
121+
122+
// MARK: - CollectionGroup
123+
124+
func getCollectionGroup<Model: FirestoreModel>(
125+
filter: [FirestoreQueryFilter],
126+
includeCache: Bool,
127+
order: [FirestoreQueryOrder],
128+
limit: Int?
129+
) async throws -> [Model]
130+
131+
func listenCollectionGroup<Model: FirestoreModel>(
132+
collectionName: String,
133+
filter: [FirestoreQueryFilter],
134+
includeCache: Bool,
135+
order: [FirestoreQueryOrder],
136+
limit: Int?
137+
) async -> AsyncThrowingStream<[Model], Error>
138+
139+
func listenCollectionGroupChanges<Model: FirestoreModel>(
140+
collectionName: String,
141+
filter: [FirestoreQueryFilter],
142+
includeCache: Bool,
143+
order: [FirestoreQueryOrder],
144+
limit: Int?
145+
) async -> AsyncThrowingStream<[FirestoreDocumentChange<Model>], Error>
146+
147+
// MARK: - Listener Management
148+
149+
func stopListening<Model: FirestoreModel>(type: Model.Type) async
150+
func stopListening<Model: FirestoreModel>(
151+
type: Model.Type,
152+
documentID: String
153+
) async
154+
func stopListening(ref: DocumentReference) async
155+
func stopListeningAll() async
156+
}
157+
158+
// MARK: - Convenience Methods (Default Parameters)
159+
160+
extension FirestoreClientType {
161+
162+
// MARK: FirestoreModel - Write
163+
164+
@discardableResult
165+
public func write<Model: FirestoreModel>(
166+
_ model: Model
167+
) async throws -> DocumentReference {
168+
try await write(model, newDocumentIdIfNotExists: nil)
169+
}
170+
171+
// MARK: FirestoreModel - Get
172+
173+
public func get<Model: FirestoreModel>(
174+
documentId: String
175+
) async throws -> Model {
176+
try await get(documentId: documentId, includeCache: true)
177+
}
178+
179+
public func get<Model: FirestoreModel>(
180+
filter: [FirestoreQueryFilter] = [],
181+
order: [FirestoreQueryOrder] = [],
182+
limit: Int? = nil
183+
) async throws -> [Model] {
184+
try await get(
185+
filter: filter,
186+
includeCache: true,
187+
order: order,
188+
limit: limit
189+
)
190+
}
191+
192+
public func get<Model: FirestoreModel>(
193+
filter: [FirestoreQueryFilter] = [],
194+
includeCache: Bool,
195+
order: [FirestoreQueryOrder] = [],
196+
limit: Int? = nil
197+
) async throws -> [Model] {
198+
try await get(
199+
filter: filter,
200+
includeCache: includeCache,
201+
order: order,
202+
limit: limit
203+
)
204+
}
205+
206+
// MARK: FirestoreModel - Listen
207+
208+
public func listen<Model: FirestoreModel>(
209+
documentId: String
210+
) async -> AsyncThrowingStream<Model, Error> {
211+
await listen(documentId: documentId, includeCache: true)
212+
}
213+
214+
public func listen<Model: FirestoreModel>(
215+
filter: [FirestoreQueryFilter] = [],
216+
order: [FirestoreQueryOrder] = [],
217+
limit: Int? = nil
218+
) async -> AsyncThrowingStream<[Model], Error> {
219+
await listen(
220+
filter: filter,
221+
includeCache: true,
222+
order: order,
223+
limit: limit
224+
)
225+
}
226+
227+
public func listen<Model: FirestoreModel>(
228+
filter: [FirestoreQueryFilter] = [],
229+
includeCache: Bool,
230+
order: [FirestoreQueryOrder] = [],
231+
limit: Int? = nil
232+
) async -> AsyncThrowingStream<[Model], Error> {
233+
await listen(
234+
filter: filter,
235+
includeCache: includeCache,
236+
order: order,
237+
limit: limit
238+
)
239+
}
240+
241+
public func listenChanges<Model: FirestoreModel>(
242+
filter: [FirestoreQueryFilter] = [],
243+
order: [FirestoreQueryOrder] = [],
244+
limit: Int? = nil
245+
) async -> AsyncThrowingStream<[FirestoreDocumentChange<Model>], Error> {
246+
await listenChanges(
247+
filter: filter,
248+
includeCache: true,
249+
order: order,
250+
limit: limit
251+
)
252+
}
253+
254+
public func listenChanges<Model: FirestoreModel>(
255+
filter: [FirestoreQueryFilter] = [],
256+
includeCache: Bool,
257+
order: [FirestoreQueryOrder] = [],
258+
limit: Int? = nil
259+
) async -> AsyncThrowingStream<[FirestoreDocumentChange<Model>], Error> {
260+
await listenChanges(
261+
filter: filter,
262+
includeCache: includeCache,
263+
order: order,
264+
limit: limit
265+
)
266+
}
267+
268+
// MARK: SubCollectionModel - Get
269+
270+
public func get<Model: SubCollectionModel>(
271+
parent parentDocumentId: String,
272+
filter: [FirestoreQueryFilter] = [],
273+
order: [FirestoreQueryOrder] = [],
274+
limit: Int? = nil
275+
) async throws -> [Model] {
276+
try await get(
277+
parent: parentDocumentId,
278+
filter: filter,
279+
includeCache: true,
280+
order: order,
281+
limit: limit
282+
)
283+
}
284+
285+
public func get<Model: SubCollectionModel>(
286+
parent parentDocumentId: String,
287+
filter: [FirestoreQueryFilter] = [],
288+
includeCache: Bool,
289+
order: [FirestoreQueryOrder] = [],
290+
limit: Int? = nil
291+
) async throws -> [Model] {
292+
try await get(
293+
parent: parentDocumentId,
294+
filter: filter,
295+
includeCache: includeCache,
296+
order: order,
297+
limit: limit
298+
)
299+
}
300+
301+
public func get<Model: SubCollectionModel>(
302+
documentId: String,
303+
parent parentUid: String
304+
) async throws -> Model {
305+
try await get(
306+
documentId: documentId,
307+
parent: parentUid,
308+
includeCache: true
309+
)
310+
}
311+
312+
// MARK: SubCollectionModel - Listen
313+
314+
public func listen<Model: FirestoreModel & SubCollectionModel>(
315+
parent: String,
316+
superParent: String? = nil,
317+
filter: [FirestoreQueryFilter],
318+
order: [FirestoreQueryOrder],
319+
limit: Int?
320+
) async -> AsyncThrowingStream<[Model], Error> {
321+
await listen(
322+
parent: parent,
323+
superParent: superParent,
324+
filter: filter,
325+
includeCache: true,
326+
order: order,
327+
limit: limit
328+
)
329+
}
330+
331+
public func listenChanges<Model: FirestoreModel & SubCollectionModel>(
332+
parent: String,
333+
superParent: String? = nil,
334+
filter: [FirestoreQueryFilter],
335+
order: [FirestoreQueryOrder],
336+
limit: Int?
337+
) async -> AsyncThrowingStream<[FirestoreDocumentChange<Model>], Error> {
338+
await listenChanges(
339+
parent: parent,
340+
superParent: superParent,
341+
filter: filter,
342+
includeCache: true,
343+
order: order,
344+
limit: limit
345+
)
346+
}
347+
348+
// MARK: CollectionGroup
349+
350+
public func getCollectionGroup<Model: FirestoreModel>(
351+
filter: [FirestoreQueryFilter] = [],
352+
order: [FirestoreQueryOrder] = [],
353+
limit: Int? = nil
354+
) async throws -> [Model] {
355+
try await getCollectionGroup(
356+
filter: filter,
357+
includeCache: true,
358+
order: order,
359+
limit: limit
360+
)
361+
}
362+
363+
public func listenCollectionGroup<Model: FirestoreModel>(
364+
collectionName: String,
365+
filter: [FirestoreQueryFilter] = [],
366+
order: [FirestoreQueryOrder] = [],
367+
limit: Int? = nil
368+
) async -> AsyncThrowingStream<[Model], Error> {
369+
await listenCollectionGroup(
370+
collectionName: collectionName,
371+
filter: filter,
372+
includeCache: true,
373+
order: order,
374+
limit: limit
375+
)
376+
}
377+
378+
public func listenCollectionGroupChanges<Model: FirestoreModel>(
379+
collectionName: String,
380+
filter: [FirestoreQueryFilter] = [],
381+
order: [FirestoreQueryOrder] = [],
382+
limit: Int? = nil
383+
) async -> AsyncThrowingStream<[FirestoreDocumentChange<Model>], Error> {
384+
await listenCollectionGroupChanges(
385+
collectionName: collectionName,
386+
filter: filter,
387+
includeCache: true,
388+
order: order,
389+
limit: limit
390+
)
391+
}
392+
}

0 commit comments

Comments
 (0)