summaryrefslogtreecommitdiff
path: root/app/src/main/java/org/terst/feedreader/readability/ExtractionFailure.kt
diff options
context:
space:
mode:
Diffstat (limited to 'app/src/main/java/org/terst/feedreader/readability/ExtractionFailure.kt')
-rw-r--r--app/src/main/java/org/terst/feedreader/readability/ExtractionFailure.kt64
1 files changed, 64 insertions, 0 deletions
diff --git a/app/src/main/java/org/terst/feedreader/readability/ExtractionFailure.kt b/app/src/main/java/org/terst/feedreader/readability/ExtractionFailure.kt
new file mode 100644
index 0000000..532b109
--- /dev/null
+++ b/app/src/main/java/org/terst/feedreader/readability/ExtractionFailure.kt
@@ -0,0 +1,64 @@
+package org.terst.feedreader.readability
+
+/**
+ * Why an extraction did not produce article content.
+ *
+ * Extraction used to fail silently: ReaderScreen did `extracted?.content?.let { }`,
+ * so a null result — whatever caused it — left the reader showing the raw feed
+ * content with nothing logged and nothing shown. The only way a failure ever
+ * surfaced was the user noticing and filing a bug report by hand, which could
+ * not say why either.
+ */
+sealed interface ExtractionFailure {
+
+ /** The page load reported a transport-level error. */
+ data class LoadError(val code: Int, val description: String?) : ExtractionFailure
+
+ /** The server answered, but with an error status. */
+ data class HttpError(val statusCode: Int, val reason: String?) : ExtractionFailure
+
+ /** The page never finished loading within the budget. */
+ data class TimedOut(val millis: Long) : ExtractionFailure
+
+ /** Readability itself threw, or returned something unusable. */
+ data class ReadabilityError(val message: String) : ExtractionFailure
+
+ /** Readability ran but found no article body worth showing. */
+ data object NoContent : ExtractionFailure
+
+ /** The JS result could not be decoded. */
+ data class BadResult(val raw: String) : ExtractionFailure
+}
+
+private const val MILLIS_PER_SECOND = 1_000
+
+/**
+ * How much of the raw JS payload a diagnostic keeps. The result can be tens of
+ * kilobytes; a bug report needs enough to recognise the shape of it, not all.
+ */
+private const val RAW_DIAGNOSTIC_CHARS = 200
+
+/** A short, user-facing explanation. Shown in a snackbar and in bug reports. */
+fun ExtractionFailure.userMessage(): String = when (this) {
+ is ExtractionFailure.LoadError ->
+ "Couldn't load the page" + (description?.takeIf { it.isNotBlank() }?.let { " ($it)" } ?: "")
+ is ExtractionFailure.HttpError -> "Server returned HTTP $statusCode"
+ is ExtractionFailure.TimedOut -> "Page took longer than ${millis / MILLIS_PER_SECOND}s to load"
+ is ExtractionFailure.ReadabilityError -> "Couldn't parse the article ($message)"
+ ExtractionFailure.NoContent -> "No article text found on the page"
+ is ExtractionFailure.BadResult -> "Unexpected result from the page"
+}
+
+/**
+ * A longer form for the bug report, carrying the detail the short message drops.
+ * Bug reports are the only channel for a failure the user has already lost, so
+ * this keeps the raw specifics rather than the tidy summary.
+ */
+fun ExtractionFailure.diagnostic(): String = when (this) {
+ is ExtractionFailure.LoadError -> "LoadError code=$code description=${description ?: "none"}"
+ is ExtractionFailure.HttpError -> "HttpError status=$statusCode reason=${reason ?: "none"}"
+ is ExtractionFailure.TimedOut -> "TimedOut after ${millis}ms"
+ is ExtractionFailure.ReadabilityError -> "ReadabilityError $message"
+ ExtractionFailure.NoContent -> "NoContent (Readability returned no usable body)"
+ is ExtractionFailure.BadResult -> "BadResult raw=${raw.take(RAW_DIAGNOSTIC_CHARS)}"
+}