Skip to content

Commit ac59d44

Browse files
committed
Add missing docs and improve exisiting ones,
Add test for network, data & download task.
1 parent 135fe72 commit ac59d44

39 files changed

+2042
-389
lines changed

Benchmarks/MyNewBenchmarkTarget/MyNewBenchmarkTarget.swift

Lines changed: 0 additions & 107 deletions
This file was deleted.

Sources/NetworkingClient/ConfigurationValues.swift

Lines changed: 3 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -35,22 +35,14 @@ extension ConfigurationValues {
3535
/// - Warning: If accessed before being explicitly set, this property will trigger a runtime
3636
/// precondition failure to help catch misconfiguration.
3737
@Config(forceUnwrapped: true) public internal(set) var tasks: any TasksStorage
38+
39+
/// The interceptors that will intercept the request & response.
40+
@Config internal var taskInterceptor: any Interceptor = TaskInterceptor()
3841
}
3942

40-
4143
extension Configurable {
4244
/// Enables or disables request/response logging.
4345
public func enableLogs(_ enabled: Bool = true) -> Self {
4446
return configuration(\.logsEnabled, enabled)
4547
}
46-
47-
/// Sets the handler used for managing response caching.
48-
public func cacheHandler(_ handler: some ResponseCacheHandler) -> Self {
49-
return configuration(\.cacheHandler, handler)
50-
}
51-
52-
/// Sets the handler for managing HTTP redirections.
53-
public func redirectionHandler(_ handler: some RedirectionHandler) -> Self {
54-
return configuration(\.redirectionHandler, handler)
55-
}
5648
}

Sources/NetworkingClient/Handlers/AuthInterceptor/AuthInterceptor.swift

Lines changed: 49 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
//
2-
// AuthenticationInterceptor.swift
2+
// AuthInterceptor.swift
33
// Networking
44
//
55
// Created by Joe Maghzal on 3/5/25.
@@ -8,14 +8,38 @@
88
import Foundation
99
import NetworkingCore
1010

