failed api call first draft

multistore
Laurent 1 year ago
parent 10455f8715
commit eaada945fb
  1. 4
      LeStorage.xcodeproj/project.pbxproj
  2. 2
      LeStorage/Codables/ApiCall.swift
  3. 39
      LeStorage/Codables/FailedAPICall.swift
  4. 22
      LeStorage/Services.swift
  5. 35
      LeStorage/Store.swift
  6. 8
      LeStorage/StoredCollection.swift

@ -13,6 +13,7 @@
C425D4452B6D24E1002A7B48 /* LeStorage.h in Headers */ = {isa = PBXBuildFile; fileRef = C425D4372B6D24E1002A7B48 /* LeStorage.h */; settings = {ATTRIBUTES = (Public, ); }; };
C425D4582B6D2519002A7B48 /* Store.swift in Sources */ = {isa = PBXBuildFile; fileRef = C425D4572B6D2519002A7B48 /* Store.swift */; };
C456EFE22BE52379007388E2 /* StoredSingleton.swift in Sources */ = {isa = PBXBuildFile; fileRef = C456EFE12BE52379007388E2 /* StoredSingleton.swift */; };
C45D35912C0A1DB5000F379F /* FailedAPICall.swift in Sources */ = {isa = PBXBuildFile; fileRef = C45D35902C0A1DB5000F379F /* FailedAPICall.swift */; };
C49EF0242BD6BDC50077B5AA /* FileManager+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = C49EF0232BD6BDC50077B5AA /* FileManager+Extensions.swift */; };
C4A47D4F2B6D280200ADC637 /* StoredCollection.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4A47D4E2B6D280200ADC637 /* StoredCollection.swift */; };
C4A47D512B6D2C4E00ADC637 /* Codable+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4A47D502B6D2C4E00ADC637 /* Codable+Extensions.swift */; };
@ -49,6 +50,7 @@
C425D4432B6D24E1002A7B48 /* LeStorageTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LeStorageTests.swift; sourceTree = "<group>"; };
C425D4572B6D2519002A7B48 /* Store.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Store.swift; sourceTree = "<group>"; };
C456EFE12BE52379007388E2 /* StoredSingleton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StoredSingleton.swift; sourceTree = "<group>"; };
C45D35902C0A1DB5000F379F /* FailedAPICall.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FailedAPICall.swift; sourceTree = "<group>"; };
C49EF0232BD6BDC50077B5AA /* FileManager+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "FileManager+Extensions.swift"; sourceTree = "<group>"; };
C4A47D4E2B6D280200ADC637 /* StoredCollection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StoredCollection.swift; sourceTree = "<group>"; };
C4A47D502B6D2C4E00ADC637 /* Codable+Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Codable+Extensions.swift"; sourceTree = "<group>"; };
@ -159,6 +161,7 @@
children = (
C4A47D9A2B7CFFC500ADC637 /* Settings.swift */,
C4A47D992B7CFFC500ADC637 /* ApiCall.swift */,
C45D35902C0A1DB5000F379F /* FailedAPICall.swift */,
);
path = Codables;
sourceTree = "<group>";
@ -290,6 +293,7 @@
C4A47D942B7CF7C500ADC637 /* MicroStorage.swift in Sources */,
C49EF0242BD6BDC50077B5AA /* FileManager+Extensions.swift in Sources */,
C425D4582B6D2519002A7B48 /* Store.swift in Sources */,
C45D35912C0A1DB5000F379F /* FailedAPICall.swift in Sources */,
C4A47D6B2B71244100ADC637 /* Collection+Extension.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;

@ -8,7 +8,9 @@
import Foundation
protocol SomeCall: Storable {
var id: String { get }
var lastAttemptDate: Date { get }
var attemptsCount: Int { get }
}
class ApiCall<T: Storable>: ModelObject, Storable, SomeCall {

@ -0,0 +1,39 @@
//
// FailedAPICall.swift
// LeStorage
//
// Created by Laurent Morvillier on 31/05/2024.
//
import Foundation
class FailedAPICall: ModelObject, Storable {
static func resourceName() -> String { return "failed_api_calls" }
static func tokenExemptedMethods() -> [HTTPMethod] { return HTTPMethod.allCases }
var id: String = Store.randomId()
/// The creation date of the call
var date: Date = Date()
/// The id of the API call
var callId: String
/// The type of the call
var type: String
/// The JSON representation of the API call
var apiCall: String
/// The server error
var error: String
init(callId: String, type: String, apiCall: String, error: String) {
self.callId = callId
self.type = type
self.apiCall = apiCall
self.error = error
}
}

@ -50,7 +50,6 @@ fileprivate enum ServiceConf: String {
}
}
}
public class Services {
@ -87,12 +86,6 @@ public class Services {
return try await _runRequest(request, apiCallId: apiCallId)
}
// fileprivate func _runRequest<T: Encodable, U: Decodable>(servicePath: String, method: HTTPMethod, payload: T, apiCallId: String? = nil) async throws -> U {
// var request = try self._baseRequest(servicePath: servicePath, method: method)
// request.httpBody = try jsonEncoder.encode(payload)
// return try await _runRequest(request, apiCallId: apiCallId)
// }
fileprivate func _runRequest<T: Decodable>(_ request: URLRequest, apiCallId: String? = nil) async throws -> T {
Logger.log("Run \(request.httpMethod ?? "") \(request.url?.absoluteString ?? "")")
let task: (Data, URLResponse) = try await URLSession.shared.data(for: request)
@ -110,17 +103,20 @@ public class Services {
}
}
default:
if let apiCallId, let type = (T.self as? any Storable.Type) {
try Store.main.rescheduleApiCall(id: apiCallId, type: type)
}
Logger.log("Failed Run \(request.httpMethod ?? "") \(request.url?.absoluteString ?? "")")
var dataString = String(describing: String(data: task.0, encoding: .utf8))
var errorString = String(describing: String(data: task.0, encoding: .utf8))
if let nfe: NonFieldError = try? JSONDecoder().decode(NonFieldError.self, from: task.0) {
if let reason = nfe.non_field_errors.first {
dataString = reason
errorString = reason
}
}
throw ServiceError.responseError(response: dataString)
if let apiCallId, let type = (T.self as? any Storable.Type) {
try Store.main.rescheduleApiCall(id: apiCallId, type: type)
Store.main.logFailedAPICall(apiCallId, collectionName: type.resourceName(), error: errorString)
}
throw ServiceError.responseError(response: errorString)
}
}
return try jsonDecoder.decode(T.self, from: task.0)

@ -68,8 +68,11 @@ public class Store {
}
}
fileprivate var _failedAPICallsCollection: StoredCollection<FailedAPICall>? = nil
public init() {
FileManager.default.createDirectoryInDocuments(directoryName: Store.storageDirectory)
// self._failedAPICallsCollection = registerCollection(synchronized: true)
}
/// Registers a collection
@ -106,15 +109,6 @@ public class Store {
return self._settingsStorage.item.userId
}
/// Returns the user's UUID
// public var currentUserUUID: UUID? {
// if let uuidString = self._settingsStorage.item.userId,
// let uuid = UUID(uuidString: uuidString) {
// return uuid
// }
// return nil
// }
/// Returns the username
public func userName() -> String? {
return self._settingsStorage.item.username
@ -271,4 +265,27 @@ public class Store {
return self._collections[resourceName]?.contentOfApiCallFile() ?? ""
}
public func logsFailedAPICalls() {
self._failedAPICallsCollection = self.registerCollection(synchronized: true)
}
func logFailedAPICall(_ apiCallId: String, collectionName: String, error: String) {
guard let failedAPICallsCollection = self._failedAPICallsCollection, let collection = self._collections[collectionName], let apiCall = try? collection.apiCallById(apiCallId) else {
return
}
if !failedAPICallsCollection.contains(where: { $0.callId == apiCallId }) && apiCall.attemptsCount > 5 {
do {
let string = try apiCall.jsonString()
let failedAPICall = FailedAPICall(callId: apiCall.id, type: collectionName, apiCall: string, error: error)
try failedAPICallsCollection.addOrUpdate(instance: failedAPICall)
} catch {
Logger.error(error)
}
}
}
}

@ -28,6 +28,7 @@ protocol SomeCollection: Identifiable {
func reset()
func resetApiCalls()
func apiCallById(_ id: String) throws -> (any SomeCall)?
}
extension Notification.Name {
@ -573,6 +574,13 @@ public class StoredCollection<T: Storable>: RandomAccessCollection, SomeCollecti
try apiCallsCollection.deleteById(id)
}
func apiCallById(_ id: String) throws -> (any SomeCall)? {
guard let apiCallsCollection else {
throw StoreError.apiCallCollectionNotRegistered(type: T.resourceName())
}
return apiCallsCollection.findById(id)
}
/// Returns if the API call collection is not empty
func hasPendingAPICalls() -> Bool {
guard let apiCallsCollection else { return false }

Loading…
Cancel
Save