-
Notifications
You must be signed in to change notification settings - Fork 31
[ECO-5633] Avoid parsing an http body error if response is not from Ably #2143
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
WalkthroughThis PR enhances error handling across REST and Realtime components by introducing HTTP-status-aware error construction, supporting underlying error propagation, and improving validation of JSON error responses to handle non-dictionary formats gracefully. Changes
Sequence DiagramsequenceDiagram
participant REST as REST Client
participant Server as HTTP Server
participant Decoder as JSON Decoder
participant ErrorInfo as ErrorInfo Factory
rect rgb(220, 240, 220)
note over REST,ErrorInfo: New: Error Response Validation Flow
REST->>Server: HTTP Request
Server-->>REST: HTTP Error Response (JSON)
rect rgb(240, 240, 255)
note over REST: isAblyResponse() + hasValidContentType()
REST->>REST: Validate Ably headers & Content-Type
end
alt Valid Ably Response
REST->>Decoder: Decode error info
alt Valid dictionary
Decoder-->>ErrorInfo: error dict
ErrorInfo->>ErrorInfo: createWithCode:status:message:underlyingError:
ErrorInfo-->>REST: ARTErrorInfo with status & underlying error
else Non-dictionary error
Decoder-->>ErrorInfo: ARTClientCodeErrorInvalidType
ErrorInfo-->>REST: 0 status + decode failure message
end
else Invalid/Non-Ably Response
REST->>REST: Handle as plaintext/HTML error
end
end
rect rgb(240, 220, 220)
note over REST,ErrorInfo: Realtime: Connection Error Handling
REST->>REST: Auth timeout or token error
rect rgb(240, 240, 255)
note over REST: Check auth provider type
end
alt Auth via provider
REST->>ErrorInfo: ARTHttpStatusCodeFromErrorCode()
ErrorInfo-->>REST: HTTP status from error code
REST->>ErrorInfo: createWithCode:status:message:underlyingError:
else Token auth error
REST->>ErrorInfo: Map ARTErrorForbidden/Unauthorized
ErrorInfo-->>REST: Appropriate HTTP status + underlying error
end
ErrorInfo-->>REST: Enhanced ARTErrorInfo
end
Estimated code review effort🎯 4 (Complex) | ⏱️ ~50 minutes
Poem
Pre-merge checks and finishing touches✅ Passed checks (5 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
Warning Review ran into problems🔥 ProblemsGit: Failed to clone repository. Please run the Tip 📝 Customizable high-level summaries are now available in beta!You can now customize how CodeRabbit generates the high-level summary in your pull requests — including its content, structure, tone, and formatting.
Example instruction:
Note: This feature is currently in beta for Pro-tier users, and pricing will be announced later. 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 |
35e72fd to
ac815c8
Compare
ac815c8 to
a82edc5
Compare
a82edc5 to
db5027a
Compare
db5027a to
93ae406
Compare
93ae406 to
b490ec6
Compare
b490ec6 to
3615757
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)
Source/ARTJsonLikeEncoder.m (1)
1123-1137: Guard against nilerrorpointer indecodeErrorInfoThe new branch correctly maps non-dictionary (or missing)
errorpayloads to anARTClientCodeErrorInvalidType, which is what the new tests expect. However, it unconditionally writes through*error, which will crash if the caller passeserror == NULL.Safer pattern:
- // We expect `decodedError` as a dictionary from Ably REST API - // In case it's something else we set the decoding error - *error = [ARTErrorInfo createWithCode:ARTClientCodeErrorInvalidType - status:0 - message:[NSString stringWithFormat:@"Failed to decode error dictionary from the provided error data."]]; + // We expect `decodedError` as a dictionary from the Ably REST API. + // If it's missing or has the wrong type, surface a decoding error. + if (error) { + *error = [ARTErrorInfo createWithCode:ARTClientCodeErrorInvalidType + status:0 + message:@"Failed to decode error dictionary from the provided error data."]; + }This keeps the new behavior but avoids relying on every caller always passing a non-null error pointer.
🧹 Nitpick comments (5)
Test/AblyTests/Tests/UtilitiesTests.swift (1)
242-271: New invalid-type REST HTTP error test looks correctThis test cleanly verifies the new behavior when the response body doesn’t contain a proper
errordictionary and asserts theARTClientCodeError.invalidTypemapping and message. This matches the new decoder semantics and guards against mis-parsing non-Ably-style error payloads. Only minor nit: the method name has a small grammar issue (doesnt_contains) if you ever want to tidy it.Source/ARTRealtime.m (1)
1196-1219: Improved token auth error modeling and underlying error propagationThe updated
handleTokenAuthError:path for custom auth providers is a solid improvement:
- Forbidden (
ARTErrorForbidden) errors are mapped toARTErrorAuthConfiguredProviderFailurewith an HTTP-like 403 status and the underlying NSError attached.- Other auth-provider errors are mapped similarly but with a 401-like status, with behavior correctly split between:
- Connected state (
connection.errorReasonupdated, RSA4c3), and- Other states (transition via
performTransitionToDisconnectedOrSuspendedWithParams, RSA4c1/c2).This should make provider failures easier to diagnose without changing the public API surface.
Source/ARTRest.m (3)
338-363: Helper methods for Ably-response and content-type detection look good, with a minor robustness nitThe new helpers cleanly encapsulate key decisions:
isAblyResponse:infers Ably responses by checking for any header name containing"-ably", which is a simple and effective heuristic given the existingX-Ably-*headers.hasValidContentType:checks the responseContent-Typeagainst the known encoder MIME types.isCustomAuthRequest:centralizes theauthUrl-host check for later guards.One minor robustness improvement you might consider: normalize
Content-Typeto lowercase beforecontainsString:to cope with servers that emit mixed-case types like"Application/JSON; charset=utf-8":- NSString *contentType = [response.allHeaderFields objectForKey:@"Content-Type"]; + NSString *contentType = [[response.allHeaderFields objectForKey:@"Content-Type"] lowercaseString]; @@ - for (NSString *mimeType in [self->_encoders.allValues valueForKeyPath:@"mimeType"]) { - if ([contentType containsString:mimeType]) { + for (NSString *mimeType in [[self->_encoders.allValues valueForKeyPath:@"mimeType"] valueForKey:@"lowercaseString"]) { + if (contentType && [contentType containsString:mimeType]) { return true; } }Not strictly required, but it would make
hasValidContentType:more tolerant of non-standard casing.
416-461: HTTP error handling now correctly distinguishes Ably vs non-Ably responsesThe updated
executeRequest:...completion:logic for error responses is a solid improvement:
- For non-auth requests with bodies and unknown content types, it continues to synthesize an
ARTErrorInfofrom the plaintext/HTML body, but now guarded against customauthUrlrequests viaisCustomAuthRequest:.- For
statusCode >= 400, it:
- Attempts to decode an Ably error only when an encoder is available and
isAblyResponse:is true, so arbitrary third-party JSON (e.g.{"error": "string"}) is no longer misinterpreted as an Ably error.- Uses
dataErrorfrom the encoder when present, otherwise falls back todecodeError(e.g. invalid-type decoding issues) or a generic, body-derived artificial error when decoding fails.- Only calls
shouldRenewToken:whendataErrorcomes from a bona fide Ably error, avoiding spurious token renewal attempts on non-Ably endpoints.This addresses the ECO‑5633 scenario while preserving existing retry/fallback behavior.
799-865: Device access serialization viadeviceAccessQueueis a nice concurrency improvementOn iOS, introducing
deviceAccessQueueand routingdevice_nosync/sharedDevice_onlyCallOnDeviceAccessQueuethrough it is a good step towards eliminating races on the shared device state and persisted storage. The pattern (single static queue + “needs loading” flag) looks correct and avoids nested dispatches on the same queue.Just ensure any new call sites that touch the shared device go through these helpers rather than accessing the static directly.
📜 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 (8)
Source/ARTJsonLikeEncoder.m(1 hunks)Source/ARTRealtime.m(3 hunks)Source/ARTRest.m(4 hunks)Source/ARTStatus.m(3 hunks)Source/PrivateHeaders/Ably/ARTErrorInfo+Private.h(1 hunks)Source/include/Ably/ARTStatus.h(2 hunks)Test/AblyTests/Tests/AuthTests.swift(1 hunks)Test/AblyTests/Tests/UtilitiesTests.swift(2 hunks)
🔇 Additional comments (12)
Test/AblyTests/Tests/AuthTests.swift (1)
884-884: Use a controlled test endpoint instead of an external API.The test relies on
https://api.restful-api.dev/objects/{UUID}to generate a 404 response. This external, uncontrolled endpoint could become unavailable, change behavior, or cause test flakiness in CI environments.Similar tests in this file (e.g., line 529) use
https://echo.ably.io/respondwith?status=404which provides a controlled, deterministic way to simulate HTTP errors.Consider changing line 884 to:
- options.authUrl = URL(string: "https://api.restful-api.dev/objects/\(UUID())")! // random address to get 404 + options.authUrl = URL(string: "https://echo.ably.io/respondwith?status=404")!Test/AblyTests/Tests/UtilitiesTests.swift (1)
273-306: Happy-path REST HTTP error decoding test is well-aligned with decoder behaviorThis test correctly exercises the case where an Ably-style
errordictionary is present under theerrorkey and assertscode,statusCode, andmessage. It gives good coverage for the updateddecodeErrorInfoand ARTRest error construction flow.Source/PrivateHeaders/Ably/ARTErrorInfo+Private.h (1)
15-17: HTTP-status mapping helper is reasonable and well-scopedThe
ARTHttpStatusCodeFromErrorCodehelper and its comment are clear and match how it’s used (e.g. mapping 40100/40300 to 401/403). Keeping it in a private header is appropriate given the “no validation” contract.Source/ARTRealtime.m (1)
1071-1079: Timeout error differentiation for custom auth vs generic timeouts looks soundThe new
onConnectionTimeOutlogic correctly:
- Treats timeouts while authorizing via
authUrl/authCallbackasARTErrorAuthConfiguredProviderFailurewith an HTTP-like 401 status (viaARTHttpStatusCodeFromErrorCode(ARTErrorUnauthorized)), and- Falls back to
ARTErrorConnectionTimedOutwithstatus:ARTStateConnectionFailedotherwise.This aligns Realtime behavior with the auth-provider-specific RSA4c expectations while preserving existing semantics for non-auth timeouts.
Source/include/Ably/ARTStatus.h (1)
234-236: Underlying-error-capable ARTErrorInfo factories are a good additionThe new
createWithCode:status:message:underlyingError:and extended variant withrequestId/additionalUserInfogive a clear path to propagate underlyingNSErrors while keeping Ably-specific metadata. This aligns well with the new usages in Realtime/REST.Since these are declared in the public header (even if
:nodoc:), just double-check that:
- The corresponding implementations exist in
ARTStatus.m, and- All call sites that need an underlying error are migrated to these overloads (so we avoid continuing to wrap and lose the original error).
Also applies to: 256-256
Source/ARTStatus.m (7)
24-34: LGTM! Consistent status code derivation.All factory methods now consistently use
ARTHttpStatusCodeFromErrorCodeto derive the HTTP status code from the error code. This ensures uniform behavior across different error creation paths.
36-38: LGTM! New factory method supports error chaining.This new factory method enables creating errors with an underlying error, which is useful for wrapping lower-level errors (such as JSON parsing failures) in Ably-specific error objects. The method properly delegates to the main factory with appropriate nil defaults for optional parameters.
40-50: LGTM! Factory methods properly updated with underlyingError support.All factory methods now properly chain to the main factory method with the
underlyingErrorparameter (defaulting to nil when not explicitly provided). This maintains backward compatibility while enabling error chaining throughout the codebase.
52-65: LGTM! Underlying error properly stored using standard iOS pattern.The main factory method now accepts and stores the
underlyingErrorparameter usingNSUnderlyingErrorKey, which is the standard iOS pattern for error chaining. The implementation correctly handles nil values (the key simply won't be added to userInfo), and the existingcauseproperty (lines 126-132) is already compatible with this approach.
138-144: LGTM! Minor formatting improvements.The formatting changes in the description method improve code readability without affecting functionality. The error description output remains unchanged.
191-193: LGTM! Minor formatting update.Cosmetic formatting change to the private setter with no functional impact.
18-20: LGTM! Canonical status code mapping function introduced.The function correctly derives HTTP status codes from error codes using integer division by 100. This assumes error codes follow the convention where the first digits represent the HTTP status (e.g., 40000-40099 → 400).
Please verify that
ARTHttpStatusCodeFromErrorCodeis properly declared in the public or private header file (likelyARTErrorInfo+Private.has mentioned in the summary).
Closes #2135
Summary by CodeRabbit
Bug Fixes
Tests