Skip to content

Commit

Permalink
Add tests, unfair lock utility class, and type erasure in the callErr…
Browse files Browse the repository at this point in the history
…or class.
  • Loading branch information
Jim Boulter committed Dec 6, 2024
1 parent cd33024 commit 2d47e47
Show file tree
Hide file tree
Showing 8 changed files with 407 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1610"
version = "1.7">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES"
buildArchitectures = "Automatic">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "SwiftyDropbox"
BuildableName = "SwiftyDropbox"
BlueprintName = "SwiftyDropbox"
ReferencedContainer = "container:">
</BuildableReference>
</BuildActionEntry>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "SwiftyDropboxObjC"
BuildableName = "SwiftyDropboxObjC"
BlueprintName = "SwiftyDropboxObjC"
ReferencedContainer = "container:">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES"
shouldAutocreateTestPlan = "YES">
<Testables>
<TestableReference
skipped = "NO">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "SwiftyDropboxUnitTests"
BuildableName = "SwiftyDropboxUnitTests"
BlueprintName = "SwiftyDropboxUnitTests"
ReferencedContainer = "container:">
</BuildableReference>
</TestableReference>
</Testables>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES">
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "SwiftyDropbox"
BuildableName = "SwiftyDropbox"
BlueprintName = "SwiftyDropbox"
ReferencedContainer = "container:">
</BuildableReference>
</MacroExpansion>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>
67 changes: 67 additions & 0 deletions .swiftpm/xcode/xcshareddata/xcschemes/SwiftyDropbox.xcscheme
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1610"
version = "1.7">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES"
buildArchitectures = "Automatic">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "SwiftyDropbox"
BuildableName = "SwiftyDropbox"
BlueprintName = "SwiftyDropbox"
ReferencedContainer = "container:">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES"
shouldAutocreateTestPlan = "YES">
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES">
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "SwiftyDropbox"
BuildableName = "SwiftyDropbox"
BlueprintName = "SwiftyDropbox"
ReferencedContainer = "container:">
</BuildableReference>
</MacroExpansion>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>
67 changes: 67 additions & 0 deletions .swiftpm/xcode/xcshareddata/xcschemes/SwiftyDropboxObjC.xcscheme
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1610"
version = "1.7">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES"
buildArchitectures = "Automatic">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "SwiftyDropboxObjC"
BuildableName = "SwiftyDropboxObjC"
BlueprintName = "SwiftyDropboxObjC"
ReferencedContainer = "container:">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES"
shouldAutocreateTestPlan = "YES">
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES">
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "SwiftyDropboxObjC"
BuildableName = "SwiftyDropboxObjC"
BlueprintName = "SwiftyDropboxObjC"
ReferencedContainer = "container:">
</BuildableReference>
</MacroExpansion>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>
26 changes: 26 additions & 0 deletions Source/SwiftyDropbox/Shared/Handwritten/Errors.swift
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,32 @@ public enum CallError<EType>: Error, CustomStringConvertible {
return "\(err)"
}
}

// Used for global error handlers which cannot know the type of the boxed route error
public var typeErased: CallError<Any> {
switch self {
case .internalServerError(let code, let msg, let requestId):
return .internalServerError(code, msg, requestId)
case .badInputError(let msg, let requestId):
return .badInputError(msg, requestId)
case .rateLimitError(let error, let locMsg, let msg, let requestId):
return .rateLimitError(error, locMsg, msg, requestId)
case .httpError(let code, let msg, let requestId):
return .httpError(code, msg, requestId)
case .authError(let error, let locMsg, let msg, let requestId):
return .authError(error, locMsg, msg, requestId)
case .accessError(let error, let locMsg, let msg, let requestId):
return .accessError(error, locMsg, msg, requestId)
case .routeError(let boxedError, let locMsg, let msg, let requestId):
return .routeError(Box(boxedError.unboxed as Any), locMsg, msg, requestId)
case .serializationError(let error):
return .serializationError(error)
case .reconnectionError(let error):
return .reconnectionError(error)
case .clientError(let error):
return .clientError(error)
}
}

