Skip to content

Report TLS/certificate failures distinctly instead of a generic network error#23

Merged
fabiodalez-dev merged 2 commits into
mainfrom
fix/distinguish-tls-errors
Jul 8, 2026
Merged

Report TLS/certificate failures distinctly instead of a generic network error#23
fabiodalez-dev merged 2 commits into
mainfrom
fix/distinguish-tls-errors

Conversation

@fabiodalez-dev

@fabiodalez-dev fabiodalez-dev commented Jul 8, 2026

Copy link
Copy Markdown
Owner

Motivated by issue #16: the app shows "Couldn't reach that address. Check the URL and your connection." while a browser on the same phone reaches the same instance over HTTPS and gets a valid /api/v1 response. The screenshots make it clear the server is fine — so the message sends the user chasing the wrong thing.

Root cause

SSLException (and its subtypes SSLHandshakeException / SSLPeerUnverifiedException) extends IOException, so a TLS/certificate handshake failure was caught by the generic IOException branch and mapped to ErrorCodes.NETWORK → the "check your connection" copy. A certificate problem (e.g. a chain that terminates in an expired root, which Android's system trust store rejects even though Chrome — using its own root store — reaches the same host fine) therefore looked like a connectivity problem.

Change

  • New ErrorCodes.TLS; catch SSLException before IOException in apiCall / apiResponse (ApiResult.kt) and in bookClubCall / bookClubCallUnit (BookClubResult.kt).
  • Map it to a certificate-specific message in OnboardingViewModel and LoginViewModel, with strings for all four locales (en/it/de/fr) via the i18n/*.json source of truth.

No app-side trust is relaxed — the bad certificate is still rejected. It's only reported accurately, so onboarding now says it's a certificate/TLS issue and to check the server certificate, instead of blaming the network.

Validation

I could not build locally (no Android SDK in my environment), so the Android CI on this PR (assembleDebug + assembleRelease + lintDebug + unit tests) is the gate. The i18n keys were added to all four locales so the GenerateI18nResTask codegen produces R.string.onboarding_error_tls / R.string.login_error_tls.

Summary by CodeRabbit

  • Bug Fixes

    • Migliorata la gestione degli errori di connessione sicura: i problemi TLS/certificato ora mostrano un messaggio dedicato invece di essere trattati come errore di rete generico.
    • Aggiornati login e onboarding per visualizzare un avviso più chiaro quando la connessione HTTPS non è considerata attendibile.
  • Documentation

    • Aggiunte nuove traduzioni per questi messaggi di errore in più lingue.

…r connection"

SSLException is a subtype of IOException, so a TLS/certificate handshake failure
was being caught by the generic IOException branch and mapped to ErrorCodes.NETWORK
-> "Couldn't reach that address. Check the URL and your connection." That sends the
user chasing DNS/Wi-Fi when the real cause is the server's certificate (e.g. a chain
that terminates in an expired root, which Android's system trust store rejects even
though a browser with its own root store reaches the same host fine).

- Add ErrorCodes.TLS and catch SSLException BEFORE IOException in apiCall/apiResponse
  (ApiResult) and in bookClubCall/bookClubCallUnit (BookClubResult).
- Map the new code to a certificate-specific message in the onboarding and login
  view models, with strings for all four locales (en/it/de/fr).

Now the onboarding "Connect your library" step tells the user it's a certificate/TLS
problem and to check the server certificate — no app-side trust is relaxed, the bad
cert is still rejected, it's just reported accurately.
@coderabbitai

coderabbitai Bot commented Jul 8, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@fabiodalez-dev, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 41 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: ef6b281e-6c11-4547-9165-a51f1a728b07

📥 Commits

Reviewing files that changed from the base of the PR and between 5ecc96e and feda639.

📒 Files selected for processing (1)
  • app/src/main/java/com/pinakes/app/ui/screens/onboarding/OnboardingViewModel.kt

Walkthrough

Viene introdotto un nuovo codice errore ErrorCodes.TLS per distinguere gli errori di certificato/TLS dagli errori di rete generici. ApiResult.kt e BookClubResult.kt catturano SSLException separatamente da IOException, mappando su Failure(ErrorCodes.TLS, ...). LoginViewModel e OnboardingViewModel gestiscono il nuovo codice mostrando stringhe dedicate, aggiunte in de/en/fr/it.json.

Changes

Gestione errori TLS

Layer / File(s) Summary
Nuovo codice errore TLS nei wrapper di rete
app/src/main/java/com/pinakes/app/data/network/ApiResult.kt, app/src/main/java/com/pinakes/app/data/network/BookClubResult.kt
Definito ErrorCodes.TLS = "tls" e aggiunta cattura esplicita di SSLException in apiCall, apiResponse, bookClubCall, bookClubCallUnit, prima della catch generica IOException, con httpStatus = 0.
Mapping errore TLS negli stati UI
app/src/main/java/com/pinakes/app/ui/screens/login/LoginViewModel.kt, app/src/main/java/com/pinakes/app/ui/screens/onboarding/OnboardingViewModel.kt
applyError in entrambi i ViewModel riconosce il codice "tls" e imposta errorRes su login_error_tls o onboarding_error_tls, azzerando error/errorArg.
Stringhe di localizzazione per errori TLS
i18n/de.json, i18n/en.json, i18n/fr.json, i18n/it.json
Aggiunte le chiavi login_error_tls e onboarding_error_tls nel blocco esistente di messaggi di errore login/onboarding.

Estimated code review effort: 2 (Simple) | ~10 minutes

Sequence Diagram(s)

sequenceDiagram
  participant ViewModel
  participant ApiResult
  participant Retrofit
  ViewModel->>ApiResult: apiCall / bookClubCall
  ApiResult->>Retrofit: esegue richiesta HTTPS
  Retrofit-->>ApiResult: SSLException
  ApiResult-->>ViewModel: Failure(ErrorCodes.TLS, message, 0)
  ViewModel->>ViewModel: applyError mostra login_error_tls / onboarding_error_tls
Loading

Poem

Un coniglietto guarda il certificato,
se non è fidato, l'errore è marcato 🐰
TLS distinto, non più confuso col rete,
quattro lingue parlano, chiare e complete.
Salto di gioia tra le stringhe json! 🥕

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed Il titolo descrive correttamente il cambio principale: distinguere gli errori TLS/certificato dall'errore di rete generico.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/distinguish-tls-errors

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
app/src/main/java/com/pinakes/app/ui/screens/onboarding/OnboardingViewModel.kt (1)

72-79: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Considerare l'uso di ErrorCodes.TLS al posto del letterale "tls".

OnboardingViewModel.applyError utilizza stringhe letterali ("network", "tls", "not_found") mentre LoginViewModel utilizza le costanti di ErrorCodes. Questa inconsistenza cross-file aumenta il rischio di typo silenziosi: una stringa mal scritta non genererebbe errori di compilazione. Si raccomanda di allineare questo file allo stesso pattern di LoginViewModel usando le costanti di ErrorCodes.

♻️ Refactor proposto per allineamento con LoginViewModel
 private fun applyError(state: OnboardingUiState, failure: ApiResult.Failure): OnboardingUiState = when (failure.code) {
-        "network" -> state.copy(error = null, errorRes = R.string.onboarding_error_network)
-        "tls" -> state.copy(error = null, errorRes = R.string.onboarding_error_tls)
-        "not_found" -> state.copy(error = null, errorRes = R.string.onboarding_error_not_found)
+        ErrorCodes.NETWORK -> state.copy(error = null, errorRes = R.string.onboarding_error_network)
+        ErrorCodes.TLS -> state.copy(error = null, errorRes = R.string.onboarding_error_tls)
+        ErrorCodes.NOT_FOUND -> state.copy(error = null, errorRes = R.string.onboarding_error_not_found)
         else ->
             if (failure.message.isNotBlank()) state.copy(error = failure.message, errorRes = null)
             else state.copy(error = null, errorRes = R.string.onboarding_error_generic)
     }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@app/src/main/java/com/pinakes/app/ui/screens/onboarding/OnboardingViewModel.kt`
around lines 72 - 79, OnboardingViewModel.applyError is still matching failure
codes with string literals instead of the shared ErrorCodes constants, which
risks silent typos and inconsistent handling. Update the when branch in
applyError to use the same ErrorCodes symbols as LoginViewModel (for example the
TLS case and the other code checks) so the mapping stays consistent and
compiler-safe across files.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In
`@app/src/main/java/com/pinakes/app/ui/screens/onboarding/OnboardingViewModel.kt`:
- Around line 72-79: OnboardingViewModel.applyError is still matching failure
codes with string literals instead of the shared ErrorCodes constants, which
risks silent typos and inconsistent handling. Update the when branch in
applyError to use the same ErrorCodes symbols as LoginViewModel (for example the
TLS case and the other code checks) so the mapping stays consistent and
compiler-safe across files.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 89921645-4677-457b-85e3-9b06050405d3

📥 Commits

Reviewing files that changed from the base of the PR and between d1fdab7 and 5ecc96e.

📒 Files selected for processing (8)
  • app/src/main/java/com/pinakes/app/data/network/ApiResult.kt
  • app/src/main/java/com/pinakes/app/data/network/BookClubResult.kt
  • app/src/main/java/com/pinakes/app/ui/screens/login/LoginViewModel.kt
  • app/src/main/java/com/pinakes/app/ui/screens/onboarding/OnboardingViewModel.kt
  • i18n/de.json
  • i18n/en.json
  • i18n/fr.json
  • i18n/it.json

…t string literals

Align applyError() with LoginViewModel — use ErrorCodes.NETWORK/TLS/NOT_FOUND
instead of "network"/"tls"/"not_found" so a typo is a compile error, not a
silently unmatched branch.
@fabiodalez-dev fabiodalez-dev merged commit b0aa8d7 into main Jul 8, 2026
1 of 2 checks passed
@fabiodalez-dev fabiodalez-dev deleted the fix/distinguish-tls-errors branch July 8, 2026 14:27
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant