fix(ios): improve ListChannelsDec decoder to handle unexpected response formats#709
fix(ios): improve ListChannelsDec decoder to handle unexpected response formats#709dhruvkej9 wants to merge 1 commit intoCap-go:mainfrom
Conversation
…se formats Fixes Cap-go#706 - listChannels() was failing with 'Response could not be decoded because of error: The data couldn't be read because it isn't in the correct format.' The original decoder used singleValueContainer() as primary path, then fell back to a keyed container for error responses. However, if the backend returned a wrapped format ({ "channels": [...] }) or any format not strictly a JSON array, the single value decode would fail, then the keyed container decode would also fail silently, resulting in a confusing decode error being surfaced to the user. Changes: - Handle direct array format (primary: backend returns [] directly) - Handle wrapped object format ({ "channels": [...] }) - Handle error response format ({ "error": "..." }) - Return a descriptive error string instead of throwing when no format matches, preventing the cryptic iOS decode error from propagating to the user
📝 WalkthroughWalkthroughModified Changes
Estimated code review effort🎯 2 (Simple) | ⏱️ ~10 minutes Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Tip Try Coding Plans. Let us write the prompt for your AI agent so you can ship faster (with fewer bugs). 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 |
|
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@ios/Sources/CapacitorUpdaterPlugin/InternalUtils.swift`:
- Around line 94-107: When a keyed container decodes but neither the expected
channels array nor the "error" string is present, set a descriptive fallback
error instead of leaving self.error nil; in the initializer that calls
decoder.container(keyedBy: CodingKeys.self) (the branch handling channels /
error for ChannelInfo), after attempting
keyedContainer.decode([ChannelInfo].self, forKey: .channels) and
keyedContainer.decode(String.self, forKey: .error), detect the case where both
self.channels and self.error are nil and assign a clear message (e.g. "Response
could not be decoded: unexpected keyed payload") to self.error so unsupported
keyed payloads are surfaced to callers.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 28165344-687a-4d58-a877-c81ad26e9a7c
📒 Files selected for processing (1)
ios/Sources/CapacitorUpdaterPlugin/InternalUtils.swift
| if let keyedContainer = try? decoder.container(keyedBy: CodingKeys.self) { | ||
| // Try wrapped channels array format: { "channels": [...] } | ||
| if let channelsArray = try? keyedContainer.decode([ChannelInfo].self, forKey: .channels) { | ||
| self.channels = channelsArray | ||
| self.error = try? keyedContainer.decode(String.self, forKey: .error) | ||
| return | ||
| } | ||
| // Error response format: { "error": "..." } | ||
| self.channels = nil | ||
| self.error = try? keyedContainer.decode(String.self, forKey: .error) | ||
| } else { | ||
| // Handle error response | ||
| let errorContainer = try decoder.container(keyedBy: CodingKeys.self) | ||
| // Could not decode in any known format — return empty/nil gracefully | ||
| self.channels = nil | ||
| self.error = try? errorContainer.decode(String.self, forKey: .error) | ||
| self.error = "Response could not be decoded: unexpected format" |
There was a problem hiding this comment.
Return the descriptive fallback error for unsupported keyed payloads.
On Lines 94-107, an object like { "message": "..." } or { "channels": {} } still decodes to channels == nil and error == nil because every keyed decode is wrapped in try?. At the only use site in ios/Sources/CapacitorUpdaterPlugin/CapgoUpdater.swift:1739-1758, that is treated as a successful decode with no error surfaced, so the original decode failure becomes a silent empty result instead of the intended descriptive message.
Suggested fix
- if let keyedContainer = try? decoder.container(keyedBy: CodingKeys.self) {
- // Try wrapped channels array format: { "channels": [...] }
- if let channelsArray = try? keyedContainer.decode([ChannelInfo].self, forKey: .channels) {
- self.channels = channelsArray
- self.error = try? keyedContainer.decode(String.self, forKey: .error)
- return
- }
- // Error response format: { "error": "..." }
- self.channels = nil
- self.error = try? keyedContainer.decode(String.self, forKey: .error)
+ if let keyedContainer = try? decoder.container(keyedBy: CodingKeys.self) {
+ // Try wrapped channels array format: { "channels": [...] }
+ if let channelsArray = try? keyedContainer.decode([ChannelInfo].self, forKey: .channels) {
+ self.channels = channelsArray
+ self.error = try? keyedContainer.decode(String.self, forKey: .error)
+ return
+ }
+
+ // Error response format: { "error": "..." }
+ if let serverError = try? keyedContainer.decode(String.self, forKey: .error) {
+ self.channels = nil
+ self.error = serverError
+ return
+ }
+
+ self.channels = nil
+ self.error = "Response could not be decoded: unexpected format"
} else {
// Could not decode in any known format — return empty/nil gracefully
self.channels = nil
self.error = "Response could not be decoded: unexpected format"
}🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@ios/Sources/CapacitorUpdaterPlugin/InternalUtils.swift` around lines 94 -
107, When a keyed container decodes but neither the expected channels array nor
the "error" string is present, set a descriptive fallback error instead of
leaving self.error nil; in the initializer that calls decoder.container(keyedBy:
CodingKeys.self) (the branch handling channels / error for ChannelInfo), after
attempting keyedContainer.decode([ChannelInfo].self, forKey: .channels) and
keyedContainer.decode(String.self, forKey: .error), detect the case where both
self.channels and self.error are nil and assign a clear message (e.g. "Response
could not be decoded: unexpected keyed payload") to self.error so unsupported
keyed payloads are surfaced to callers.



Problem
Fixes #706 -
listChannels()was throwing a confusing error on iOS:Root Cause
The original
ListChannelsDeccustom decoder usedsingleValueContainer()as the primary decode path, then fell back to a keyed container for error responses.When the backend returns a response in a format that is not strictly a raw JSON array (e.g. a wrapped object
{ "channels": [...] }or an error object with unexpected keys), the decode chain fails silently at both stages — causing Swift's Codable to surface a cryptic format error instead of a useful message.Fix
Restructured the
init(from:)decoder to handle three distinct response shapes:[...]— primary backend format{ "channels": [...] }— handles potential future/wrapped formats{ "error": "..." }— properly surfaces server errorsAs a safety net, when none of the known formats match, a descriptive error string is returned instead of propagating Swift's cryptic decode error.
Testing
/claim #706
Summary by CodeRabbit
Bug Fixes