static func error<ESerial: JSONSerializer>(response: HTTPURLResponse, data: Data?, errorSerializer: ESerial) throws -> CallError<ESerial.ValueType> {
let requestId = requestId(from: response)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import Foundation

/// Similar to `DBGlobalErrorResponseHandler` in the Objc SDK
/// It does not have special handling for route errors, which end up type-erased right now
/// It also does not allow you to easily retry requests yet, like Objc's does.
public class GlobalErrorResponseHandler {
static var shared = { GlobalErrorResponseHandler() }()

private struct Handler {
let callback: (CallError<Any>) -> Void
let queue: OperationQueue
}

// Locked state
private struct State {
var handlers: [Handler] = []
}

private var state = UnfairLock<State>(value: State())

internal init() { }

func reportGlobalError(_ error: CallError<Any>) {
state.read { lockedState in
lockedState.handlers.forEach { handler in
handler.queue.addOperation {
handler.callback(error)
}
}
}
}

func registerGlobalErrorHandler(_ callback: @escaping (CallError<Any>) -> Void, queue: OperationQueue = .main) {
state.mutate { lockedState in
lockedState.handlers.append(Handler(callback: callback, queue: queue))
}
}
}
10 changes: 10 additions & 0 deletions Source/SwiftyDropbox/Shared/Handwritten/Request.swift
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,16 @@ public class Request<RSerial: JSONSerializer, ESerial: JSONSerializer> {
}

func handleResponseError(networkTaskFailure: NetworkTaskFailure) -> CallError<ESerial.ValueType> {
let callError = parseCallError(from: networkTaskFailure)

// We call the global error response handler to alert it to an error
// But unlike in the objc SDK we do not stop the SDK from calling the per-route completion handler as well.
GlobalErrorResponseHandler.shared.reportGlobalError(callError.typeErased)

return callError
}

private func parseCallError(from networkTaskFailure: NetworkTaskFailure) -> CallError<ESerial.ValueType> {
switch networkTaskFailure {
case .badStatusCode(let data, _, let response):
return CallError(response, data: data, errorSerializer: errorSerializer)
Expand Down
43 changes: 43 additions & 0 deletions Source/SwiftyDropbox/Shared/Handwritten/Utilities.swift
Original file line number Diff line number Diff line change
Expand Up @@ -124,3 +124,46 @@ public enum LogHelper {
log(backgroundSessionLogLevel, "bg session - \(message)")
}
}

// MARK: Locking
/// Wrapper around a value with a lock
///
/// Copied from https://github.com/home-assistant/HAKit/blob/main/Source/Internal/HAProtected.swift
public class UnfairLock<ValueType> {
private var value: ValueType
private let lock: os_unfair_lock_t = {
let value = os_unfair_lock_t.allocate(capacity: 1)
value.initialize(to: os_unfair_lock())
return value
}()

/// Create a new protected value
/// - Parameter value: The initial value
public init(value: ValueType) {
self.value = value
}

deinit {
lock.deinitialize(count: 1)
lock.deallocate()
}

/// Get and optionally change the value
/// - Parameter handler: Will be invoked immediately with the current value as an inout parameter.
/// - Returns: The value returned by the handler block
@discardableResult
public func mutate<HandlerType>(using handler: (inout ValueType) -> HandlerType) -> HandlerType {
os_unfair_lock_lock(lock)
defer { os_unfair_lock_unlock(lock) }
return handler(&value)
}

/// Read the value and get a result out of it
/// - Parameter handler: Will be invoked immediately with the current value.
/// - Returns: The value returned by the handler block
public func read<T>(_ handler: (ValueType) -> T) -> T {
os_unfair_lock_lock(lock)
defer { os_unfair_lock_unlock(lock) }
return handler(value)
}
}
Loading

0 comments on commit 2d47e47

Please sign in to comment.