From 214420f98aa21480a11bfa66af7a434627f9865f Mon Sep 17 00:00:00 2001 From: Laurent Date: Fri, 8 Nov 2024 16:24:01 +0100 Subject: [PATCH 1/3] add logs --- LeStorage/ApiCallCollection.swift | 20 ++++++++++++++++---- LeStorage/Services.swift | 2 +- LeStorage/StoredCollection.swift | 5 ++--- 3 files changed, 19 insertions(+), 8 deletions(-) diff --git a/LeStorage/ApiCallCollection.swift b/LeStorage/ApiCallCollection.swift index e9b74fb..8e8fe59 100644 --- a/LeStorage/ApiCallCollection.swift +++ b/LeStorage/ApiCallCollection.swift @@ -19,6 +19,10 @@ protocol SomeCallCollection { } +enum ApiCallError: Error { + case cantCreateCall +} + /// ApiCallCollection is an object communicating with a server to synchronize data managed locally /// The Api calls are serialized and stored in a JSON file /// Failing Api calls are stored forever and will be executed again later @@ -227,12 +231,17 @@ actor ApiCallCollection: SomeCallCollection { /// Creates an API call for the Storable [instance] and an HTTP [method] fileprivate func _createCall(_ instance: T, method: HTTPMethod) throws -> ApiCall { - let jsonString = try instance.jsonString() - return ApiCall(method: method, dataId: instance.stringId, body: jsonString) + do { + let jsonString = try instance.jsonString() + return ApiCall(method: method, dataId: instance.stringId, body: jsonString) + } catch { + StoreCenter.main.log(message: "call could not be created for \(T.resourceName()): \(error.localizedDescription)") + throw ApiCallError.cantCreateCall + } } /// Prepares a call for execution by updating its properties and adding it to its collection for storage - fileprivate func _prepareCall(apiCall: ApiCall) throws { + fileprivate func _prepareCall(apiCall: ApiCall) { apiCall.lastAttemptDate = Date() apiCall.attemptsCount += 1 self.addOrUpdate(apiCall) @@ -244,6 +253,7 @@ actor ApiCallCollection: SomeCallCollection { return try await self._synchronize(instance, method: HTTPMethod.post) } catch { self.rescheduleApiCallsIfNecessary() + StoreCenter.main.log(message: "POST failed for \(instance)") Logger.error(error) } return nil @@ -256,6 +266,7 @@ actor ApiCallCollection: SomeCallCollection { return try await self._synchronize(instance, method: HTTPMethod.put) } catch { self.rescheduleApiCallsIfNecessary() + StoreCenter.main.log(message: "PUT failed for \(instance)") Logger.error(error) } return nil @@ -267,6 +278,7 @@ actor ApiCallCollection: SomeCallCollection { let _: Empty? = try await self._synchronize(instance, method: HTTPMethod.delete) } catch { self.rescheduleApiCallsIfNecessary() + StoreCenter.main.log(message: "DELETE failed for \(instance)") Logger.error(error) } return @@ -275,7 +287,7 @@ actor ApiCallCollection: SomeCallCollection { /// Initiates the process of sending the data with the server fileprivate func _synchronize(_ instance: T, method: HTTPMethod) async throws -> V? { if let apiCall = try self._callForInstance(instance, method: method) { - try self._prepareCall(apiCall: apiCall) + self._prepareCall(apiCall: apiCall) return try await self._executeApiCall(apiCall) } else { return nil diff --git a/LeStorage/Services.swift b/LeStorage/Services.swift index 4fd0be2..d6df69c 100644 --- a/LeStorage/Services.swift +++ b/LeStorage/Services.swift @@ -121,7 +121,7 @@ public class Services { print("\(debugURL) ended, status code = \(statusCode)") switch statusCode { case 200..<300: // success - try await StoreCenter.main.deleteApiCallById(type: T.self, id: apiCall.id) + try await StoreCenter.main.deleteApiCallById(type: T.self, id: apiCall.id) default: // error Logger.log("Failed Run \(request.httpMethod ?? "") \(request.url?.absoluteString ?? "")") let errorString: String = String(data: task.0, encoding: .utf8) ?? "" diff --git a/LeStorage/StoredCollection.swift b/LeStorage/StoredCollection.swift index 982bab5..849b493 100644 --- a/LeStorage/StoredCollection.swift +++ b/LeStorage/StoredCollection.swift @@ -399,14 +399,13 @@ public class StoredCollection: RandomAccessCollection, SomeCollecti /// Writes all the items as a json array inside a file fileprivate func _write() { -// Logger.log("Start write to \(T.fileName())...") do { let jsonString: String = try self.items.jsonString() try self._store.write(content: jsonString, fileName: T.fileName()) } catch { - Logger.error(error) // TODO how to notify the main project + Logger.error(error) + StoreCenter.main.log(message: "write failed for \(T.resourceName()): \(error.localizedDescription)") } -// Logger.log("End write") } /// Simply clears the items of the collection From 1ef5725029cdcc64c43b99b8c8abac4380f93dea Mon Sep 17 00:00:00 2001 From: Laurent Date: Tue, 12 Nov 2024 10:12:54 +0100 Subject: [PATCH 2/3] Improve logging --- LeStorage/ApiCallCollection.swift | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/LeStorage/ApiCallCollection.swift b/LeStorage/ApiCallCollection.swift index 8e8fe59..6d7adf8 100644 --- a/LeStorage/ApiCallCollection.swift +++ b/LeStorage/ApiCallCollection.swift @@ -253,7 +253,7 @@ actor ApiCallCollection: SomeCallCollection { return try await self._synchronize(instance, method: HTTPMethod.post) } catch { self.rescheduleApiCallsIfNecessary() - StoreCenter.main.log(message: "POST failed for \(instance)") + StoreCenter.main.log(message: "POST failed for \(instance): \(error.localizedDescription)") Logger.error(error) } return nil @@ -266,7 +266,7 @@ actor ApiCallCollection: SomeCallCollection { return try await self._synchronize(instance, method: HTTPMethod.put) } catch { self.rescheduleApiCallsIfNecessary() - StoreCenter.main.log(message: "PUT failed for \(instance)") + StoreCenter.main.log(message: "PUT failed for \(instance): \(error.localizedDescription)") Logger.error(error) } return nil @@ -278,7 +278,7 @@ actor ApiCallCollection: SomeCallCollection { let _: Empty? = try await self._synchronize(instance, method: HTTPMethod.delete) } catch { self.rescheduleApiCallsIfNecessary() - StoreCenter.main.log(message: "DELETE failed for \(instance)") + StoreCenter.main.log(message: "DELETE failed for \(instance): \(error.localizedDescription)") Logger.error(error) } return @@ -294,12 +294,6 @@ actor ApiCallCollection: SomeCallCollection { } } - /// Executes an API call - /// For POST requests, potentially copies additional data coming from the server during the insert -// fileprivate func _executeApiCall(_ apiCall: ApiCall) async throws -> T { -// return try await StoreCenter.main.execute(apiCall: apiCall) -// } - /// Executes an API call /// For POST requests, potentially copies additional data coming from the server during the insert fileprivate func _executeApiCall(_ apiCall: ApiCall) async throws -> V { From b9a5e7c48216a5db2eddc22813ff1acc3e7ea22a Mon Sep 17 00:00:00 2001 From: Laurent Date: Mon, 18 Nov 2024 10:13:16 +0100 Subject: [PATCH 3/3] Added error message to Error enums --- LeStorage/ApiCallCollection.swift | 13 ++++++++++--- LeStorage/Services.swift | 2 +- LeStorage/Store.swift | 18 +++++++++++++++--- LeStorage/StoredCollection.swift | 6 ------ LeStorage/Utils/Errors.swift | 25 ++++++++++++++++++++++--- LeStorage/Utils/FileUtils.swift | 9 ++++++++- LeStorage/Utils/KeychainStore.swift | 11 +++++++++++ 7 files changed, 67 insertions(+), 17 deletions(-) diff --git a/LeStorage/ApiCallCollection.swift b/LeStorage/ApiCallCollection.swift index 6d7adf8..b132a62 100644 --- a/LeStorage/ApiCallCollection.swift +++ b/LeStorage/ApiCallCollection.swift @@ -19,8 +19,15 @@ protocol SomeCallCollection { } -enum ApiCallError: Error { - case cantCreateCall +enum ApiCallError: Error, LocalizedError { + case encodingError(id: String, type: String) + + var errorDescription: String? { + switch self { + case .encodingError(let id, let type): + return "Can't encode instance \(type) with id: \(id)" + } + } } /// ApiCallCollection is an object communicating with a server to synchronize data managed locally @@ -236,7 +243,7 @@ actor ApiCallCollection: SomeCallCollection { return ApiCall(method: method, dataId: instance.stringId, body: jsonString) } catch { StoreCenter.main.log(message: "call could not be created for \(T.resourceName()): \(error.localizedDescription)") - throw ApiCallError.cantCreateCall + throw ApiCallError.encodingError(id: instance.stringId, type: T.resourceName()) } } diff --git a/LeStorage/Services.swift b/LeStorage/Services.swift index d6df69c..a52e38a 100644 --- a/LeStorage/Services.swift +++ b/LeStorage/Services.swift @@ -436,7 +436,7 @@ public class Services { /// - password: the account's password public func deleteAccount() async throws { guard let userId = StoreCenter.main.userId else { - throw ServiceError.missingUserId + throw StoreError.missingUserId } let path = "users/\(userId)/" let deleteAccount = ServiceCall(path: path, method: .delete, requiresToken: true) diff --git a/LeStorage/Store.swift b/LeStorage/Store.swift index 2bbca5b..f023778 100644 --- a/LeStorage/Store.swift +++ b/LeStorage/Store.swift @@ -8,13 +8,25 @@ import Foundation import UIKit -public enum StoreError: Error { +public enum StoreError: Error, LocalizedError { case missingService case missingUserId - case unexpectedCollectionType(name: String) - case apiCallCollectionNotRegistered(type: String) case collectionNotRegistered(type: String) case cannotSyncCollection(name: String) + + public var errorDescription: String? { + switch self { + case .missingService: + return "Services instance is nil" + case .missingUserId: + return "The user id is missing" + case .collectionNotRegistered(let type): + return "The collection \(type) is not registered" + case .cannotSyncCollection(let name): + return "Tries to load the collection \(name) from the server while it's not authorized" + } + } + } public struct StoreIdentifier { diff --git a/LeStorage/StoredCollection.swift b/LeStorage/StoredCollection.swift index 849b493..4c6165d 100644 --- a/LeStorage/StoredCollection.swift +++ b/LeStorage/StoredCollection.swift @@ -7,12 +7,6 @@ import Foundation -enum StoredCollectionError: Error { - case unmanagedHTTPMethod(method: String) - case missingApiCallCollection - case missingInstance -} - protocol CollectionHolder { associatedtype Item diff --git a/LeStorage/Utils/Errors.swift b/LeStorage/Utils/Errors.swift index 2eb0d9f..492fd86 100644 --- a/LeStorage/Utils/Errors.swift +++ b/LeStorage/Utils/Errors.swift @@ -20,14 +20,33 @@ public class ErrorUtils { } -public enum ServiceError: Error { +public enum ServiceError: Error, LocalizedError { case urlCreationError(url: String) case cantConvertToUUID(id: String) case missingUserName - case missingUserId case responseError(response: String) + + public var errorDescription: String? { + switch self { + case .urlCreationError(let url): + return "Can't create URL from \(url)" + case .cantConvertToUUID(let id): + return "Cant convert \(id) to UUID" + case .missingUserName: + return "There is no userName defined in the Settings" + case .responseError(let response): + return "The server returned an error: \(response)" + } + } } -public enum UUIDError: Error { +public enum UUIDError: Error, LocalizedError { case cantConvertString(string: String) + + public var errorDescription: String? { + switch self { + case .cantConvertString(let string): + return "cant convert string to UUID: \(string)" + } + } } diff --git a/LeStorage/Utils/FileUtils.swift b/LeStorage/Utils/FileUtils.swift index 5edf902..c906ab4 100644 --- a/LeStorage/Utils/FileUtils.swift +++ b/LeStorage/Utils/FileUtils.swift @@ -7,8 +7,15 @@ import Foundation -enum FileError : Error { +enum FileError: Error, LocalizedError { case documentDirectoryNotFound + + var errorDescription: String? { + switch self { + case .documentDirectoryNotFound: + return "The document directory has not been found" + } + } } class FileUtils { diff --git a/LeStorage/Utils/KeychainStore.swift b/LeStorage/Utils/KeychainStore.swift index 0f43c57..7e650b6 100644 --- a/LeStorage/Utils/KeychainStore.swift +++ b/LeStorage/Utils/KeychainStore.swift @@ -11,6 +11,17 @@ enum KeychainError: Error { case keychainItemNotFound(serverId: String) case unexpectedPasswordData case unhandledError(status: OSStatus) + + var errorDescription: String? { + switch self { + case .keychainItemNotFound(let serverId): + return "The keychainItem was not found: \(serverId)" + case .unexpectedPasswordData: + return "Keychain error: The data could not be converted to string" + case .unhandledError(let status): + return "Keychain error: Unmanaged status: \(status)" + } + } } class KeychainStore {