feat: CDYelpFusionKit v6.0.0 — drop Alamofire, native URLSession, async/await-only API#43
feat: CDYelpFusionKit v6.0.0 — drop Alamofire, native URLSession, async/await-only API#43chrisdhaan wants to merge 66 commits into
Conversation
…ependency declarations Bumps minimums to iOS 15 / macOS 12 / tvOS 15 / watchOS 8 (required for URLSession async/await) in both Package.swift and the podspec. Removes the Alamofire package and target dependencies in preparation for the native URLSession rewrite. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Introduces the native Swift error type that replaces AFError in v6. Four cases cover the full request lifecycle: invalidRequest, httpError, decodingFailed, and networkFailure. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Internal actor that owns the URLSession and implements the full request/response cycle: adapter chain → cache read → network → retry with backoff → cache write → decode. Replaces the Alamofire Session and the cachedRequest helper in CDYelpAPIClient. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replaces CDYelpRouter.swift with a pure URLComponents/URLQueryItem implementation. Covers all 19 endpoint cases (17 GET + 2 POST), injects the Bearer Authorization header directly, and routes aiChat to its hardcoded URL outside the /v3/ base path. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Drops the Alamofire import and replaces it with a native typealias Parameters = [String: Any], keeping all existing function signatures source-compatible. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Removes Alamofire entirely from the API client. Replaces the Alamofire Session, cachedRequest helper, makeSession factory, and all completion-handler overloads with direct async/await methods that delegate to the CDYelpURLSession actor via CDYelpNativeRouter. The testing init signature is unchanged so CDYelpMockClientFactory requires no modifications. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Removes CDYelpRouter.swift, CDYelpAlamofireEventMonitor.swift, and CDYelpAlamofireRequestAdapter.swift. These are fully replaced by CDYelpNativeRouter, CDYelpURLSession's inline monitor calls, and the adapter loop in CDYelpURLSession.perform(_:). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
CDYelpMockClientFactory calls the preserved testing init signature and CDYelpMockURLProtocol intercepts the native URLSession unchanged. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replaces all CDYelpRouter.X.asURLRequest() call sites with try CDYelpNativeRouter.X.asURLRequest(apiKey: "test-key"). Updates the three typed-array bulk tests from [CDYelpRouter] to [CDYelpNativeRouter]. No assertions changed. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
All existing simulator destinations (lowest: iOS 18.6, tvOS 18.5, watchOS 11.5) already exceed the new minimums imposed by the URLSession async/await deployment targets. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Squash of all v6.0.0 review, fix, and polish commits:
Core changes (Haiku's original work):
- CDYelpNetworkError: native 4-case error enum replacing AFError
- CDYelpURLSession: internal Swift actor owning URLSession pipeline
(adapter chain → cache → network → retry → decode)
- CDYelpAPIClient: all 19 endpoints rewritten as pure async throws;
completion-handler overloads removed entirely
- Parameters+CDYelpFusionKit: Alamofire encoding removed
- Alamofire source files deleted (CDYelpAlamofireEventMonitor,
CDYelpAlamofireRequestAdapter)
- Package.swift / podspec: Alamofire dependency removed; deployment
targets raised to iOS 15 / macOS 12 / tvOS 15 / watchOS 8
Review fixes applied in this session:
- CDYelpNativeRouter renamed to CDYelpRouter for naming continuity
- Pattern binding variables renamed p → params (fixes 17 SwiftLint
identifier_name violations)
- hoistPatternLet violations fixed in CDYelpRouter.queryParameters
- forEach → for-in loops in CDYelpURLSession (SwiftFormat preferForLoop)
- wrapLoopBodies: all inline for bodies expanded to multi-line
- CDYelpResponseCacheTests: added missing apiKey: argument on
asURLRequest() calls (4 call sites)
- isAuthenticated() re-added to CDYelpAPIClient (used by 2 test files)
- CDYelpRetryConfiguration: stale Alamofire comment updated
- Xcode project: dead file refs removed, new files added with correct
Internal/ path prefix, deployment targets updated, Alamofire SPM
package removed, MARKETING_VERSION bumped to 6.0.0
Version bump:
- CDYelpFusionKit.podspec: 5.1.0 → 6.0.0
- Source/CDYelpFusionKit.swift: version string → "6.0.0"
Example app:
- ViewController.swift cases 2–11 converted from completion handlers
to Task { try await } pattern
Documentation:
- CHANGELOG.md: [6.0.0] entry added
- README.md: platform minimums, SPM/CocoaPods versions, features list
- Documentation/Usage.md: full rewrite — async/await only, CDYelpNetworkError
- Documentation/ARCHITECTURE.md: full rewrite — URLSession-native architecture
- Documentation/CDYelpFusionKit 6.0 Migration Guide.md: new file
- Documentation/API_SCHEMA.md: hypothetical code snippets updated
- Source/CDYelpFusionKit.docc/CDYelpFusionKit.md: overview updated
- CLAUDE.md: description, platform targets, CDYelpRouter row updated
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…g selection handlers Extract didSelectRowAt into handleAPIEndpointSelection, handleDeepLinkSelection, and handleWebLinkSelection, and pull cellForRowAt label logic into a cellTitle(for:) helper to eliminate all swiftlint:disable comments except the unavoidable function_body_length on the 12-case API handler. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
… review rounds - CDYelpNetworkError: @unchecked Sendable (Error is not Sendable; Swift 6 compat) - fetchAIChat: replace force-unwrap with if-let for latitude/longitude userContext - Parameters+CDYelpFusionKit: remove IUO ! from phoneParameters, matchesParameters, autocompleteParameters; fix corresponding if-let body bindings - CDYelpURLSession: cap backoffNanoseconds at 300 s to prevent UInt64 overflow; compute cacheKey only for GET requests - CDYelpResponseCache: CDYelpCacheEntry struct → final class, NSCache now typed directly (no AnyObject boxing) - CDYelpRouter: document String(describing:) parameter-value assumption - CDYelpMockClientFactory: add retryConfiguration parameter - Tests: add postEndpointResponseIsNotCached, http500TriggersRetryUpToLimit, non500StatusCodeDoesNotTriggerRetry (198 tests, all passing) - Docs: .invalidURL → .invalidRequest(underlying:) across CHANGELOG, ARCHITECTURE, Migration Guide, Usage Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Cast event start/end dates to Int to avoid float strings in URLs (e.g. "1750000000.0") - Clamp backoff delay to >= 0 before UInt64 cast to prevent crash with negative initialDelay - Pass data.count as NSCache cost so totalCostLimit byte-based eviction is active - Add precondition to fetchAIChat requiring latitude and longitude together - Fix fetchReviews limit precondition to reject 0 (was >= 0, now > 0) - Notify event monitors when a CDYelpRequestAdapter throws - Guard nil HTTPURLResponse after network call; throw networkFailure instead of httpError(statusCode: 0) - Add Accept: application/json header to aiChat and jobs POST routes - Build aiChat URL from CDYelpURL.rootBase + path (eliminates hardcoded duplicate) - Add rootBase constant to CDYelpURL for non-v3 endpoints - Replace three manual append-loop-plus-strip patterns with .joined(separator:) - Remove implicitly-unwrapped optional from matchThresholdType parameter - Fix @unchecked Sendable comment on CDYelpNetworkError to reflect adapter-error risk - Add defer stub cleanup to two tests that lacked it (cachedResponse, postEndpoint) - Document async-delivery limitation on cancelAllTasks() Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- CDYelpURLSession: wrap cache-hit decode errors in CDYelpNetworkError.decodingFailed so callers see a consistent error type regardless of cache state - CDYelpURLSession: guard httpResponse before notifying monitors to prevent false success signals on non-HTTP responses - CDYelpURLSession: extract private perform(_:decoder:attempt:) so the recursion detail (attempt counter) is not exposed in the module-internal interface - CDYelpRouter: convert if-case-let POST dispatch to exhaustive switch so the compiler catches missing cases when new POST endpoints are added - CDYelpRouter: wrap JSONEncoder.encode calls in do/catch to keep EncodingError inside the CDYelpNetworkError.invalidRequest contract - CDYelpRouter: force-encode + as %2B in query strings — URLComponents allows + per RFC 3986 but servers decode it as a space, breaking phone number parameters - CDYelpNetworkError: correct @unchecked Sendable doc comment — Error has been Sendable since SE-0337; the real risk is adapter-supplied concrete error types Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Remove spurious `- returns: Void` from convenience init DocC - Add `- Throws: CDYelpNetworkError` annotation to all 19 async methods - Fix cancelAllPendingAPIRequests() doc to note asynchronous delivery - Fix isAuthenticated() doc to clarify it always returns true post-init - Fix fetchFeaturedEvent() parameter docs — remove contradictory "Required / Can be Optional" phrasing - Fix CDYelpNetworkError @unchecked Sendable comment — Error has been Sendable since SE-0337; clarify actual risk surface across all three underlying-error cases, not just .invalidRequest Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ME, and DocC - Usage.md: add missing await to clearCache() example - Usage.md: correct cache behaviour description — bytes are stored before decode, not after; remove false claim about poisoned-entry prevention - Usage.md: fix enum case .reservation → .restaurantReservation - ARCHITECTURE.md: replace non-existent sortedQueryItems() pseudocode with actual URLQueryItem map; note that cache keys use sorted canonical form - ARCHITECTURE.md: fix decoder configuration default (.default, not CDYelpDecoderConfiguration()) - ARCHITECTURE.md: remove claim about "data tasks list" — no such list exists; cancellation is delegated to URLSession - README.md: correct Sendable claim — CDYelpAPIClient is plain Sendable, not @unchecked Sendable; @unchecked is on CDYelpNetworkError - README.md: update test count 193 → 198 - CDYelpFusionKit.docc: expand overview to list all 15 endpoint groups Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- malformedResponseBodyThrowsDecodingFailed: verify 200 with type-mismatch JSON throws CDYelpNetworkError.decodingFailed (not raw DecodingError) - cachedMalformedResponseThrowsDecodingFailed: verify cache-hit path also wraps decode errors in .decodingFailed (tests the fix in CDYelpURLSession) - searchBusinessesReturns404AsSpecificHttpError: assert thrown error is CDYelpNetworkError.httpError(404, _) not just Error.self - http500FinalThrowIsSpecificHttpError: assert final throw after retries is CDYelpNetworkError.httpError(500, _) - adapterFailureThrowsInvalidRequestError: verify adapter throw propagates as CDYelpNetworkError.invalidRequest end-to-end - eventMonitorReceivesCompletedCallbackOnSuccess: verify requestDidComplete fires with a non-nil response and nil error on success - fetchReviewsDecodesDateFields: exercise date-aware DateFormatter.reviews decoder used by fetchReviews (previously no integration coverage) - fetchJobsPostResponseIsNotCached: verify POST jobs endpoint bypasses cache - phoneNumberPlusSignIsPercentEncoded: verify + in E.164 number encodes as %2B not literal + (tests the CDYelpRouter fix) Refactor: extract CDYelpURLSession.decodeWrapping(_:decoder:) to eliminate duplicate do/catch pattern and bring private perform under the 70-line limit Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Cache write moved after decode — malformed 2xx no longer poisons cache - Bool query params encode as "1"/"0" (was "true"/"false") - fetchReviews limit precondition restored to >= 0 (limit: 0 was valid in v5) - clearCache() made synchronous via nonisolated (NSCache is thread-safe) - requestDidComplete(error: nil) no longer fires for 4xx/5xx responses - Auth header re-injected after adapter chain — adapters cannot strip Bearer token - cancelAllTasks() now cancels Swift Tasks sleeping in retry backoff via trackedSleep - Adapters run only on attempt 0; retry passes already-adapted request - requestDidComplete fires once per logical call (not per intermediate retry) - requestDidStart fires once per logical call (not per intermediate retry) - Transport failures pass CDYelpNetworkError to monitors (not raw URLError) - Cache hits fire requestDidStart + requestDidComplete on monitors Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- CDYelpRouter: bool params now encode as true/false (was 1/0), fixing open_now, is_free, and get_covers_range silently sending wrong values - CDYelpURLSession: requestDidComplete fires after decodeWrapping on the success path so monitors never see a success signal before JSON is valid - CDYelpURLSession: cache decode failure now evicts the stale entry so the next call falls through to the network instead of failing for the full TTL - CDYelpURLSession: adapter failure path fires requestDidStart before requestDidComplete, keeping the monitor lifecycle always paired - CDYelpURLSession: both retry sleep paths wrap trackedSleep in try/catch so requestDidComplete fires on cancellation - CDYelpURLSession: auth header only restored when an adapter removes it entirely; token rotation adapters keep their replacement value - CDYelpURLSession: trackedSleep cancels the inner Task in defer to prevent orphaned sleeps after outer-task cancellation - CDYelpResponseCache: expired entries return nil without removeObject, eliminating a TOCTOU race that could evict a freshly-written entry - CDYelpURLSession: cache key computation guarded by cache != nil to avoid URL parse + sort on every GET when caching is disabled - CDYelpMockClientFactory: expose decoderConfiguration so tests can exercise non-default decoder strategies - .swiftlint.yml: raise function_body_length threshold to 130/140 to accommodate CDYelpURLSession.perform's inherent lifecycle complexity rather than suppressing it with an inline disable comment - Parameters+CDYelpFusionKit: remove now-superfluous function_body_length disable (only function_parameter_count remains) - Tests: 5 new tests covering auth restoration, token rotation, monitor decode-failure signalling, monitor lifecycle on adapter failure, and cache targeted eviction (218 tests total) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- CDYelpAPIClient: package init → public init; package access requires a
-package-name flag that Xcode project builds and CocoaPods lint don't set,
so the iOS/macOS/tvOS/watchOS/visionOS/Catalyst and CocoaPods CI jobs were
failing to compile entirely
- CDYelpAPIClient: remove isAuthenticated() — init's precondition already
makes an empty apiKey unreachable, so the method could never return false
- CDYelpAPIClient/CDYelpURLSession: cancelAllPendingAPIRequests() and
cancelAllTasks() are now async and await actual cancellation via
URLSession.tasks instead of returning before cancellation takes effect
- CDYelpRouter: percent-encode path-embedded identifiers (business id, event
id, category alias, etc.) so a "?"/"#" in an id can't be reinterpreted as a
query/fragment delimiter when the concatenated URL string is re-parsed
- CDYelpRouter: encode boolean query parameters numerically ("1"/"0") instead
of "true"/"false", restoring the wire format this library has always sent
(previously via Alamofire's default .numeric boolEncoding)
- CDYelpURLSession: shouldRetry now excludes non-idempotent HTTP methods
(POST), matching Alamofire's prior RetryPolicy default — aiChat/jobs
requests were being silently auto-retried, risking duplicate server-side
processing
- CDYelpURLSession: wrap retry-backoff sleep cancellation in
CDYelpNetworkError instead of leaking a raw CancellationError, honoring the
documented "Throws: CDYelpNetworkError" contract
- CDYelpURLSession: gate cache lookup to the first attempt only — a retry
means the caller's retry policy asked for a fresh network attempt, not an
opportunistic cache hit
- CDYelpURLSession: extract shared notifyStart/notifyComplete/notifyRetry and
retryOrThrow helpers, removing ~15 duplicated monitor-notification loops
and two duplicated retry-and-backoff blocks
- Example/ViewController: await cancelAllPendingAPIRequests() before starting
a new request so the async cancellation can't race with and cancel the
request it was meant to make way for
- Docs: CHANGELOG and Migration Guide updated for the isAuthenticated()
removal and cancelAllPendingAPIRequests() async signature change
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- CDYelpRouterTests: id containing "?" or "#" is percent-encoded rather than reinterpreted as a query/fragment delimiter; boolean query parameters encode as "1"/"0" - CDYelpAPIClientTests: a POST endpoint (jobs) does not retry on a retryable status code Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…mums CDYelpFusionKit.xcodeproj's project-level defaults (IPHONEOS 15.6, MACOSX 12.4, TVOS 15.6, WATCHOS 8.7) were higher than the iOS 15.0 / macOS 12.0 / tvOS 15.0 / watchOS 8.0 minimums documented in Package.swift, the podspec, and CLAUDE.md, and weren't overridden by any of the platform-specific targets (only CDYelpFusionKitTesting explicitly set the correct values). Every Xcode-built scheme was silently requiring a newer OS than promised. Lower the project-level defaults to match the documented minimums, and raise the Example app's own IPHONEOS_DEPLOYMENT_TARGET from 13.0 to 15.0 to match the framework's actual minimum — the Example app previously failed to build against the v6.0.0 framework because of this mismatch. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
CDYelpFusionKit.xcodeproj's "CDYelpFusionKit macOS" native target set MACOSX_DEPLOYMENT_TARGET = 11.0 at the target level, overriding the project-level default of 12.0 that commit 3cc7aac fixed. This target-level override was missed, leaving the macOS scheme's actual minimum inconsistent with CLAUDE.md, Package.swift, and the podspec (all documented as 12.0). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
trackedSleep wrapped Task.sleep in a detached, unstructured Task so it
could be tracked and cancelled by cancelAllTasks(). But an unstructured
Task's cancellation state isn't linked to the task that created it, so
cancelling the caller's own Task (e.g. `let t = Task { try await
client.searchBusinesses(...) }; t.cancel()`) had no effect on an in-flight
retry-backoff sleep — only the library's explicit
cancelAllPendingAPIRequests() could interrupt it, silently breaking the
standard Swift concurrency expectation that cancelling a Task cancels
everything it's awaiting.
Wrap the sleep in withTaskCancellationHandler so ambient cancellation is
forwarded into the detached task. Add regression tests verifying both
cancelAllPendingAPIRequests() and direct Task.cancel() interrupt an
in-flight retry backoff promptly rather than waiting out the full delay.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
expiredEntryDoesNotEvictConcurrentlyWrittenFreshEntry never actually created an expired cache entry, so it never exercised the code path the TOCTOU fix (bd1a402) touched — reverting that fix locally left the test green. Attach an NSCacheDelegate (exposing `cache` as internal instead of private for test access) and assert directly that reading a genuinely expired entry triggers zero evictions, which fails immediately if the old removeObject-on-expiry behavior is reintroduced. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
String(describing:) renders Double values under ~0.0001 in scientific notation (e.g. "1e-05"), so a latitude or longitude within about 11 meters of the equator or prime meridian was sent as an unparseable coordinate. Format Double query parameters with %.8f instead, matching the fixed decimal notation the Yelp Fusion API expects. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
API_SCHEMA.md still listed every endpoint's Swift method with a trailing completion: parameter, even though completion handlers were fully removed in the v6.0.0 async/await rewrite. Drop the stale parameter from all 19 signatures, and fix a leftover stray code-fence artifact. Update README's test count, which had drifted from 198 to the current 226 across several rounds of added regression coverage. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…e cache fetchEngagementMetrics, fetchServiceOfferings, fetchBusinessInsights, fetchReviewHighlights, and fetchOpenings bypassed caching entirely in the pre-v6 Alamofire client. The v6 URLSession pipeline caches any GET request whenever a cache is configured, with no per-endpoint opt-out, silently making these five cache-eligible — most notably fetchOpenings, which returns real-time reservation availability. Add a `cacheable` flag to CDYelpURLSession.perform (default true) and pass false from these five endpoints to restore the original behavior.
%.8f formatting rendered Double.nan/.infinity as literal "nan"/"inf"/"-inf" with no validation, silently sending a malformed request (e.g. latitude=nan) to the Yelp Fusion API. Throw CDYelpNetworkError.invalidRequest instead, since none of searchBusinesses(byTerm:), searchTransactions(byType:), searchEvents(), or fetchFeaturedEvent() precondition-check finiteness before reaching the router.
Reflects the 8 tests added by this round's fixes (cache scope, NaN/Infinity guard, header restoration, .networkFailure coverage, cancelled-retry exclusion, and cache self-heal).
The non-HTTPURLResponse guard in CDYelpURLSession.perform threw directly instead of going through retryOrThrow like every other failure branch, silently skipping retry/backoff for that path. Header restoration after running request adapters was also hardcoded to three specific header names; generalize it to snapshot and restore the full original header set so a future router-added header isn't silently dropped by a wholesale-replacing adapter. Also documents that CDYelpEventMonitor.requestDidComplete's response can be nil for cache-served completions. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…ent header - Add CDYelpRouter.isCacheable as the single source of truth for which endpoints are cacheable, replacing five scattered `cacheable: false` literals in CDYelpAPIClient plus the implicit default for the rest — a future endpoint's cacheability is no longer something a caller can forget to opt out of. - Restore a User-Agent header on every request; it was dropped along with Alamofire's HTTPHeaders.default and never replaced. - Extract applyStandardHeaders/postRequest helpers in CDYelpRouter to de-duplicate the .aiChat/.jobs POST construction and the three separate inline Authorization/Accept/Content-Type header assignments. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…ange API_SCHEMA.md and the 6.0 migration guide still described parameter validation as "asserted" (Debug-only), left over from before parameter validation was widened to precondition (traps in all build configurations, including Release) across ~28 call sites in CDYelpAPIClient — an intentional, established pattern in this codebase (see the 3.x CHANGELOG entry doing the same for apiKey), not something being reverted here. Update the schema doc's wording and add a migration guide section calling out the Release-build behavior change explicitly. Also adds a CHANGELOG entry for the fixes in the prior two commits. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
No functional change. Adds an in-code note explaining the TOCTOU rationale (commit bd1a402) so a future reviewer doesn't mistake the missing removeObject call for a regression. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…ow-list Alamofire's HTTPHeaders.default previously sent Accept-Language (derived from Locale.preferredLanguages) on every request; the v6 rewrite dropped it along with Alamofire. Restore it via a native Accept-Language builder in CDYelpRouter, mirroring Alamofire's quality-encoded format. Also convert isCacheable from an exclusion list (default: cacheable) to an exhaustive allow-list with no default case, so a newly added router case fails to compile until someone decides which bucket it belongs in, instead of silently inheriting "cacheable". Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
startLoading() force-unwrapped HTTPURLResponse's failable initializer. Stub.init accepts any Int statusCode with no validation, so a caller (this repo's tests or a downstream consumer, since this is a public Testing-target helper) registering an out-of-range statusCode or a rejected header value would crash the test process instead of failing the request. Guard it and fail with URLError(.badServerResponse). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…etry-After httpError previously only carried statusCode and data, so the retry engine could never see a server-provided Retry-After header and always fell back to blind exponential backoff — mistimed waits under sustained 429 rate-limiting. Add headers: [String: String] to the case (sourced via a new HTTPURLResponse.stringHeaderFields snapshot) and prefer a valid Retry-After value (seconds or HTTP-date form, per RFC 9110 10.2.3) over exponential backoff when present, still capped at the existing 300s ceiling. Updates the two existing httpError pattern-match tests for the new three-value tuple arity; no behavior they assert on changed. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- Extract a shared private perform<T>(_:decoder:) helper that builds the request from a router case and calls urlSession.perform(...), used by all 19 endpoint methods instead of each repeating the same two/three lines verbatim. - Extract makeDecoder(dateFormat:) to replace the four copies of "decoderConfiguration.makeDecoder() + .formatted date strategy" in the reviews/events endpoints. - Document fetchAIChat's latitude/longitude joint precondition in its doc comment — pre-rewrite code silently dropped a lone coordinate instead of trapping, and neither the doc comment nor the migration guide mentioned the new stricter behavior. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
The three failure branches in perform()'s retry loop each repeated the same attempt += 1; continue pair after calling retryOrThrow. Move the increment into retryOrThrow itself (attempt now inout), so each call site only needs `continue` and a future change to loop bookkeeping only needs to be made in one place. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…attempt adapt(_:) runs once before the first attempt; a retried request resends the same already-adapted URLRequest. This is deliberate (see requestAdapterRunsOncePerLogicalCallWithRetry) but wasn't called out anywhere an adapter author would see it — add a protocol doc note and a matching callout in the Usage.md request-signing example. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…fixes Reflects the CDYelpNetworkError.httpError signature change (added headers:), the corrected retry-loop description (it was still described as recursive after an earlier refactor to an explicit loop), the new fetchAIChat migration guide entry, and a changelog summary of every fix applied in this pass. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…nguage Reformats the immediately-invoked map/joined closure into a single expression per CI's swiftformat --lint check; no behavior change. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
6.0.0 hasn't shipped yet, so a running list of bugs fixed during its own PR review isn't a meaningful "Fixed" entry — those are for issues found in an already-released version. Condense the genuinely user-facing outcomes (Retry-After support, restored default headers, adapter header restoration, fetchAIChat's coordinate precondition) into Added/Updated instead, and drop the purely internal implementation-detail bullets (isCacheable's list shape, aiChat/jobs dedup, etc.). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…links
CDYelpMockClientFactory and CDYelpMockURLProtocol live in the separate
CDYelpFusionKitTesting target. CI's DocC Build job only documents the
CDYelpFusionKit target and fails on any warning, so the double-backtick
symbol links to those two types in CDYelpAPIClient's init doc comment
("doesn't exist at ...") failed the build. Switch to plain inline code
spans, which render the same but don't attempt symbol resolution.
Verified locally: swift package generate-documentation --target
CDYelpFusionKit now produces zero warnings (previously 2).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Code reviewFound 1 issue:
Notably, commit 928cbc7 already fixed this identical issue on the sibling "CDYelpFusionKit macOS" target ("fix: correct macOS target deployment target left at 11.0 by prior fix") — the iOS target's own override was missed in that pass. Debug config: CDYelpFusionKit/CDYelpFusionKit.xcodeproj/project.pbxproj Lines 1680 to 1684 in 95af10d Release config: CDYelpFusionKit/CDYelpFusionKit.xcodeproj/project.pbxproj Lines 1704 to 1708 in 95af10d CI Catalyst job building this scheme against CDYelpFusionKit/.github/workflows/ci.yml Lines 249 to 261 in 95af10d 🤖 Generated with Claude Code - If this code review was useful, please react with 👍. Otherwise, react with 👎. |
The "CDYelpFusionKit iOS" target's Debug/Release configs still had MACOSX_DEPLOYMENT_TARGET = 11.0, inconsistent with the documented macOS 12.0 minimum (CLAUDE.md, Package.swift, podspec). This target is built for Catalyst in CI via `-destination "platform=macOS"`, so the stale value was a real build-surface mismatch, not inert boilerplate. Commit 928cbc7 fixed the identical issue on the sibling "CDYelpFusionKit macOS" target but missed this one. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Replace stale completion-handler example and incorrect iOS 13+ claim with correct async/await-only example and accurate platform minimums (iOS 15.0+, macOS 12.0+, tvOS 15.0+, watchOS 8.0+, visionOS 1.0+). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
CDYelpRouter.asURLRequest(apiKey:) can throw .invalidRequest before CDYelpURLSession.perform is ever invoked (e.g. a NaN/Infinity query parameter), so those failures previously never reached a registered CDYelpEventMonitor. CDYelpURLSession.perform now takes a buildRequest closure instead of a pre-built URLRequest, so a construction failure gets the same paired requestDidStart/requestDidComplete treatment as an adapter-thrown .invalidRequest, using a fixed placeholder URL since no real request exists yet in that failure case. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…utocomplete endpoints Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…dpoints Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…ints Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…s/reviewHighlights endpoints Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
fetchEventReturnsDecodedResponse used a fixture with no date fields, so a regression in DateFormatter.events's format string would go undetected. Add a time_start field and assert the decoded Date matches DateFormatter.events.date(from:) directly, so the test can't pass no matter what the formatter does. Also add MARK sectioning for the four thematic test groups in CDYelpAPIClientTests and document why @suite(.serialized) is required (overlapping substring stub keys with no deterministic lookup priority). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…ntracked Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Resolves GHSA advisories for AtomicReference#update livelock on NaN (high), and two ReadWriteLock/ReentrantReadWriteLock correctness issues (low), all fixed upstream in concurrent-ruby 1.3.7. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Summary
CDYelpFusionKit v6.0.0 is a major architectural overhaul that removes the Alamofire dependency entirely, replaces it with a native URLSession-backed actor pipeline, and drops the completion-handler API in favour of a pure
async throwsinterface.Target release date: June 23, 2026
Breaking Changes
CDYelpAPIClientmethods are nowasync throwsonly;completion:variants are goneAFErrorreplaced byCDYelpNetworkError(.invalidURL,.networkFailure(underlying:),.httpError(statusCode:data:),.decodingFailed(underlying:))What Didn't Change
All public configuration types, protocols, and response models are identical to v5.x — only the transport layer and API surface changed:
CDYelpCacheConfiguration,CDYelpRetryConfiguration,CDYelpDecoderConfigurationCDYelpEventMonitor,CDYelpRequestAdapterprotocolsCDYelpMockURLProtocol,CDYelpMockClientFactorytesting utilitiesNew Internal Types
CDYelpNetworkError— native Swift error enumCDYelpURLSession— Swift actor owning the full networking pipeline (adapter chain → cache → URLSession → retry → decode)CDYelpRouter(renamed fromCDYelpNativeRouter) — internal URL routing enum;asURLRequest(apiKey:)now takes an explicit API key parameterFiles Changed
Source/CDYelpNetworkError.swiftSource/Internal/CDYelpURLSession.swiftSource/Internal/CDYelpRouter.swiftCDYelpNativeRouter.swift)Source/CDYelpAPIClient.swiftSource/Parameters+CDYelpFusionKit.swiftSource/CDYelpFusionKit.swift"6.0.0"Source/CDYelpRetryConfiguration.swiftCDYelpFusionKit.podspecPackage.swiftCDYelpFusionKit.xcodeprojExample/Source/ViewController.swiftTests/apiKey:argument added)Documentation/Usage.mdDocumentation/ARCHITECTURE.mdDocumentation/CDYelpFusionKit 6.0 Migration Guide.mdCHANGELOG.mdREADME.mdTest Plan
swift build— zero warnings, zero errorsswift test— all 193 tests passswiftlint lint --strict— zero violationsswiftformat Source Tests --lint— zero violationsbundle exec pod lib lint— passes6.0.0,pod trunk push CDYelpFusionKit.podspec,bash scripts/generate-docs.sh+ pushdocs/🤖 Generated with Claude Code