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}