Skip to main content

hs_predict/
error.rs

1use thiserror::Error;
2
3/// All errors produced by `hs-predict`.
4#[derive(Debug, Error)]
5pub enum HsPredictError {
6    // ── Input validation ────────────────────────────────────────────
7    #[error("Invalid HS code format: '{0}' (expected 6-digit number, e.g. \"281511\")")]
8    InvalidHsCode(String),
9
10    #[error("Invalid CAS number format: '{0}' (expected digits-digits-digit, e.g. \"1310-73-2\")")]
11    InvalidCasNumber(String),
12
13    #[error("At least one identifier (cas, smiles, iupac_name, or inchi) must be provided")]
14    MissingIdentifier,
15
16    #[error("Mixture component #{index} is missing an identifier")]
17    MissingComponentIdentifier { index: usize },
18
19    // ── Session ─────────────────────────────────────────────────────
20    #[error("No active question — call start() or answer() first")]
21    NoActiveQuestion,
22
23    #[error("Answer type mismatch: expected {expected}, got {got}")]
24    AnswerTypeMismatch { expected: &'static str, got: &'static str },
25
26    #[error("Choice index {index} out of range (valid range: 0..={max})")]
27    InvalidChoiceIndex { index: usize, max: usize },
28
29    #[error("Number {value} is out of range [{min}, {max}]")]
30    NumberOutOfRange { value: f64, min: f64, max: f64 },
31
32    #[error("Session is already complete — create a new ClassificationSession")]
33    SessionAlreadyComplete,
34
35    // ── Pipeline / classification ───────────────────────────────────
36    #[error(
37        "Confidence {confidence:.2} is below threshold {threshold:.2} \
38         and no LLM client is configured"
39    )]
40    LowConfidenceNoLlm { confidence: f32, threshold: f32 },
41
42    #[error("Mixture classification depth limit exceeded (max 2 levels)")]
43    MixtureDepthExceeded,
44
45    // ── LLM ────────────────────────────────────────────────────────
46    #[error(
47        "LLM client is not configured. \
48         Enable the `llm` feature and supply an API key."
49    )]
50    LlmNotConfigured,
51
52    #[error("LLM API error {status_code}: {message}")]
53    LlmApiError { status_code: u16, message: String },
54
55    #[error("Failed to parse LLM response as JSON: {source}\nRaw response: {raw}")]
56    LlmResponseParseError {
57        #[source]
58        source: serde_json::Error,
59        raw: String,
60    },
61
62    #[error("LLM rate limit exceeded — retry after {retry_after_secs}s")]
63    LlmRateLimitError { retry_after_secs: u64 },
64
65    // ── Validation ──────────────────────────────────────────────────
66    #[error("LLM returned '{code}': not a valid 6-digit HS 2022 code")]
67    ValidationFailed { code: String },
68
69    #[error(
70        "LLM code '{llm_code}' conflicts with rule-engine chapter '{expected_chapter}'"
71    )]
72    ChapterConflict { llm_code: String, expected_chapter: String },
73
74    // ── PubChem ────────────────────────────────────────────────────
75    #[cfg(feature = "pubchem")]
76    #[error("PubChem error: {0}")]
77    PubChem(#[from] crate::pubchem::PubChemError),
78
79    // ── Generic ─────────────────────────────────────────────────────
80    #[error("Serialization error: {0}")]
81    Serialization(#[from] serde_json::Error),
82
83    #[error("HTTP error: {0}")]
84    Http(String),
85}
86
87pub type Result<T> = std::result::Result<T, HsPredictError>;