Skip to content

Commit 98eb017

Browse files
committed
refactor: restructure Authenticating as struct with nested types
- Convert Authenticating from enum namespace to generic struct - Nest Client, API, and Router types inside Authenticating for cleaner API - Reuse generic parameters from parent struct in nested types - Add convenience typealiases for backwards compatibility - Enable usage pattern: @dependency(Service.self) with service.client and service.router - Improve type inference and reduce generic parameter duplication
1 parent b83ca05 commit 98eb017

File tree

6 files changed

+457
-263
lines changed

6 files changed

+457
-263
lines changed

Package.swift

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,14 @@ let package = Package(
8383
.authenticatingEmailAddress
8484
]
8585
),
86+
.testTarget(
87+
name: .authenticating.tests,
88+
dependencies: [
89+
.authenticating,
90+
.dependencies,
91+
.product(name: "DependenciesMacros", package: "swift-dependencies")
92+
]
93+
),
8694
],
8795
swiftLanguageModes: [.v6]
8896
)

Sources/Authenticating/Authenticating API.swift

Lines changed: 16 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -43,21 +43,19 @@ extension Authenticating {
4343
/// api: MyAPI.getUser(id: "123")
4444
/// )
4545
/// ```
46-
public struct API<
47-
OtherAPI: Equatable & Sendable
48-
>: Equatable & Sendable {
46+
public struct API: Equatable & Sendable {
4947
/// The authentication credentials.
5048
public let auth: Auth
5149

5250
/// The API route or request.
53-
public let api: OtherAPI
51+
public let api: API
5452

5553
/// Creates a new authenticated API instance.
5654
///
5755
/// - Parameters:
5856
/// - auth: The authentication credentials to use.
5957
/// - api: The API route or request to authenticate.
60-
public init(auth: Auth, api: OtherAPI) {
58+
public init(auth: Auth, api: API) {
6159
self.auth = auth
6260
self.api = api
6361
}
@@ -73,13 +71,13 @@ extension Authenticating.API where Auth == BearerAuth {
7371
/// - apiKey: The API key to use as the Bearer token.
7472
/// - api: The API route or request to authenticate.
7573
/// - Throws: An error if the API key is invalid.
76-
public init(apiKey: String, api: OtherAPI) throws {
74+
public init(apiKey: String, api: API) throws {
7775
self.auth = try .init(token: apiKey)
7876
self.api = api
7977
}
8078
}
8179

82-
extension Authenticating.API {
80+
extension Authenticating {
8381
/// A router that combines authentication routing with API routing.
8482
///
8583
/// ``Router`` handles the parsing and printing of authenticated API requests,
@@ -107,16 +105,7 @@ extension Authenticating.API {
107105
/// )
108106
/// let request = try router.request(for: api)
109107
/// ```
110-
public struct Router<
111-
AuthRouter: ParserPrinter & Sendable,
112-
OtherAPIRouter: ParserPrinter & Sendable
113-
>: ParserPrinter, Sendable
114-
where
115-
OtherAPIRouter.Input == URLRequestData,
116-
OtherAPIRouter.Output == OtherAPI,
117-
AuthRouter.Input == URLRequestData,
118-
AuthRouter.Output == Auth
119-
{
108+
public struct Router: ParserPrinter, Sendable {
120109

121110
/// The base URL for all API requests.
122111
let baseURL: URL
@@ -125,7 +114,7 @@ extension Authenticating.API {
125114
let authRouter: AuthRouter
126115

127116
/// The router responsible for handling API routes.
128-
let router: OtherAPIRouter
117+
let router: APIRouter
129118

130119
/// Creates a new authenticated API router.
131120
///
@@ -136,22 +125,22 @@ extension Authenticating.API {
136125
public init(
137126
baseURL: URL,
138127
authRouter: AuthRouter,
139-
router: OtherAPIRouter
128+
router: APIRouter
140129
) {
141130
self.baseURL = baseURL
142131
self.authRouter = authRouter
143132
self.router = router
144133
}
145134

146-
/// The router body that combines authentication and API routing.
147-
public var body: some URLRouting.Router<Authenticating<Auth>.API<OtherAPI>> {
148-
Parse(.memberwise(Authenticating<Auth>.API<OtherAPI>.init)) {
149-
authRouter
150-
151-
router
152-
}
153-
.baseURL(self.baseURL.absoluteString)
135+
/// The router body that combines authentication and API routing.
136+
public var body: some URLRouting.Router<Authenticating.API> {
137+
Parse(.memberwise(Authenticating.API.init)) {
138+
authRouter
139+
140+
router
154141
}
142+
.baseURL(self.baseURL.absoluteString)
155143
}
156144
}
145+
}
157146

Sources/Authenticating/Authenticating Client.swift

Lines changed: 41 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -54,25 +54,14 @@ extension Authenticating {
5454
/// The client uses `@dynamicMemberLookup` to provide direct access to the underlying
5555
/// client's methods and properties, making authenticated requests feel natural.
5656
@dynamicMemberLookup
57-
public struct Client<
58-
AuthRouter: ParserPrinter & Sendable,
59-
API: Equatable & Sendable,
60-
APIRouter: ParserPrinter & Sendable,
61-
ClientOutput: Sendable
62-
>: Sendable
63-
where
64-
APIRouter.Input == URLRequestData,
65-
APIRouter.Output == API,
66-
AuthRouter.Input == URLRequestData,
67-
AuthRouter.Output == Auth
68-
{
57+
public struct Client: Sendable {
6958

7059
private let baseURL: URL
7160
private let auth: Auth
7261

7362
private let router: APIRouter
7463
private let buildClient: @Sendable (@escaping @Sendable (API) throws -> URLRequest) -> ClientOutput
75-
private let authenticatedRouter: Authenticating<Auth>.API<API>.Router<AuthRouter, APIRouter>
64+
private let authenticatedRouter: Router
7665

7766
/// Creates a new authenticated client.
7867
///
@@ -93,7 +82,7 @@ extension Authenticating {
9382
self.auth = auth
9483
self.router = router
9584
self.buildClient = buildClient
96-
self.authenticatedRouter = Authenticating.API.Router(
85+
self.authenticatedRouter = Router(
9786
baseURL: baseURL,
9887
authRouter: authRouter,
9988
router: router
@@ -102,15 +91,15 @@ extension Authenticating {
10291

10392
/// Provides dynamic access to the underlying client's properties and methods.
10493
///
105-
/// This subscript automatically wraps all API calls with authentication,
106-
/// ensuring that every request includes the necessary authentication headers.
94+
/// This allows you to access closure properties directly on the authenticated client:
10795
///
108-
/// - Parameter keyPath: The key path to a property or method on the underlying client.
109-
/// - Returns: The value at the specified key path, with authentication automatically applied.
96+
/// ```swift
97+
/// let response = try await authenticatedClient.send(.getUser(id: "123"))
98+
/// ```
11099
public subscript<T>(dynamicMember keyPath: KeyPath<ClientOutput, T>) -> T {
111100
@Sendable
112101
func makeRequest(for api: API) throws -> URLRequest {
113-
let data = try authenticatedRouter.print(.init(auth: auth, api: api))
102+
let data = try authenticatedRouter.print(Authenticating.API(auth: auth, api: api))
114103

115104
guard let request = URLRequest(data: data) else {
116105
throw Error.requestError
@@ -127,6 +116,37 @@ extension Authenticating {
127116
}[keyPath: keyPath]
128117
}
129118
}
119+
120+
/// Provides access to the underlying client with all authentication automatically applied.
121+
///
122+
/// This computed property returns the client instance with authentication headers
123+
/// automatically injected into every request. This allows you to call methods
124+
/// with proper parameter labels:
125+
///
126+
/// ```swift
127+
/// // With proper method syntax:
128+
/// let response = try await authenticatedClient.client.send(request)
129+
/// ```
130+
public var client: ClientOutput {
131+
@Sendable
132+
func makeRequest(for api: API) throws -> URLRequest {
133+
let data = try authenticatedRouter.print(Authenticating.API(auth: auth, api: api))
134+
135+
guard let request = URLRequest(data: data) else {
136+
throw Error.requestError
137+
}
138+
139+
return request
140+
}
141+
142+
return withEscapedDependencies { dependencies in
143+
buildClient { api in
144+
try dependencies.yield {
145+
try makeRequest(for: api)
146+
}
147+
}
148+
}
149+
}
130150
}
131151
}
132152

@@ -143,7 +163,7 @@ public enum Error: Swift.Error {
143163

144164
// MARK: - Bearer Authentication Conveniences
145165

146-
extension Authenticating.Client {
166+
extension AuthenticatingClient where Auth == BearerAuth {
147167
/// Creates a new client with Bearer token authentication.
148168
///
149169
/// This convenience initializer is available when using Bearer authentication
@@ -199,7 +219,7 @@ extension Authenticating.Client where APIRouter: TestDependencyKey, APIRouter.Va
199219

200220
// MARK: - Basic Authentication Conveniences
201221

202-
extension Authenticating.Client {
222+
extension AuthenticatingClient where Auth == BasicAuth {
203223
/// Creates a new client with Basic authentication.
204224
///
205225
/// This convenience initializer is available when using Basic authentication

0 commit comments

Comments
 (0)