diff --git a/Core/EntityGateway/Cache/CachePersonsGateway.swift b/Core/EntityGateway/Cache/CachePersonsGateway.swift index 761748d..b47aa63 100644 --- a/Core/EntityGateway/Cache/CachePersonsGateway.swift +++ b/Core/EntityGateway/Cache/CachePersonsGateway.swift @@ -21,57 +21,91 @@ public final class CachePersonsGateway: PersonsGateway { public func add(parameters: AddPersonParameters, completionHandler: @escaping AddPersonEntityGatewayCompletionHandler) { Task { do { - let savedImages = try await save(newImages(in: parameters)) - let result = await addToCoreData(parameters: parameters) - if case .failure = result { - await deleteBestEffort(savedImages) - } - await complete(result, using: completionHandler) + await complete(.success(try await add(parameters: parameters)), using: completionHandler) + } catch let error as CoreError { + await complete(.failure(error), using: completionHandler) } catch { await complete(.failure(CoreError(error: error)), using: completionHandler) } } } + public func add(parameters: AddPersonParameters) async throws -> Person { + try Task.checkCancellation() + let savedImages = try await save(newImages(in: parameters)) + do { + try Task.checkCancellation() + return try await coreDataGateway.add(parameters: parameters) + } catch { + await deleteBestEffort(savedImages) + throw error + } + } + public func fetchPersons(completionHandler: @escaping FetchPersonsEntityGatewayCompletionHandler) { coreDataGateway.fetchPersons(completionHandler: completionHandler) } + public func fetchPersons() async throws -> [Person] { + try await coreDataGateway.fetchPersons() + } + public func fetchWidgetPersons(completion: @escaping FetchPersonsEntityGatewayCompletionHandler) { coreDataGateway.fetchWidgetPersons(completion: completion) } + public func fetchWidgetPersons() async throws -> [Person] { + try await coreDataGateway.fetchWidgetPersons() + } + public func edit( person: Person, with parameters: AddPersonParameters, completionHandler: @escaping EditPersonEntityGatewayCompletionHandler ) { Task { do { - let savedImages = try await save(newImages(in: parameters)) - let result = await editInCoreData(person: person, parameters: parameters) - switch result { - case .success: - await deleteBestEffort(outdatedImages(for: person, parameters: parameters)) - case .failure: - await deleteBestEffort(savedImages) - } - await complete(result, using: completionHandler) + await complete(.success(try await edit(person: person, with: parameters)), using: completionHandler) + } catch let error as CoreError { + await complete(.failure(error), using: completionHandler) } catch { await complete(.failure(CoreError(error: error)), using: completionHandler) } } } + public func edit(person: Person, with parameters: AddPersonParameters) async throws -> Person { + try Task.checkCancellation() + let savedImages = try await save(newImages(in: parameters)) + do { + try Task.checkCancellation() + let updatedPerson = try await coreDataGateway.edit(person: person, with: parameters) + await deleteBestEffort(outdatedImages(for: person, parameters: parameters)) + return updatedPerson + } catch { + await deleteBestEffort(savedImages) + throw error + } + } + public func remove(person: Person, completionHandler: @escaping RemovePersonEntityGatewayCompletionHandler) { Task { - let result = await removeFromCoreData(person: person) - if case .success = result { - await deleteBestEffort(storedImages(for: person)) + do { + try await remove(person: person) + await complete(.success(()), using: completionHandler) + } catch let error as CoreError { + await complete(.failure(error), using: completionHandler) + } catch { + await complete(.failure(CoreError(error: error)), using: completionHandler) } - await complete(result, using: completionHandler) } } + public func remove(person: Person) async throws { + try Task.checkCancellation() + try await coreDataGateway.remove(person: person) + await deleteBestEffort(storedImages(for: person)) + } + // MARK: - Images private func newImages(in parameters: AddPersonParameters) -> [PersonImage] { @@ -100,6 +134,7 @@ public final class CachePersonsGateway: PersonsGateway { var savedImages = [PersonImage]() do { for image in images { + try Task.checkCancellation() try await imageStore.save(image) savedImages.append(image) } @@ -120,28 +155,6 @@ public final class CachePersonsGateway: PersonsGateway { } } - // MARK: - Core Data bridge - - private func addToCoreData(parameters: AddPersonParameters) async -> Result { - await withCheckedContinuation { continuation in - coreDataGateway.add(parameters: parameters) { continuation.resume(returning: $0) } - } - } - - private func editInCoreData( - person: Person, parameters: AddPersonParameters - ) async -> Result { - await withCheckedContinuation { continuation in - coreDataGateway.edit(person: person, with: parameters) { continuation.resume(returning: $0) } - } - } - - private func removeFromCoreData(person: Person) async -> Result { - await withCheckedContinuation { continuation in - coreDataGateway.remove(person: person) { continuation.resume(returning: $0) } - } - } - private func complete( _ result: Result, using completion: @escaping (Result) -> Void ) async { diff --git a/Core/Gateways/PersonsGateway.swift b/Core/Gateways/PersonsGateway.swift index 169e3eb..8aaca96 100644 --- a/Core/Gateways/PersonsGateway.swift +++ b/Core/Gateways/PersonsGateway.swift @@ -15,10 +15,57 @@ public typealias RemovePersonEntityGatewayCompletionHandler = (_ result: Result< public protocol PersonsGateway { func add(parameters: AddPersonParameters, completionHandler: @escaping AddPersonEntityGatewayCompletionHandler) + func add(parameters: AddPersonParameters) async throws -> Person func fetchPersons(completionHandler: @escaping FetchPersonsEntityGatewayCompletionHandler) + func fetchPersons() async throws -> [Person] func edit( person: Person, with parameters: AddPersonParameters, completionHandler: @escaping EditPersonEntityGatewayCompletionHandler) + func edit(person: Person, with parameters: AddPersonParameters) async throws -> Person func remove(person: Person, completionHandler: @escaping RemovePersonEntityGatewayCompletionHandler) + func remove(person: Person) async throws func fetchWidgetPersons(completion: @escaping FetchPersonsEntityGatewayCompletionHandler) + func fetchWidgetPersons() async throws -> [Person] +} + +public extension PersonsGateway { + + func add(parameters: AddPersonParameters) async throws -> Person { + try Task.checkCancellation() + return try await withCheckedThrowingContinuation { continuation in + add(parameters: parameters) { continuation.resume(with: $0) } + } + } + + func fetchPersons() async throws -> [Person] { + try Task.checkCancellation() + let persons = try await withCheckedThrowingContinuation { continuation in + fetchPersons { continuation.resume(with: $0) } + } + try Task.checkCancellation() + return persons + } + + func edit(person: Person, with parameters: AddPersonParameters) async throws -> Person { + try Task.checkCancellation() + return try await withCheckedThrowingContinuation { continuation in + edit(person: person, with: parameters) { continuation.resume(with: $0) } + } + } + + func remove(person: Person) async throws { + try Task.checkCancellation() + try await withCheckedThrowingContinuation { continuation in + remove(person: person) { continuation.resume(with: $0) } + } + } + + func fetchWidgetPersons() async throws -> [Person] { + try Task.checkCancellation() + let persons = try await withCheckedThrowingContinuation { continuation in + fetchWidgetPersons { continuation.resume(with: $0) } + } + try Task.checkCancellation() + return persons + } } diff --git a/Core/UseCases/AddPersonUseCase.swift b/Core/UseCases/AddPersonUseCase.swift index 6e224e0..99a7f13 100644 --- a/Core/UseCases/AddPersonUseCase.swift +++ b/Core/UseCases/AddPersonUseCase.swift @@ -12,6 +12,7 @@ public typealias AddPersonUseCaseCompletionHandler = (_ person: Result Person } public final class AddPersonUseCaseImplementation: AddPersonUseCase { @@ -27,4 +28,8 @@ public final class AddPersonUseCaseImplementation: AddPersonUseCase { } } + public func add(parameters: AddPersonParameters) async throws -> Person { + try await personsGateway.add(parameters: parameters) + } + } diff --git a/Core/UseCases/EditPersonUseCase.swift b/Core/UseCases/EditPersonUseCase.swift index 77ba5da..8df0c99 100644 --- a/Core/UseCases/EditPersonUseCase.swift +++ b/Core/UseCases/EditPersonUseCase.swift @@ -14,6 +14,7 @@ public protocol EditPersonUseCase { func edit( person: Person, with parameters: AddPersonParameters, completionHandler: @escaping EditPersonUseCaseCompletionHandler) + func edit(person: Person, with parameters: AddPersonParameters) async throws -> Person } public final class EditPersonUseCaseImplementation: EditPersonUseCase { @@ -32,4 +33,8 @@ public final class EditPersonUseCaseImplementation: EditPersonUseCase { } } + public func edit(person: Person, with parameters: AddPersonParameters) async throws -> Person { + try await personsGateway.edit(person: person, with: parameters) + } + } diff --git a/Core/UseCases/FetchPersonsUseCase.swift b/Core/UseCases/FetchPersonsUseCase.swift index 8929dbd..034bdaf 100644 --- a/Core/UseCases/FetchPersonsUseCase.swift +++ b/Core/UseCases/FetchPersonsUseCase.swift @@ -12,7 +12,9 @@ public typealias FetchPersonsUseCaseCompletionHandler = (_ persons: Result<[Pers public protocol FetchPersonsUseCase { func fetchPersons(completionHandler: @escaping FetchPersonsUseCaseCompletionHandler) + func fetchPersons() async throws -> [Person] func fetchWidgetPersons(completion: @escaping FetchPersonsUseCaseCompletionHandler) + func fetchWidgetPersons() async throws -> [Person] } public final class FetchPersonsUseCaseImplementation: FetchPersonsUseCase { @@ -33,4 +35,12 @@ public final class FetchPersonsUseCaseImplementation: FetchPersonsUseCase { public func fetchWidgetPersons(completion: @escaping FetchPersonsUseCaseCompletionHandler) { personsGateway.fetchWidgetPersons(completion: completion) } + + public func fetchPersons() async throws -> [Person] { + try await personsGateway.fetchPersons() + } + + public func fetchWidgetPersons() async throws -> [Person] { + try await personsGateway.fetchWidgetPersons() + } } diff --git a/Core/UseCases/RemovePersonUseCase.swift b/Core/UseCases/RemovePersonUseCase.swift index 26a1416..d7ca3ea 100644 --- a/Core/UseCases/RemovePersonUseCase.swift +++ b/Core/UseCases/RemovePersonUseCase.swift @@ -12,6 +12,7 @@ public typealias RemovePersonUseCaseCompletionHandler = (_ result: Result AddPersonParameters { AddPersonParameters( name: "John", dayOfBirth: Date(), timeOfBirth: Date(), diff --git a/GrowingUpTests/Helpers/UserCases/AddPersonUseCaseSpy.swift b/GrowingUpTests/Helpers/UserCases/AddPersonUseCaseSpy.swift index f5bcae4..948e72b 100644 --- a/GrowingUpTests/Helpers/UserCases/AddPersonUseCaseSpy.swift +++ b/GrowingUpTests/Helpers/UserCases/AddPersonUseCaseSpy.swift @@ -26,6 +26,12 @@ class AddPersonUseCaseSpy: AddPersonUseCase { } } + func add(parameters: AddPersonParameters) async throws -> Person { + try await withCheckedThrowingContinuation { continuation in + add(parameters: parameters) { continuation.resume(with: $0) } + } + } + func callCompletionHandler() { self.completionHandler?(resultToBeReturned) } diff --git a/GrowingUpTests/Helpers/UserCases/DisplayPersonsUseCaseSpy.swift b/GrowingUpTests/Helpers/UserCases/DisplayPersonsUseCaseSpy.swift index 2fd06f9..b716e9a 100644 --- a/GrowingUpTests/Helpers/UserCases/DisplayPersonsUseCaseSpy.swift +++ b/GrowingUpTests/Helpers/UserCases/DisplayPersonsUseCaseSpy.swift @@ -24,4 +24,13 @@ class DisplayPersonsUseCaseSpy: FetchPersonsUseCase { func fetchWidgetPersons(completion: @escaping FetchPersonsUseCaseCompletionHandler) { completion(resultToBeReturned) } + + func fetchPersons() async throws -> [Person] { + displayPersonsCalled = true + return try resultToBeReturned.get() + } + + func fetchWidgetPersons() async throws -> [Person] { + try resultToBeReturned.get() + } } diff --git a/GrowingUpTests/Helpers/UserCases/EditPersonUseCaseSpy.swift b/GrowingUpTests/Helpers/UserCases/EditPersonUseCaseSpy.swift index 32e24c2..41b74e7 100644 --- a/GrowingUpTests/Helpers/UserCases/EditPersonUseCaseSpy.swift +++ b/GrowingUpTests/Helpers/UserCases/EditPersonUseCaseSpy.swift @@ -29,6 +29,12 @@ class EditPersonUseCaseSpy: EditPersonUseCase { } } + func edit(person: Person, with parameters: AddPersonParameters) async throws -> Person { + try await withCheckedThrowingContinuation { continuation in + edit(person: person, with: parameters) { continuation.resume(with: $0) } + } + } + func callCompletionHandler() { self.completionHandler?(resultToBeReturned) } diff --git a/GrowingUpTests/Helpers/UserCases/FetchPersonsUseCaseSpy.swift b/GrowingUpTests/Helpers/UserCases/FetchPersonsUseCaseSpy.swift index 6b77160..0d9d919 100644 --- a/GrowingUpTests/Helpers/UserCases/FetchPersonsUseCaseSpy.swift +++ b/GrowingUpTests/Helpers/UserCases/FetchPersonsUseCaseSpy.swift @@ -21,4 +21,12 @@ final class FetchPersonsUseCaseSpy: FetchPersonsUseCase { func fetchWidgetPersons(completion: @escaping FetchPersonsUseCaseCompletionHandler) { completion(completionResult) } + + func fetchPersons() async throws -> [Person] { + try completionResult.get() + } + + func fetchWidgetPersons() async throws -> [Person] { + try completionResult.get() + } } diff --git a/GrowingUpTests/Helpers/UserCases/RemovePersonUseCaseSpy.swift b/GrowingUpTests/Helpers/UserCases/RemovePersonUseCaseSpy.swift index cfaccd1..540343e 100644 --- a/GrowingUpTests/Helpers/UserCases/RemovePersonUseCaseSpy.swift +++ b/GrowingUpTests/Helpers/UserCases/RemovePersonUseCaseSpy.swift @@ -19,4 +19,9 @@ final class RemovePersonUseCaseSpy: RemovePersonUseCase { didCallRemovePerson = true completionHandler(resultToBeReturned) } + + func remove(person: Person) async throws { + didCallRemovePerson = true + try resultToBeReturned.get() + } } diff --git a/GrowingUpTests/UseCases/AddPersonUseCaseTests.swift b/GrowingUpTests/UseCases/AddPersonUseCaseTests.swift index b1e5fab..0e78284 100644 --- a/GrowingUpTests/UseCases/AddPersonUseCaseTests.swift +++ b/GrowingUpTests/UseCases/AddPersonUseCaseTests.swift @@ -57,4 +57,15 @@ class AddPersonUseCaseTests: XCTestCase { } waitForExpectations(timeout: 1, handler: nil) } + + func testAsyncAddReturnsPerson() async throws { + let parameters = AddPersonParameters.createParameters() + let expectedPerson = Person.createPerson() + personsGatewaySpy.addPersonResultToBeReturned = .success(expectedPerson) + + let person = try await sut.add(parameters: parameters) + + XCTAssertEqual(person, expectedPerson) + XCTAssertEqual(personsGatewaySpy.addPersonParameters, parameters) + } } diff --git a/GrowingUpTests/UseCases/FetchPersonsUseCaseTests.swift b/GrowingUpTests/UseCases/FetchPersonsUseCaseTests.swift index 14b0daa..9d5d31d 100644 --- a/GrowingUpTests/UseCases/FetchPersonsUseCaseTests.swift +++ b/GrowingUpTests/UseCases/FetchPersonsUseCaseTests.swift @@ -50,4 +50,15 @@ class FetchPersonsUseCaseTests: XCTestCase { } waitForExpectations(timeout: 0.1) } + + func testAsyncFetchPropagatesError() async { + gatewaySpy.fetchPersonsResultToBeReturned = .failure(.coreDataFetchFailed) + + do { + _ = try await sut.fetchPersons() + XCTFail("Expected fetch failure") + } catch { + XCTAssertEqual(error as? CoreError, .coreDataFetchFailed) + } + } }