mirror of
https://github.com/Comfy-Org/comfy-swift-sdk.git
synced 2026-11-04 23:47:26 +00:00
[PR #44] [MERGED] refactor(auth): extract shared OAuth token-endpoint POST helper #46
Labels
No labels
enhancement
pull-request
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
Comfy-Org/comfy-swift-sdk#46
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
📋 Pull Request Information
Original PR: https://github.com/Comfy-Org/comfy-swift-sdk/pull/44
Author: @mattmillerai
Created: 7/11/2026
Status: ✅ Merged
Merged: 7/16/2026
Merged by: @mattmillerai
Base:
main← Head:matt/be-2861-oauth-token-endpoint📝 Commits (2)
e8c5e44refactor(auth): extract shared OAuth token-endpoint POST helper (BE-2861)0cf459bfix(auth): x-www-form-urlencoded token body; drop unused tokenType📊 Changes
4 files changed (+126 additions, -89 deletions)
View changed files
📝
Sources/ComfySwiftSDK/Internal/Auth/OAuthExchanger.swift(+7 -42)➕
Sources/ComfySwiftSDK/Internal/Auth/OAuthTokenEndpoint.swift(+86 -0)📝
Sources/ComfySwiftSDK/Internal/Auth/OAuthTokenRefreshExecutor.swift(+7 -47)📝
Tests/ComfySwiftSDKTests/OAuthExchangeTests.swift(+26 -0)📄 Description
ELI-5
Two bits of the OAuth code — the one that trades an authorization code for tokens (
OAuthExchanger) and the one that refreshes an expired token (OAuthTokenRefreshExecutor) — were doing the exact same five-step "POST a form to the token endpoint and decode the reply" dance, copy-pasted. This PR pulls that shared dance into one helper (OAuthTokenEndpoint.post) so there's a single copy. Each caller still builds its own form fields; nothing about how the SDK behaves changes.What changed
Sources/ComfySwiftSDK/Internal/Auth/OAuthTokenEndpoint.swift— an internalenumwith a singlestatic func post(queryItems:session:mapAuthInvalidToExpired:)that owns the shared flow: form-encode the body → POSTapplication/x-www-form-urlencoded→session.data(for:)in ado/catch→Transport.translate→Transport.checkStatus→ decode the token DTO → buildOAuthTokenResponse.OAuthExchanger.exchangenow builds only its query items and delegates to the helper withmapAuthInvalidToExpired: false. Its privateTokenExchangeDTOis deleted.OAuthTokenRefreshExecutor.refreshnow builds only its query items and delegates withmapAuthInvalidToExpired: true. Its privateTokenRefreshDTOis deleted.init(session:), and every call site are untouched.Net: +87 / −89, a pure refactor with no public API and no behavior change.
Load-bearing differences preserved (each pinned by an existing test)
authInvalid→authExpiredremap stays refresh-only. The helper wrapscheckStatusincatch ComfyError.authInvalid where mapAuthInvalidToExpired. Exchange passesfalse, so its.authInvalidpropagates unchanged; refresh passestrue, so a rejected refresh surfaces.authExpired. Thewhereclause guarantees any non-authInvaliderror (rate-limited, network, etc.) propagates in both modes.token_type. The mergedTokenDTOkeepstokenType: String?so both the exchange mock body (which includestoken_type) and the refresh mock body (which omits it) decode.ComfyError.unknown(underlying: URLError(.badURL))rather than silently sending an empty body (exchange's old behavior). Strictly safer; in practice unreachable for a non-empty query-item list.Note for the reviewer
The current source had already evolved to thread per-client config into these methods (
exchange(..., config: OAuthClientConfig = .comfyIOS)andrefresh(..., clientId:)with its RFC 6749 §6 comment). Those parameters only affect how each caller builds its query items, not the shared five-step dance, so they were preserved verbatim and the extraction applies cleanly on top.Testing
Full package suite green locally — 226 tests, 0 failures (
swift build+swift test). The four behavior-pinning tests called out for this change all pass:exchange HTTP 401 throws .authInvalidsecond 401 after a successful refresh surfaces .authExpired, never a looprefresh failure (401 on the refresh POST — family revoked) surfaces .authExpiredapiKey mode 401 still surfaces .authInvalid (no refresh machinery) — regressionNo new tests added: behavior is unchanged and already fully covered (the optional guarded-body
.badURLpath is unreachable through the public query-item API, so a test would only assert the guard's own premise).🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.