fix: show readable error messages when feed subscription fails - #1325
fix: show readable error messages when feed subscription fails#1325mlkgrnt wants to merge 2 commits into
Conversation
When importing a feed fails, ReadYou previously surfaced the raw exception message in the subscribe dialog, e.g. "SSLHandshakeException: connection closed" for hosts that abort the TLS handshake, or "Unable to detect RSS feed URL" without any hint of the HTTP status. Such messages are confusing for end users and gave no clue whether the problem was the network, the server, or the URL itself. Instead of throwing bare IOExceptions, classify the failure and present a short, localized message: - RssHelper now throws FeedHttpException (carrying the HTTP status code) and FeedNotFoundException instead of raw IOExceptions. - A new toSubscribeError() mapping turns these, along with network exceptions (SSL/DNS/connect/timeout) and parse exceptions, into localized string resources shown in the subscribe dialog. Anything unexpected falls back to the original message or a generic hint. - Strings are added in values/ and values-zh-rCN/. Adds unit tests for the error mapping and for parsing an Atom document in the shape served by statuspage-style hosts (an RSS URL that redirects to an Atom feed with content type application/atom+xml).
ibrahim-iqbal
left a comment
There was a problem hiding this comment.
Nice structure — the sealed SubscribeError with a Text / Raw split makes the fallback path explicit without swallowing the original message, and the Throwable.toSubscribeError() extension keeps the ViewModel readable.
A few things I noticed while reading through:
toSubscribeError() mapping
- The
SSLExceptionbranch catchesSSLHandshakeException,SSLPeerUnverifiedException, andSSLProtocolException(all subclasses), which is good for the ICP/geo-blocking case called out in the description. ConnectException+SocketTimeoutExceptioncover the common connectivity failures.NoRouteToHostException(a differentSocketExceptionsubclass) will not match and will fall through toSubscribeError.Raw. Not necessarily a bug — the raw message from that one is usually already legible — but worth a note in case it's meant to be part of the "network" bucket.
FeedHttpException / FeedNotFoundException
- Both extend
IOException, so any caller further up that was already catchingIOExceptionkeeps working. Good. FeedHttpExceptioncarries the status code, which makes it easy to specialize the string later (e.g.subscribe_error_http_not_foundfor 404s), if you want that as a follow-up.
Strings
subscribe_error_fallbackis only referenced fromSubscribeError.Rawwhenmessageis blank — the resource is present in bothvalues/strings.xmlandvalues-zh-rCN/strings.xml, so the fallback path renders in both locales.
The test additions (both the parser fixture and the mapper table) exercise the meaningful branches. LGTM overall.
|
Thanks for the careful read, @ibrahim-iqbal — and you were right about On the |
|
Nice — |
ibrahim-iqbal
left a comment
There was a problem hiding this comment.
Confirmed. NoRouteToHostException in the network bucket in SubscribeErrors.kt plus the new row in unreachable host maps to network message covers the geo-blocking / ICP scenario from the PR description — that was the last edge case I was worried about.
Agreed on keeping the per-status-code specialization out of this PR. FeedHttpException already carries the code and subscribe_error_http (HTTP %1$d) surfaces it, which is enough for a readable-fallback first cut. Happy to see a dedicated 404 (and 401/403) string land in a follow-up.
LGTM.
PR: fix: show readable error messages when feed subscription fails
Problem
When a feed cannot be imported in the subscribe dialog, ReadYou surfaces the raw exception message to the user. Real-world failures therefore look like:
SSLHandshakeException: connection closed— e.g.status.deepseek.comfrom a mainland-China network whose exit route cannot complete the TLS handshakeUnable to detect RSS feed URL— no hint of the HTTP status behind the failureFailed to connect to xxx.com/...— indistinguishable network/HTTP/parse causesThese messages are meaningless to end users and give no clue whether the problem is the network, the server, or the URL itself. This matches long-standing user complaints about "cannot import feed with no clear reason" (e.g. #394 and similar).
Root cause of the investigated case (
status.deepseek.com/feed.rss)Investigated end-to-end on an emulator with the real app:
status.deepseek.comis hosted on Atlassian Statuspage; its/feed.rssalways 302-redirects to/history.atom(a standard Atom 1.0 document,application/atom+xml; charset=utf-8). Reproduced with githubstatus.com as a control.SubscribeViewModel.searchFeed()→RssHelper.searchFeed()→ OkHttp (followRedirects = true) → ROMESyndFeedInput— handles this shape correctly: verified by driving the realsearchFeed()against a local mock statuspage server (redirect → Atom) in instrumentation tests, all green.SSLHandshakeException: connection closed, reproduced on-device. Follow-up on a physical device confirmed this is network-reachability dependent: the domain (hosted overseas on Atlassian Statuspage) is reachable when the exit route is "clean" but the TLS handshake is terminated on some mainland-China exit paths (direct connection through certain ISPs, or through shared/flagged proxy nodes). The feed itself is healthy — it imports fine once a working route is used.Conclusion: ReadYou has no parsing/redirect bug for statuspage-style feeds — the pipeline handles the RSS→Atom redirect shape end to end. The user-visible defect is that reachability failures of this kind are reported as a raw, unreadable exception string (
SSLHandshakeException: connection closed) with no actionable hint, which is what this PR fixes.Fix
Classify subscription failures and present a short, localized message instead of the raw exception.
RssHelper.ktIOException:FeedHttpException(message, statusCode)— the server answered with a non-success HTTP code (both for the direct feed response and the HTML-discovery response).FeedNotFoundException— the URL is not a feed and no feed could be discovered on the page.SubscribeErrors.kt(new)Throwable.toSubscribeError(): SubscribeError:FeedHttpException→ localized "server rejected this URL (HTTP %1$d)"FeedNotFoundException→ localized "no RSS/Atom feed found at this URL"SSLException/UnknownHostException/ConnectException/SocketTimeoutException→ localized "couldn't connect to the server"SAXParseException/ ROMEFeedException→ localized "content could not be parsed"SubscribeViewModel.kt.onFailurenow maps the throwable throughtoSubscribeError()and resolves the string resource (with format args) via the existingandroidStringsHelper, instead of copyingit.message.Resources
values/strings.xml+values-zh-rCN/strings.xml: 5 new strings (subscribe_error_network/http/feed_not_found/parse/fallback).Tests
SubscribeErrorsTest(JVM): covers every branch oftoSubscribeError().StatuspageAtomParsingTest(JVM): proves ROME parses an Atom document in the exact shape statuspage hosts serve (RSS URL → Atom redirect,application/atom+xml; charset=utf-8); fixture uses a fictional domain.RssHelper.searchFeed()through a 302→Atom mock, an HTML-discovery page, and a direct Atom URL — all green on the emulator../gradlew :app:testGithubReleaseUnitTest→ BUILD SUCCESSFUL.Notes for maintainers
SubscribeError.Rawfallback already preserves it for unexpected error types.