Skip to main content

sqlite_graphrag/embedding_api/
error.rs

1//! Embedding failure type carrying the retry verdict from its origin.
2
3use crate::errors::AppError;
4use crate::retry::AttemptOutcome;
5
6/// [`crate::embedding_api::OpenRouterClient::embed_single`] / [`crate::embedding_api::OpenRouterClient::embed_batch`]
7/// failure (reauditor addendum, mirrors [`crate::chat_api::ChatError`]).
8///
9/// `retry_class` is the retry verdict computed AT THE ORIGIN (the exact HTTP
10/// status, or the provider's structured error `code`) via the same
11/// `openrouter_http::status_retry_class` /
12/// `openrouter_http::provider_error_retry_class` classifiers (private helpers)
13/// [`crate::chat_api::OpenRouterChatClient`] uses (GAP-SG-74 DRY) — never
14/// inferred downstream from `source.to_string()`. The enrich `re-embed`
15/// consumer reads this field directly instead of pattern-matching the
16/// formatted message.
17#[derive(Debug)]
18pub struct EmbedError {
19    /// Underlying cause, preserved via `source()` rather than restated.
20    pub source: AppError,
21    /// Typed retry verdict computed where the failure originated (HTTP
22    /// status / provider code), not by matching `source`'s message.
23    pub retry_class: AttemptOutcome,
24}
25
26impl EmbedError {
27    pub(super) fn new(source: AppError, retry_class: AttemptOutcome) -> Self {
28        Self {
29            source,
30            retry_class,
31        }
32    }
33}
34
35impl std::fmt::Display for EmbedError {
36    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
37        std::fmt::Display::fmt(&self.source, f)
38    }
39}
40
41impl std::error::Error for EmbedError {
42    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
43        Some(&self.source)
44    }
45}
46
47/// Converts a bare `AppError` into an `EmbedError` with `retry_class:
48/// HardFailure`. Used by the `?` operator on call sites that predate the
49/// origin-typed classification (the GAP-SG-02 oversized-input guard, the
50/// dimension-mismatch guard in `OpenRouterClient::truncate_embedding`, and
51/// the batch-size-mismatch check) — all of those are genuine permanent
52/// client/config errors, never transient. Every `EmbedError` constructed
53/// inside `execute_with_retry` uses `EmbedError::new` explicitly with a
54/// retry verdict computed at the exact HTTP status / provider code instead.
55impl From<AppError> for EmbedError {
56    fn from(source: AppError) -> Self {
57        Self::new(source, AttemptOutcome::HardFailure)
58    }
59}
60
61/// Unwraps `EmbedError` back down to its `source`, discarding `retry_class`.
62/// Lets the many pre-existing `?`-based callers of [`crate::embedding_api::OpenRouterClient::embed_single`]
63/// / [`crate::embedding_api::OpenRouterClient::embed_batch`] (in [`crate::embedder`]) keep compiling
64/// unchanged; callers that need the typed retry verdict (the enrich
65/// `re-embed` path) should match on `EmbedError` directly instead of relying
66/// on this conversion.
67impl From<EmbedError> for AppError {
68    fn from(err: EmbedError) -> Self {
69        err.source
70    }
71}