11+
/// An interceptor that manages authentication using an ``AuthProvider``.
12+
///
13+
/// `AuthInterceptor` applies credentials to outgoing requests and
14+
/// refreshes them if the response indicates an authentication failure.
15+
///
16+
/// It acts as both a ``RequestInterceptor`` and a ``ResponseInterceptor``, enabling:
17+
/// - Credential injection before the request is sent
18+
/// - Automatic refresh when an unauthorized (`401`) response is received
19+
///
20+
/// Use this type via ``Configurable/authorization(_:)`` to automatically apply and refresh auth.
21+
///
22+
/// - Important: Only a single refresh task is executed at a time.
23+
/// Concurrent requests will wait for the same refresh to complete.
1124
public actor AuthInterceptor {
25+
/// The provider that handles authorization.
1226
private let provider: any AuthProvider
27+
28+
/// The current refresh task.
1329
private var task: Task<Void, any Error>?
1430

31+
/// Creates a new interceptor with the given authentication provider.
32+
///
33+
/// - Parameter provider: A type conforming to ``AuthProvider``.
1534
public init(provider: any AuthProvider) {
1635
self.provider = provider
1736
}
1837

38+
/// Refreshes the credentials if not already in progress.
39+
///
40+
/// If a refresh is already ongoing, subsequent callers await its result.
41+
///
42+
/// - Parameter session: The active session to use for refreshing.
1943
private func refresh(with session: Session) async throws {
2044
if let task {
2145
try await task.value
@@ -30,6 +54,12 @@ public actor AuthInterceptor {
3054

3155
// MARK: - RequestInterceptor
3256
extension AuthInterceptor: RequestInterceptor {
57+
/// Applies the authentication credential to the outgoing request.
58+
///
59+
/// If the provider indicates that a refresh is needed,
60+
/// this method refreshes the credentials before continuing.
61+
///
62+
/// - Returns: A modified request with authentication applied.
3363
public func intercept(
3464
_ task: some NetworkingTask,
3565
request: consuming URLRequest,
@@ -45,6 +75,11 @@ extension AuthInterceptor: RequestInterceptor {
4575

4676
// MARK: - ResponseInterceptor
4777
extension AuthInterceptor: ResponseInterceptor {
78+
/// Intercepts unauthorized responses to trigger a credential refresh.
79+
///
80+
/// This only runs if the response is `401 Unauthorized` and the retry count is zero.
81+
///
82+
/// - Returns: `.retry` if refresh succeeds; otherwise `.continue` or rethrows on failure.
4883
public func intercept(
4984
_ task: some NetworkingTask,
5085
for session: Session,
@@ -64,7 +99,19 @@ extension AuthInterceptor: ResponseInterceptor {
6499
}
65100

66101
extension Configurable {
67-
/// Sets the handler used to manage request authorization.
102+
/// Disables request authorization.
103+
///
104+
/// Call this to explicitly disable authentication for a request.
105+
public func unauthorized() -> Self {
106+
return configuration(\.authInterceptor, nil)
107+
}
108+
109+
/// Sets the authorization provider used to authorize requests.
110+
///
111+
/// The interceptor will apply credentials before sending and refresh them
112+
/// automatically if the response is unauthorized.
113+
///
114+
/// - Parameter provider: A type conforming to ``AuthProvider``.
68115
public func authorization(
69116
_ provider: some AuthProvider
70117
) -> Self {

Sources/NetworkingClient/Handlers/AuthInterceptor/AuthProvider.swift

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,55 @@
88
import Foundation
99
import NetworkingCore
1010

11+
/// A type that manages authentication and provides credentials for requests.
12+
///
13+
/// Use an ``AuthProvider`` to encapsulate how credentials are created, validated,
14+
/// and refreshed. You can inject your provider into a request pipeline or session
15+
/// to automatically apply authentication state.
16+
///
17+
/// The provider supplies a ``RequestModifier`` via ``AuthProvider/credential``,
18+
/// which is attached to requests requiring authentication. When expired,
19+
/// the system can invoke ``AuthProvider/refresh(with:)`` to update it.
20+
///
21+
/// - Important: You are responsible for detecting expiration using ``AuthProvider/requiresRefresh()``
22+
/// and for updating the internal credential inside `refresh(with:)`.
23+
///
24+
/// ```swift
25+
/// struct TokenAuth: AuthProvider {
26+
/// var token: String
27+
///
28+
/// var credential: some RequestModifier {
29+
/// HeaderRequestModifier(name: "Authorization", value: "Bearer \(token)")
30+
/// }
31+
///
32+
/// func requiresRefresh() -> Bool { /* check token expiration */ }
33+
///
34+
/// func refresh(with session: Session) async throws {
35+
/// // request new token
36+
/// }
37+
/// }
38+
/// ```
1139
public protocol AuthProvider: Sendable {
40+
/// The type of request modifier that carries authentication information.
1241
associatedtype Credential: RequestModifier
42+
43+
/// The current credential used to modify requests.
44+
///
45+
/// Typically this returns a request modifier that injects a token,
46+
/// header, or query parameter.
1347
var credential: Self.Credential {get}
1448

49+
/// Refreshes the credential by interacting with a backend or store.
50+
///
51+
/// This method is called when ``requiresRefresh()`` returns `true`.
52+
/// Update any internal state so that the next ``credential`` reflects the new data.
53+
///
54+
/// - Parameter session: A `Session` instance for making network requests.
1555
func refresh(with session: Session) async throws
56+
57+
/// Returns a Boolean value indicating whether the credential needs refreshing.
58+
///
59+
/// Use this method to determine if the current authentication state is valid.
60+
/// If it returns `true`, ``refresh(with:)`` will be called before sending requests.
1661
func requiresRefresh() -> Bool
1762
}

Sources/NetworkingClient/Handlers/RedirectionHandler.swift

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,45 @@
66
//
77

88
import Foundation
9+
import NetworkingCore
910

11+
/// A value that defines how an HTTP redirection should be handled.
12+
///
13+
/// Use ``RedirectionBehavior`` to decide whether to follow, ignore,
14+
/// or modify an HTTP redirect during request execution.
15+
///
16+
/// You return one of these cases from a
17+
/// ``RedirectionHandler/redirect(_:redirectResponse:newRequest:)``
18+
/// implementation to control how the redirection is processed.
1019
@frozen public enum RedirectionBehavior: Equatable, Hashable, Sendable {
20+
/// Follow the redirection as proposed.
1121
case redirect
22+
23+
/// Ignore the redirection and return the original response.
1224
case ignore
25+
26+
/// Provide a custom request to use instead of the proposed redirect.
27+
///
28+
/// Pass `nil` to explicitly cancel the redirect.
1329
case modified(URLRequest?)
1430
}
1531

32+
/// A type that handles HTTP redirection behavior.
33+
///
34+
/// Conform to ``RedirectionHandler`` to inspect or modify redirect responses
35+
/// during request execution. You can choose to follow the redirect,
36+
/// ignore it, or override the new request.
37+
///
38+
/// Attach a handler using ``Configurable/redirectionHandler(_:)``.
1639
public protocol RedirectionHandler: Sendable {
40+
/// Evaluates the redirect response and determines the next step.
41+
///
42+
/// - Parameters:
43+
/// - task: The networking task in progress.
44+
/// - redirectResponse: The response that triggered the redirect.
45+
/// - newRequest: The new request proposed by the system.
46+
///
47+
/// - Returns: A ``RedirectionBehavior`` that indicates how to proceed.
1748
func redirect(
1849
_ task: some NetworkingTask,
1950
redirectResponse: URLResponse,
@@ -22,12 +53,22 @@ public protocol RedirectionHandler: Sendable {
2253
}
2354

2455
extension RedirectionHandler where Self == DefaultRedirectionHandler {
56+
/// A redirection handler that disables all redirections.
57+
///
58+
/// This value causes the request to continue without following any redirects.
2559
public static var none: Self {
2660
return DefaultRedirectionHandler()
2761
}
2862
}
2963

64+
/// A redirection handler that accepts all redirects.
65+
///
66+
/// This type always returns ``RedirectionBehavior/redirect``, allowing
67+
/// the request to follow any proposed redirection without modification.
68+
///
69+
/// Use this as a default or baseline implementation.
3070
public struct DefaultRedirectionHandler: RedirectionHandler {
71+
/// Always follows the proposed redirection.
3172
public func redirect(
3273
_ task: some NetworkingTask,
3374
redirectResponse: URLResponse,
@@ -36,3 +77,17 @@ public struct DefaultRedirectionHandler: RedirectionHandler {
3677
return .redirect
3778
}
3879
}
80+
81+
extension Configurable {
82+
/// Sets the handler used to manage HTTP redirections.
83+
///
84+
/// Use this method to override the default redirection behavior for a request.
85+
/// You can ignore, modify, or explicitly cancel redirects based on response metadata.
86+
///
87+
/// - Parameter handler: A type conforming to ``RedirectionHandler``.
88+
///
89+
/// - Note: Use ``RedirectionHandler/none`` to disable redirects.
90+
public func redirectionHandler(_ handler: some RedirectionHandler) -> Self {
91+
return configuration(\.redirectionHandler, handler)
92+
}
93+
}

0 commit comments

Comments
 (0)