-
Notifications
You must be signed in to change notification settings - Fork 31
[ECO-5633] Refactor executeRequest
#2158
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
WalkthroughCentralizes and renames ARTRest HTTP execution: introduces per-request timeouts, new entry points Changes
Sequence Diagram(s)sequenceDiagram
autonumber
participant Client
participant ARTRest as ARTRestInternal
participant Auth as Authentication
participant Network as NetworkLayer
participant Fallback as ARTFallback
Client->>ARTRest: executeAblyRequest(request, authOption, wrapperSDKAgents)
alt authOption == Off
ARTRest->>Network: executeExternalRequest(request)
Network-->>ARTRest: response / data / error
else needs auth
ARTRest->>Auth: obtain token or sign request
Auth-->>ARTRest: token / signedRequest
ARTRest->>Network: executeExternalRequest(signedRequest)
Network-->>ARTRest: response / data / error
end
ARTRest->>ARTRest: handleAblyRestRequestCompletion(response, data, error, requestId)
alt invalid content-type or decode error
ARTRest-->>Client: error (with requestId)
else HTTP error and shouldRenewToken
ARTRest->>Auth: renew token (single retry)
Auth-->>ARTRest: new token / error
ARTRest->>Network: retry request with new token
Network-->>ARTRest: response / data / error
ARTRest-->>Client: response or error
else HTTP error and shouldRetryWithFallback
ARTRest->>Fallback: choose alternate host
ARTRest->>Network: retry request on fallback host
Network-->>ARTRest: response / data / error
ARTRest-->>Client: response or error
else success
ARTRest-->>Client: response / data (update prioritized host)
end
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Poem
Pre-merge checks and finishing touches❌ Failed checks (1 warning, 1 inconclusive)
✅ Passed checks (3 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
f014e2a to
047e575
Compare
047e575 to
c67fa1d
Compare
c67fa1d to
4d01feb
Compare
4d01feb to
f6126a7
Compare
f6126a7 to
ffb92af
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
Source/ARTHTTPPaginatedResponse.m (1)
96-127: Ably HTTP errors are now likely swallowed in paginated responsesWith
executeAblyRequestnow surfacing Ably HTTP errors via theerrorparameter (asARTErrorInfo), the existing logic here will treat those as successful paginated responses with emptyitems:
- For Ably errors:
erroris non-nil anderror.domain == ARTAblyErrorDomain, so the earlyif (error && ![error.domain isEqualToString:ARTAblyErrorDomain])branch is skipped.itemsbecomes@[]becauseerroris non-nil.- The method then builds an
ARTHTTPPaginatedResponseand callscallback(result, nil), losing the actual error.That makes paginated Ably calls (e.g. stats/history) report success with an empty page when the server actually returned an error.
You probably want to treat any non-nil
errorfromexecuteAblyRequestas a failure, and only decode items whenerror == nil. For example:- [rest executeAblyRequest:request withAuthOption:ARTAuthenticationOn wrapperSDKAgents:wrapperSDKAgents completion:^(NSHTTPURLResponse *response, NSData *data, NSError *error) { - if (error && ![error.domain isEqualToString:ARTAblyErrorDomain]) { - callback(nil, [ARTErrorInfo createFromNSError:error]); - return; - } + [rest executeAblyRequest:request withAuthOption:ARTAuthenticationOn wrapperSDKAgents:wrapperSDKAgents completion:^(NSHTTPURLResponse *response, NSData *data, NSError *error) { + if (error) { + ARTErrorInfo *errorInfo; + if ([error isKindOfClass:[ARTErrorInfo class]]) { + errorInfo = (ARTErrorInfo *)error; + } else { + errorInfo = [ARTErrorInfo createFromNSError:error); + } + callback(nil, errorInfo); + return; + } ARTLogDebug(logger, @"HTTP Paginated response: %@", response); ARTLogDebug(logger, @"HTTP Paginated response data: %@", [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]); NSError *decodeError = nil; ARTPaginatedResultResponseProcessor responseProcessor = ^(NSHTTPURLResponse *response, NSData *data, NSError **errorPtr) { id<ARTEncoder> encoder = [rest.encoders objectForKey:response.MIMEType]; return [encoder decodeToArray:data error:errorPtr]; }; - NSArray *items = error ? @[] : responseProcessor(response, data, &decodeError); + NSArray *items = responseProcessor(response, data, &decodeError);This preserves decode-error handling (still returning
callback(nil, ARTErrorInfo-from-decodeError)below) while ensuring Ably HTTP errors fromexecuteAblyRequestare surfaced to callers instead of being turned into empty pages.
🧹 Nitpick comments (7)
Test/AblyTests/Tests/UtilitiesTests.swift (1)
260-263: Align closure parameters with usage to satisfy SwiftLintThe migration to
rest.internal.executeAblyRequestand the explanatory comment on using a placeholder URL both look fine for these simulated-error tests.Given that neither test uses the
responseparameter, you can simplify the closure signatures to avoid theunused_closure_parameterwarnings mentioned in static analysis:- rest.internal.executeAblyRequest(request, wrapperSDKAgents:nil, completion: { response, _, error in + rest.internal.executeAblyRequest(request, wrapperSDKAgents:nil, completion: { _, _, error in guard let error = error as? ARTErrorInfo else { fail("Should be ARTErrorInfo"); done(); return } ... })Apply the same pattern to the second call at Line 297.
Also applies to: 295-298
Test/AblyTests/Tests/RestClientTests.swift (1)
179-181: Confirm executor choice for non‑Ably URL in HTML/plaintext error testThis test now calls
rest.internal.executeAblyRequestfor a request whose URL host iswww.example.com. IfexecuteAblyRequestis intended only for Ably API hosts (with fallback, auth, and Ably‑specific error shaping), you may want to route this via the newexecuteExternalRequestinstead so this test more closely mirrors how external endpoints (like the one in ECO‑5633) are handled in production. IfexecuteAblyRequestis explicitly designed to support arbitrary hosts as well, keeping it here is fine but worth confirming that assumption.Source/ARTPushActivationStateMachine.m (1)
192-215: Push activation HTTP calls now use executeAblyRequest; device registration error path is saferSwitching the four push‑activation HTTP calls to
executeAblyRequestkeeps semantics aligned with other Ably REST interactions.In
deviceRegistration, the newreturnafter emittingGettingDeviceRegistrationFailedonerrorprevents attempting to decodedataon a failed request, which is the right control‑flow fix.Minor follow‑up you may consider while you’re here: in the decode‑error branch you still log
error.localizedDescriptioneven though the failure is indecodeError; swapping todecodeError.localizedDescriptionwould make the log more useful.Also applies to: 255-274, 304-322, 363-383
Source/ARTPushChannelSubscriptions.m (1)
93-108: executeAblyRequest migration for push channel subscriptions looks fine; relies on non‑nil responsesBoth
saveand_removeWherenow route viaexecuteAblyRequestwithARTAuthenticationOn, preserving the existing status‑code‑based success logic and error wrapping. These completions still assumeresponseis non‑nil (they readresponse.statusCodebefore consultingerror), so just ensure the new executor preserves the previous guarantee of always providing anNSHTTPURLResponseon completion.Also applies to: 211-226
Source/ARTRestChannel.m (1)
258-305: Status request now uses executeAblyRequest; verify response non‑nil invariantRouting the channel status call through
executeAblyRequestwithARTAuthenticationOnis consistent with the rest of the REST layer refactor. This method, however, dereferencesresponse.statusCodeunconditionally and only later considerserror. Please confirm thatexecuteAblyRequeststill guarantees a non‑nilNSHTTPURLResponsein all completion paths (including network failures); otherwise, it would be safer to guard forresponse == nilbefore accessingstatusCodeand synthesize anARTErrorInfofromerrorin that case.Source/PrivateHeaders/Ably/ARTRest+Private.h (1)
74-76: Internal REST APIs are well-factored; consider aligning nullability annotationsThe separation between
executeExternalRequest(raw HTTP) and the twoexecuteAblyRequestoverloads (Ably-aware + auth) is clear and matches the implementation inARTRest.m.One small polish point: in the header, the
completion:parameter for- (nullable NSObject<ARTCancellable> *)executeAblyRequest:(NSURLRequest *)request wrapperSDKAgents:(nullable NSDictionary<NSString *, NSString *> *)wrapperSDKAgents completion:(nullable ARTURLRequestCallback)callback;is nullable, whereas the implementation declares it as nonnull. It would be good to make these consistent (either allow
nilin both, or require it in both) to avoid confusion and potential nullability warnings.Also applies to: 79-83, 84-86
Source/ARTRest.m (1)
247-268: Centralised auth/REST execution is solid; consider improving cancellable semanticsThe new flow:
executeAblyRequest:withAuthOption:...normalising auth modes to either:
- a plain Ably request (
ARTAuthenticationOff), orexecuteAblyRequestWithAuthentication:...with the appropriate method / force flag; and_timeWithWrapperSDKAgentsnow going throughexecuteAblyRequest(..., ARTAuthenticationOff, ...)is a nice consolidation of the various call sites into a single pipeline.
One thing to be aware of is the
__block NSObject<ARTCancellable> *taskpattern inexecuteAblyRequestWithAuthentication:...:__block NSObject<ARTCancellable> *task; if (method == ARTAuthMethodBasic) { ... task = [self executeAblyRequest:request wrapperSDKAgents:wrapperSDKAgents completion:callback]; } else { if (!force && [self.auth tokenRemainsValid]) { ... task = [self executeAblyRequest:request wrapperSDKAgents:wrapperSDKAgents completion:callback]; } else { task = [self.auth _authorize:nil options:self.options callback:^(ARTTokenDetails *tokenDetails, NSError *error) { ... task = [self executeAblyRequest:request wrapperSDKAgents:wrapperSDKAgents completion:callback]; }]; } } return task;For the “new token then send request” path, the
ARTCancellablereturned to the caller represents the auth request, not the eventual Ably HTTP call (reassigningtaskinside the callback does not update what the caller already received). If you wantexecuteAblyRequestWithAuthenticationto be cancellable as a single operation, you may want a small compositeARTCancellablethat coordinates cancellation of both the auth and (once created) the subsequent HTTP task, instead of returning just the auth task.Also applies to: 270-281, 318-341, 616-616
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
Disabled knowledge base sources:
- Jira integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (16)
Source/ARTAuth.m(3 hunks)Source/ARTHTTPPaginatedResponse.m(1 hunks)Source/ARTPaginatedResult.m(1 hunks)Source/ARTPushActivationStateMachine.m(4 hunks)Source/ARTPushAdmin.m(1 hunks)Source/ARTPushChannel.m(4 hunks)Source/ARTPushChannelSubscriptions.m(2 hunks)Source/ARTPushDeviceRegistrations.m(4 hunks)Source/ARTRest.m(6 hunks)Source/ARTRestAnnotations.m(1 hunks)Source/ARTRestChannel.m(4 hunks)Source/PrivateHeaders/Ably/ARTRest+Private.h(1 hunks)Test/AblyTests/Tests/AuthTests.swift(1 hunks)Test/AblyTests/Tests/RealtimeClientConnectionTests.swift(4 hunks)Test/AblyTests/Tests/RestClientTests.swift(1 hunks)Test/AblyTests/Tests/UtilitiesTests.swift(2 hunks)
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-12-10T14:14:47.456Z
Learnt from: lawrence-forooghian
Repo: ably/ably-cocoa PR: 2162
File: Source/ARTJsonLikeEncoder.m:522-525
Timestamp: 2025-12-10T14:14:47.456Z
Learning: In Source/ARTJsonLikeEncoder.m, ensure that artMessage.actionIsInternallySet is only set to YES when the SDK explicitly sets the action field (e.g., in ARTMessage initWithName:data:). Messages decoded from the wire must not have actionIsInternallySet set, even if they contain an action field, because the flag should distinguish SDK-internal actions from wire-provided actions. Apply this behavior broadly to Objective-C files in the Source directory that involve message encoding/decoding, and add tests to verify that decoding does not flip the flag.
Applied to files:
Source/ARTPushChannel.mSource/ARTAuth.mSource/ARTPushChannelSubscriptions.mSource/ARTPushAdmin.mSource/ARTRestAnnotations.mSource/ARTPushDeviceRegistrations.mSource/ARTPushActivationStateMachine.mSource/ARTRestChannel.mSource/ARTPaginatedResult.mSource/ARTHTTPPaginatedResponse.mSource/ARTRest.m
🧬 Code graph analysis (1)
Test/AblyTests/Tests/UtilitiesTests.swift (1)
Test/AblyTests/Tests/MessageUpdatesDeletesTests.swift (1)
rest(41-49)
🪛 SwiftLint (0.57.0)
Test/AblyTests/Tests/UtilitiesTests.swift
[Warning] 262-262: Unused parameter in a closure should be replaced with _
(unused_closure_parameter)
[Warning] 297-297: Unused parameter in a closure should be replaced with _
(unused_closure_parameter)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (6)
- GitHub Check: check (iOS, test_iOS18_4)
- GitHub Check: check (macOS, test_macOS)
- GitHub Check: check
- GitHub Check: check (tvOS, test_tvOS18_4)
- GitHub Check: build
- GitHub Check: check
🔇 Additional comments (13)
Source/ARTRestAnnotations.m (1)
205-208: executeAblyRequest migration for annotation publish looks correctUsing
executeAblyRequest:withAuthOption:ARTAuthenticationOn wrapperSDKAgents:nilhere is consistent with other internal REST calls and preserves the existing error-wrapping behavior aroundARTErrorInfo. No further changes needed.Test/AblyTests/Tests/AuthTests.swift (1)
595-595: Additional assertion on error description strengthens regression coverageAsserting that
errorInfo.description()contains"Invalid request: body param is required"complements the existing checks on error code and message and directly guards against regressions for the #2135 error-shape case. Looks good as written.Source/ARTPushAdmin.m (1)
87-94: Push admin now correctly uses executeAblyRequestSwitching to
executeAblyRequest:withAuthOption:ARTAuthenticationOn wrapperSDKAgents:...here is consistent with the rest of the refactor and retains the existing logging andARTErrorInfopropagation semantics for push publishes.Source/ARTPushChannel.m (1)
132-138: executeAblyRequest migration for push channel subscribe/unsubscribe looks correctAll four push subscribe/unsubscribe paths now call
executeAblyRequestwithARTAuthenticationOn, preserving the existing request construction, logging, and callback behavior. No functional issues spotted.Also applies to: 166-172, 203-210, 237-244
Source/ARTAuth.m (2)
259-275: AuthUrl requests now respect client HTTP timeoutSetting
request.timeoutIntervalfrom_options.httpRequestTimeoutbrings authUrl token requests in line with the client’s HTTP timeout configuration. This assumes_optionsis always anARTClientOptions(or subclass ofARTAuthOptions) that actually defineshttpRequestTimeout; if there’s any code path where_optionscould be a bareARTAuthOptions, it’d be good to double‑check that before merging.
369-382: Executor split between external authUrl and Ably token endpoint is consistentUsing
_rest executeExternalRequestforauthUrland_rest executeAblyRequestwithARTAuthenticationOfffor/keys/{keyName}/requestTokenmatches the intended separation of concerns (generic external HTTP vs Ably API with key‑based auth). Error propagation and decoding logic around these calls remains unchanged, which should keep existing behavior intact apart from the centralized request handling in the new executors.Also applies to: 512-525
Source/ARTRestChannel.m (3)
393-404: Message publish now goes via executeAblyRequest; error wrapping preserved
internalPostMessagesnow usesexecuteAblyRequestand still wraps anyNSErrorintoARTErrorInfo, optionally prepending the request URL whenaddRequestIdsis enabled. The behavior and threading model (callback re‑dispatched onto_userQueue) are preserved.
517-528: Message update/delete HTTP call migration is consistentThe
_updateMessagehelper now callsexecuteAblyRequestwithARTAuthenticationOn, keeping the constructed URL, body, and error wrapping unchanged. The higher‑levelupdateMessage/deleteMessageentry points retain their semantics.
572-621: getMessageWithSerial now uses executeAblyRequest with unchanged decoding behaviorThe
getMessageWithSerialpath is now routed throughexecuteAblyRequest, but still:
- wraps transport errors into
ARTErrorInfo(respectingaddRequestIds), and- on HTTP 200, decodes a single
ARTMessagethen re‑decodes its data viadataEncoder.No regressions are apparent in the control flow.
Source/ARTPushDeviceRegistrations.m (1)
94-117: executeAblyRequest migration for push device registrations is consistentAll device registration CRUD operations now call
executeAblyRequestwithARTAuthenticationOn, while preserving existing request construction, status‑code checks (200/204), decoding, and error‑to‑ARTErrorInfomapping. This keeps behavior aligned with the refactored REST stack.Also applies to: 143-168, 215-230, 261-274
Source/ARTPaginatedResult.m (1)
147-183: executePaginated now uses executeAblyRequest with auth; callback threading preserved
executePaginatednow routes throughrest executeAblyRequestwithARTAuthenticationOn, centralizing Ably‑specific handling (auth, retries, error shaping) for all paginated resources.first/nextstill wrap the callback to hop onto_userQueue, so caller‑visible threading remains the same. This looks correct assuming all paginated endpoints in use are authenticated Ably API calls, which matches current call sites (channel history, push/device lists, etc.).Test/AblyTests/Tests/RealtimeClientConnectionTests.swift (1)
5354-5354: Tests correctly migrated toexecuteAblyRequestSwitching these helpers to
client.internal.rest.executeAblyRequest(..., withAuthOption: .on, wrapperSDKAgents: nil, ...)keeps the semantics of the original REST calls while exercising the new internal pipeline. The changes are consistent and look good.Also applies to: 5380-5380, 5444-5444, 5511-5511
Source/ARTRest.m (1)
318-341:executeExternalRequestcleanly isolates non-Ably HTTP callsThe new
executeExternalRequest:completion:method provides a straightforward, Ably-agnostic path for arbitraryNSURLRequests:
- Uses
httpExecutordirectly, bypassing Ably-specific headers, token renewal, and fallback logic.- Maps
statusCode >= 400to anARTErrorInfo(if no error already present) using the raw body as a string.This clear separation between external and Ably-internal requests is appropriate and should make callers’ intent and behavior much easier to reason about.
ffb92af to
9d6fd3f
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
Test/AblyTests/Tests/AuthTests.swift (1)
593-605: Fixdescription()call – usedescriptionproperty instead
ARTErrorInfo/NSErrorexposesdescriptionas a property in Swift, not a method.XCTAssertTrue(errorInfo.description().contains(...))will not compile; use the property instead.- XCTAssertTrue(errorInfo.description().contains("Invalid request: body param is required")) // see https://github.com/ably/ably-cocoa/issues/2135 + XCTAssertTrue(errorInfo.description.contains("Invalid request: body param is required")) // see https://github.com/ably/ably-cocoa/issues/2135The subsequent
errorInfo.messageassertion can remain as is.
🧹 Nitpick comments (3)
Source/ARTPushActivationStateMachine.m (2)
192-214: Minor: log uses wrong error variable on decode failureIn the decode‑failure branch you log
error.localizedDescription, buterroris the network error parameter; the failure at this point isdecodeError.if (decodeError != nil) { ARTLogError(self->_logger, @"%@: decode identity token details failed (%@)", NSStringFromClass(self.class), - error.localizedDescription); + decodeError.localizedDescription); [self sendEvent:[ARTPushActivationEventGettingDeviceRegistrationFailed newWithError:[ARTErrorInfo createFromNSError:decodeError]]]; return; }This keeps the emitted error unchanged while making the log accurate.
363-383: Consider guarding againstresponse == nilin deregistration error pathThe deregistration completion handler assumes that when
erroris non‑nil,responseis also non‑nil:if (error) { // ... if (response.statusCode == 401 || error.code == 40005) { ...If
executeAblyRequestever calls this completion with a transport‑level error (e.g. network failure) andresponse == nil, this would dereference a nil response. Today this may be prevented by the internal contract, but adding a defensive check would make this code more robust:if (error) { const BOOL unauthorized = (response && response.statusCode == 401) || error.code == 40005; if (unauthorized) { ... } else { ... } return; }Behaviour for existing 401 / 40005 cases would remain unchanged.
Source/ARTRest.m (1)
318-341: Suggest usingart_shortStringfor consistency and safety.The error message construction at line 327 converts the full response data to a string without truncation. For consistency with
handleAblyRestRequestCompletion(line 396) and to prevent potentially large error messages, consider usingart_shortString:if (response.statusCode >= 400) { if (!error) { + NSString *plain = [[NSString alloc] initWithData:data ?: [NSData data] encoding:NSUTF8StringEncoding]; // Return error with HTTP StatusCode if ARTErrorStatusCode does not exist error = [ARTErrorInfo createWithCode:response.statusCode * 100 status:response.statusCode - message:[[NSString alloc] initWithData:data ?: [NSData data] encoding:NSUTF8StringEncoding]]; + message:[plain art_shortString]]; } }
📜 Review details
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Disabled knowledge base sources:
- Jira integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (16)
Source/ARTAuth.m(3 hunks)Source/ARTHTTPPaginatedResponse.m(1 hunks)Source/ARTPaginatedResult.m(1 hunks)Source/ARTPushActivationStateMachine.m(4 hunks)Source/ARTPushAdmin.m(1 hunks)Source/ARTPushChannel.m(4 hunks)Source/ARTPushChannelSubscriptions.m(2 hunks)Source/ARTPushDeviceRegistrations.m(4 hunks)Source/ARTRest.m(6 hunks)Source/ARTRestAnnotations.m(1 hunks)Source/ARTRestChannel.m(4 hunks)Source/PrivateHeaders/Ably/ARTRest+Private.h(1 hunks)Test/AblyTests/Tests/AuthTests.swift(1 hunks)Test/AblyTests/Tests/RealtimeClientConnectionTests.swift(4 hunks)Test/AblyTests/Tests/RestClientTests.swift(1 hunks)Test/AblyTests/Tests/UtilitiesTests.swift(2 hunks)
🚧 Files skipped from review as they are similar to previous changes (8)
- Source/ARTPushAdmin.m
- Source/ARTRestChannel.m
- Source/ARTPushChannelSubscriptions.m
- Source/ARTRestAnnotations.m
- Test/AblyTests/Tests/RealtimeClientConnectionTests.swift
- Source/ARTPushDeviceRegistrations.m
- Test/AblyTests/Tests/RestClientTests.swift
- Source/PrivateHeaders/Ably/ARTRest+Private.h
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-12-10T14:14:47.456Z
Learnt from: lawrence-forooghian
Repo: ably/ably-cocoa PR: 2162
File: Source/ARTJsonLikeEncoder.m:522-525
Timestamp: 2025-12-10T14:14:47.456Z
Learning: In Source/ARTJsonLikeEncoder.m, ensure that artMessage.actionIsInternallySet is only set to YES when the SDK explicitly sets the action field (e.g., in ARTMessage initWithName:data:). Messages decoded from the wire must not have actionIsInternallySet set, even if they contain an action field, because the flag should distinguish SDK-internal actions from wire-provided actions. Apply this behavior broadly to Objective-C files in the Source directory that involve message encoding/decoding, and add tests to verify that decoding does not flip the flag.
Applied to files:
Source/ARTPushActivationStateMachine.mSource/ARTHTTPPaginatedResponse.mSource/ARTAuth.mSource/ARTPushChannel.mSource/ARTPaginatedResult.mSource/ARTRest.m
🧬 Code graph analysis (1)
Test/AblyTests/Tests/UtilitiesTests.swift (1)
Test/AblyTests/Tests/MessageUpdatesDeletesTests.swift (1)
rest(41-49)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (6)
- GitHub Check: check (iOS, test_iOS18_4)
- GitHub Check: check (tvOS, test_tvOS18_4)
- GitHub Check: check (macOS, test_macOS)
- GitHub Check: check
- GitHub Check: build
- GitHub Check: check
🔇 Additional comments (11)
Source/ARTAuth.m (3)
259-275: Per‑request timeout for authUrl token requests looks correctUsing
_options.httpRequestTimeouton the auth URLNSMutableURLRequestaligns these token requests with the client-level HTTP timeout; no functional issues spotted.
369-382: ConfirmexecuteExternalRequestcontract matcheshandleAuthUrlResponseexpectationsSwitching authUrl token fetching to
_rest executeExternalRequest:completion:makes sense for non‑Ably endpoints, but this code assumes:
responseanddataare still provided for non‑2xx HTTP statuses, and- HTTP errors are not converted into an
ARTErrorInfovia theerrorparameter and short‑circuited beforehandleAuthUrlResponseruns.If
executeExternalRequestwere to start surfacing HTTP errors only viaerror(with empty/omitteddata), JSON/plain‑text token responses on error paths would no longer be decodable here. Please double‑check the new implementation’s contract.
493-525: Token request now goes throughexecuteAblyRequest– call site looks soundRouting
/keys/{keyName}/requestTokenvia_rest executeAblyRequest:… withAuthOption:ARTAuthenticationOffkeeps the previous auth option and centralises Ably error handling; the completion still decodesARTTokenDetailsfrom the configured encoder with the same behaviour as before.Test/AblyTests/Tests/UtilitiesTests.swift (1)
246-305: Tests correctly updated to useexecuteAblyRequestfor REST error handlingBoth tests now invoke
rest.internal.executeAblyRequest(_:wrapperSDKAgents:completion:)against the simulated error responses, keeping the same URL, executor wiring, and ARTErrorInfo assertions. This keeps the tests aligned with the new internal REST surface without changing their semantics.Source/ARTHTTPPaginatedResponse.m (1)
89-128: EnsureexecuteAblyRequestpreserves the expected error contract for paginated responses
executePaginatedassumes that:
erroris only non‑nil for transport/client errors (e.g. network issues), and- Ably HTTP error responses (non‑2xx with Ably error headers/body) are not surfaced via
errorbut instead viaresponse/data.That’s why it only wraps non‑Ably errors:
if (error && ![error.domain isEqualToString:ARTAblyErrorDomain]) { callback(nil, [ARTErrorInfo createFromNSError:error]); return; }and then calls:
NSArray *items = error ? @[] : responseProcessor(response, data, &decodeError); ... callback(result, nil);after constructing the
ARTHTTPPaginatedResponse.With the switch to
executeAblyRequest, please confirm that this contract is still honoured; otherwise Ably HTTP errors could end up being hidden (non‑nilerrorwith Ably domain, emptyitems, andcallback(result, nil)).Source/ARTPushChannel.m (1)
106-245: Push channel subscribe/unsubscribe now correctly useexecuteAblyRequestAll four push channel subscription endpoints (device/client subscribe/unsubscribe) now go through
_rest executeAblyRequest:… withAuthOption:ARTAuthenticationOn, still:
- constructing the same requests (paths, methods, bodies, headers),
- logging failures, and
- wrapping any
NSErrorintoARTErrorInfofor the user callback on_userQueue.This keeps the public behaviour intact while aligning these calls with the new centralised Ably request pipeline.
Source/ARTPaginatedResult.m (1)
147-150: LGTM! Correct authentication option for paginated requests.The change from
executeRequesttoexecuteAblyRequestwithARTAuthenticationOncorrectly ensures paginated requests are authenticated, which is the expected behavior for Ably API requests.Source/ARTRest.m (4)
247-316: LGTM! Authentication routing correctly refactored.The authentication routing logic correctly handles the different authentication modes (Off, On, NewToken, TokenRetry, UseBasic) and properly reuses valid tokens before requesting new ones. The refactoring maintains the existing authentication behavior while improving code organization.
368-476: LGTM! Excellent centralization of completion logic.The new
handleAblyRestRequestCompletionmethod effectively centralizes:
- Content-type validation (380-400): Correctly constructs artificial errors for invalid content-types using
art_shortString, addressing the PR objective for handling non-JSON error responses- Token renewal (408-416): Properly implements single-retry token refresh logic
- Fallback retry (441-463): Correctly implements host fallback with proper request cloning using
mutableCopy- Error wrapping (464-474): Ensures non-ARTErrorInfo errors are wrapped with requestId for traceability
The refactoring improves maintainability by consolidating error handling logic that was previously scattered.
481-544: LGTM! Request preparation and execution correctly implemented.The method properly:
- Configures request headers (Accept, Ably-Agent, X-Ably-ClientId, etc.)
- Handles
request_idquery parameter, correctly reusingoriginalRequestIdfor fallback attempts (line 505)- Manages fallback retry timeout expiration (516-522)
- Delegates completion handling to
handleAblyRestRequestCompletionThe implementation correctly centralizes request preparation and execution logic.
616-616: LGTM! Time method correctly updated to new API.The time method now uses
executeAblyRequestwithARTAuthenticationOff, which is correct since time requests don't require authentication.
…' authUrl). Rename executeRequest to executeAblyRequest for better readability. Refactor private executeRequest to decrease its size.
9d6fd3f to
9546629
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
🧹 Nitpick comments (2)
Test/AblyTests/Tests/UtilitiesTests.swift (2)
262-262: Replace unused closure parameter with_.The
responseparameter in the completion handler is not used and should be replaced with_following Swift best practices:🔎 Proposed fix
- rest.internal.executeAblyRequest(request, wrapperSDKAgents:nil, completion: { response, _, error in + rest.internal.executeAblyRequest(request, wrapperSDKAgents:nil, completion: { _, _, error in
297-297: Replace unused closure parameter with_.The
responseparameter in the completion handler is not used and should be replaced with_following Swift best practices:🔎 Proposed fix
- rest.internal.executeAblyRequest(request, wrapperSDKAgents:nil, completion: { response, _, error in + rest.internal.executeAblyRequest(request, wrapperSDKAgents:nil, completion: { _, _, error in
📜 Review details
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Disabled knowledge base sources:
- Jira integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (16)
Source/ARTAuth.mSource/ARTHTTPPaginatedResponse.mSource/ARTPaginatedResult.mSource/ARTPushActivationStateMachine.mSource/ARTPushAdmin.mSource/ARTPushChannel.mSource/ARTPushChannelSubscriptions.mSource/ARTPushDeviceRegistrations.mSource/ARTRest.mSource/ARTRestAnnotations.mSource/ARTRestChannel.mSource/PrivateHeaders/Ably/ARTRest+Private.hTest/AblyTests/Tests/AuthTests.swiftTest/AblyTests/Tests/RealtimeClientConnectionTests.swiftTest/AblyTests/Tests/RestClientTests.swiftTest/AblyTests/Tests/UtilitiesTests.swift
🚧 Files skipped from review as they are similar to previous changes (8)
- Source/ARTRestAnnotations.m
- Source/ARTPushChannelSubscriptions.m
- Test/AblyTests/Tests/RestClientTests.swift
- Source/ARTPushActivationStateMachine.m
- Source/ARTRestChannel.m
- Test/AblyTests/Tests/RealtimeClientConnectionTests.swift
- Source/ARTPushChannel.m
- Source/ARTPushDeviceRegistrations.m
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-12-10T14:14:47.456Z
Learnt from: lawrence-forooghian
Repo: ably/ably-cocoa PR: 2162
File: Source/ARTJsonLikeEncoder.m:522-525
Timestamp: 2025-12-10T14:14:47.456Z
Learning: In Source/ARTJsonLikeEncoder.m, ensure that artMessage.actionIsInternallySet is only set to YES when the SDK explicitly sets the action field (e.g., in ARTMessage initWithName:data:). Messages decoded from the wire must not have actionIsInternallySet set, even if they contain an action field, because the flag should distinguish SDK-internal actions from wire-provided actions. Apply this behavior broadly to Objective-C files in the Source directory that involve message encoding/decoding, and add tests to verify that decoding does not flip the flag.
Applied to files:
Source/ARTPushAdmin.mSource/ARTHTTPPaginatedResponse.mSource/ARTPaginatedResult.mSource/ARTAuth.mSource/ARTRest.m
🪛 SwiftLint (0.57.0)
Test/AblyTests/Tests/UtilitiesTests.swift
[Warning] 262-262: Unused parameter in a closure should be replaced with _
(unused_closure_parameter)
[Warning] 297-297: Unused parameter in a closure should be replaced with _
(unused_closure_parameter)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (6)
- GitHub Check: build
- GitHub Check: check
- GitHub Check: check (macOS, test_macOS)
- GitHub Check: check
- GitHub Check: check (tvOS, test_tvOS18_4)
- GitHub Check: check (iOS, test_iOS18_4)
🔇 Additional comments (16)
Test/AblyTests/Tests/AuthTests.swift (1)
595-595: LGTM! Error description validation aligns with PR objectives.The assertion correctly verifies that error descriptions contain the expected message when authUrl requests fail. This addresses the issue referenced (#2135) about handling JSON error responses properly. The combination of checking both
description()(line 595) andmessage(line 604) ensures complete error information is populated.Source/ARTPushAdmin.m (1)
87-87: LGTM! Correct method rename for Ably API call.The rename from
executeRequesttoexecuteAblyRequestcorrectly distinguishes this as an Ably API invocation (push publish endpoint) rather than an external request. The parameters and control flow remain unchanged.Source/ARTHTTPPaginatedResponse.m (1)
96-96: LGTM! Pagination request correctly uses Ably-specific path.The method rename appropriately routes paginated Ably API requests through
executeAblyRequest, maintaining consistency with the refactored execution flow.Source/ARTAuth.m (3)
263-263: Good addition: Per-request timeout support.Setting the timeout interval from client options enables per-request timeout control, which aligns with the PR's stated objectives. This applies to authUrl requests built by this method.
374-374: Correct distinction: External request for authUrl.The switch to
executeExternalRequestis appropriate here sinceauthUrlcan point to any external authentication server (not Ably's API). This architectural separation improves clarity.
512-512: LGTM! Token request correctly uses Ably API path.The rename to
executeAblyRequestis correct for token requests, which target Ably's/keys/{keyName}/requestTokenendpoint. The authentication option parameter is properly maintained.Source/ARTPaginatedResult.m (1)
150-150: LGTM! Consistent rename for paginated Ably requests.The method rename correctly routes paginated result requests through
executeAblyRequest, maintaining consistency with the refactored execution architecture.Source/PrivateHeaders/Ably/ARTRest+Private.h (1)
74-86: Excellent API evolution: Clear separation of concerns.The new API surface provides good architectural improvements:
executeExternalRequest: For non-Ably requests (e.g., authUrl), with a simpler signature using immutableNSURLRequestexecuteAblyRequestoverloads: For Ably API calls, with variants that allow full control over authentication or use defaultsThis separation makes the API more self-documenting and reduces the chance of misconfiguration (e.g., accidentally applying authentication to external URLs or vice versa).
Source/ARTRest.m (8)
247-268: LGTM! Clear authentication routing.The authentication option routing logic correctly delegates to the appropriate execution paths, with proper token retry counter management.
270-316: LGTM! Robust authentication flow.The authentication methods correctly handle both Basic and Token auth, with proper token reuse logic and new token acquisition when needed.
318-341: LGTM! Appropriate handling for external requests.The simplified error handling is correct for external requests — no request ID tracking, no token renewal, and no fallback retries, which is appropriate since these aren't Ably API calls.
343-347: LGTM!Clean entry point that delegates to the internal execution method with initial state.
363-476: LGTM! Robust centralized completion handling.This completion handler correctly implements:
- Content-type validation with appropriate fallback error creation
- Error decoding with proper handling of decode failures (addresses the PR objective of handling non-dictionary JSON error responses)
- Token renewal with single-retry limit
- Fallback host retry logic with proper host switching and retry counting
- Consistent error wrapping to ensure
requestIdpropagationThe error handling chain (decode → decodeError → raw fallback) ensures that even malformed JSON responses are handled gracefully.
478-523: LGTM! Comprehensive request preparation.The request preparation logic correctly sets all necessary headers and handles request ID generation, fallback retry timeout expiration per RSC15f, and allows header overrides where appropriate.
527-544: Verify cancellation behavior for retry tasks.The task returned to the caller is the initial HTTP request task. If a retry occurs (line 538-540), the retry task is assigned to the local
taskvariable, but this happens asynchronously after the method has already returned. This means:
- The caller holds a reference to the original request task only
- Canceling the original task won't cancel any retry tasks initiated by the completion handler
- Retry tasks will continue even if the caller attempts to cancel the operation
Is this the intended behavior, or should cancellation propagate to retry attempts? If propagation is desired, a parent cancellable wrapper managing all retry tasks would be needed.
592-632: LGTM! Correctly integrated with refactored execution path.The
timeWithWrapperSDKAgentsmethod correctly uses the newexecuteAblyRequestAPI withARTAuthenticationOff, and error handling remains appropriate.
Closes #2135
Summary by CodeRabbit
Chores
Bug Fixes
Tests
✏️ Tip: You can customize this high-level summary in your review settings.