From d50a7173ad948bc81ca28573e14b4bc649de3728 Mon Sep 17 00:00:00 2001 From: Matthew Jackson <1085847+MattJackson@users.noreply.github.com> Date: Fri, 31 Jul 2026 14:46:39 -0700 Subject: [PATCH] Expose error_code so consumers can read a code instead of parsing one MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit io_error_code was private, so the predicates built on it (is_halt, is_skippable_title_stub, is_disc_level_no_key) were the only way to ask anything about an io::Error's origin. A consumer that needs the code itself — to report WHY a title failed rather than to branch on one of three known cases — had no route to it: mux_stream returns an io::Error, the typed Error is gone by then, and only the E string prefix survives. That left every front-end to re-implement the prefix parse by hand, which is precisely the string-matching 1.5.x spent its time removing. One parser, exported. No behaviour change: the function is unchanged and the three predicates still call it. --- src/error.rs | 16 ++++++++++++---- src/io/writeback_file/macos.rs | 2 +- src/lib.rs | 4 +++- 3 files changed, 16 insertions(+), 6 deletions(-) diff --git a/src/error.rs b/src/error.rs index b5cb636..65a8d03 100644 --- a/src/error.rs +++ b/src/error.rs @@ -1055,13 +1055,21 @@ pub type Result = std::result::Result; /// The numeric error code carried by an [`io::Error`](std::io::Error) that was /// produced from an [`Error`], or `None` if it carries none. /// +/// Public because consumers need the code itself, not just the yes/no +/// predicates built on it below. `mux_stream` hands back an `io::Error`, and a +/// front-end reporting *why* a title failed had no way to recover the code +/// from it — the typed `Error` is gone by then and only the `E` string +/// prefix survives. Parsing that prefix is this function's job; every consumer +/// re-implementing the parse is how the string-matching this crate spent 1.5.x +/// removing comes back. +/// /// [`From for io::Error`] is the ONLY path from a typed [`Error`] to an /// `io::Error` in this crate, and it stringifies (`io::Error::new(kind, msg)` /// where `msg` is the `Error`'s `E[: …]` [`Display`](std::fmt::Display) /// string) rather than boxing the typed value — no code path constructs an /// `io::Error` that still holds a `crate::error::Error` via `get_ref`. So the /// only recognised shape is the round-tripped `E` message prefix. -fn io_error_code(e: &std::io::Error) -> Option { +pub fn error_code(e: &std::io::Error) -> Option { // Round-tripped: `From for io::Error` stringifies as "E[: …]". let s = e.to_string(); let digits = s.strip_prefix('E')?; @@ -1097,7 +1105,7 @@ fn io_error_code(e: &std::io::Error) -> Option { /// all of them and exited successfully. It is [`is_disc_level_no_key`]'s, and /// fatal here. pub fn is_skippable_title_stub(e: &std::io::Error) -> bool { - matches!(io_error_code(e), Some(E_MKV_INVALID | E_CSS_KEY_MISSING)) + matches!(error_code(e), Some(E_MKV_INVALID | E_CSS_KEY_MISSING)) } /// Whether an [`io::Error`](std::io::Error) is a cooperative user stop @@ -1106,7 +1114,7 @@ pub fn is_skippable_title_stub(e: &std::io::Error) -> bool { /// `completed = false`, and consumers preserve staging rather than quarantining. /// Typed replacement for the consumers' `E`-leading-token string match. pub fn is_halt(e: &std::io::Error) -> bool { - io_error_code(e) == Some(E_HALTED) + error_code(e) == Some(E_HALTED) } /// Whether an [`io::Error`](std::io::Error) is a **disc-level** key failure — @@ -1126,7 +1134,7 @@ pub fn is_halt(e: &std::io::Error) -> bool { /// "empty stub" notice and exited successfully. pub fn is_disc_level_no_key(e: &std::io::Error) -> bool { matches!( - io_error_code(e), + error_code(e), Some(E_NO_DISC_KEY | E_KEYDB_LOAD | E_AACS_NO_KEYS | E_CSS_NO_DISC_KEY) ) } diff --git a/src/io/writeback_file/macos.rs b/src/io/writeback_file/macos.rs index ba3f570..d5f0ae1 100644 --- a/src/io/writeback_file/macos.rs +++ b/src/io/writeback_file/macos.rs @@ -217,7 +217,7 @@ mod tests { // The three arms must be DISTINGUISHABLE, not merely non-Ok. Each // carries its own numeric code through the "E" prefix that - // `From for io::Error` mints — the only shape `io_error_code` + // `From for io::Error` mints — the only shape `error_code` // recognises. A bare `ErrorKind` cannot be classified, which is how a // user cancel here used to read as a hard I/O failure. let lost = bounded_failure_to_result(BoundedError::WorkerLost) diff --git a/src/lib.rs b/src/lib.rs index fcc1148..0c06d04 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -156,7 +156,9 @@ pub use session::{ // All fallible APIs return `Result`. `Error` is a typed enum with a // numeric `code()`; **no English text in the library** — applications map // codes to localized messages. See `error.rs` for the full taxonomy. -pub use error::{Error, Result, is_disc_level_no_key, is_halt, is_skippable_title_stub}; +pub use error::{ + Error, Result, error_code, is_disc_level_no_key, is_halt, is_skippable_title_stub, +}; // ─── Cooperative cancellation ─────────────────────────────────────────────── //