Skip to main content

unparser_shared/ai/
error.rs

1//! The failure reasons this module's two entry points can return.
2//!
3//! Classification here stays purely diagnostic. This crate has no C ABI boundary of
4//! its own — `unpdf`/`undoc`/`unhwp` each classify their own `ErrorKind` at the point
5//! that knows the reason, so a caller wiring [`super::understand_image`] or
6//! [`super::refine_markdown`] into its own error type maps from this enum, or (per the
7//! fallback policy that governs the `unpdf` wiring) collapses every variant into a
8//! single failure counter. Either way, the variants below stay specific — collapsing
9//! them is the caller's decision to make, not this module's to make for it.
10
11use std::fmt;
12
13/// Why a call to [`super::understand_image`] or [`super::refine_markdown`] failed.
14#[derive(Debug, thiserror::Error)]
15#[non_exhaustive]
16pub enum Error {
17    /// The request never reached the endpoint, or the connection dropped before a
18    /// response arrived — DNS, connect, TLS, or read/write failure.
19    #[error("transport failure reaching the AI endpoint: {0}")]
20    Transport(String),
21
22    /// The endpoint responded, but with a non-success HTTP status.
23    #[error("AI endpoint responded with status {code}: {body}")]
24    Status {
25        /// The HTTP status code.
26        code: u16,
27        /// The response body, for diagnostics.
28        body: String,
29    },
30
31    /// The response was truncated (`finish_reason == "length"`) even after every
32    /// retry. Never treated as success — a truncated table or JSON body is silently
33    /// wrong, not partially right.
34    #[error("response was truncated (finish_reason=length) after all retries")]
35    Truncated,
36
37    /// The response body was not the shape this call expects — malformed JSON, or
38    /// valid JSON that does not match the schema the prompt asked for.
39    #[error("response did not match the expected schema: {0}")]
40    MalformedResponse(String),
41
42    /// Every attempt failed (transport failure or truncation) up to `max_retries`.
43    #[error("all {attempts} attempt(s) failed; last error: {last_error}")]
44    RetriesExhausted {
45        /// How many attempts were made.
46        attempts: u32,
47        /// The error from the last attempt.
48        last_error: Box<Error>,
49    },
50}
51
52impl Error {
53    pub(crate) fn transport(err: impl fmt::Display) -> Self {
54        Self::Transport(err.to_string())
55    }
56}