Skip to main content

seyk_core/
error.rs

1use std::path::PathBuf;
2
3/// Errors produced by the search engine and its components.
4///
5/// Non-exhaustive: new failure kinds can be added without breaking matches.
6#[derive(Debug, thiserror::Error)]
7#[non_exhaustive]
8pub enum Error {
9    #[error(
10        "refusing secret file or directory: {path} (built-in deny policy cannot be overridden)"
11    )]
12    SecretFile { path: PathBuf },
13
14    #[error("STT model {reason}; run `seyk setup audio`")]
15    SttModel { reason: &'static str },
16    #[error("speech-to-text failed: {reason}")]
17    SpeechToText { reason: &'static str },
18    #[error("audio extraction interval exceeds the 30-second request limit")]
19    AudioIntervalTooLong,
20
21    #[error("resource changed while reading: {resource}")]
22    ChangedWhileReading { resource: crate::ResourceId },
23    #[error("unsupported audio for {resource}: {reason}")]
24    UnsupportedAudio {
25        resource: crate::ResourceId,
26        reason: &'static str,
27    },
28    #[error("PDF rendering failed for {resource}: {reason}")]
29    PdfRender {
30        resource: crate::ResourceId,
31        reason: &'static str,
32    },
33    #[error(
34        "readable text for scanned pages needs OCR, which is not available yet ({resource}, page {page})"
35    )]
36    PdfOcrUnavailable {
37        resource: crate::ResourceId,
38        page: u32,
39    },
40    #[error("PDF parse failed for {resource}: {reason}")]
41    PdfParse {
42        resource: crate::ResourceId,
43        reason: &'static str,
44    },
45    #[error("PDF parse failed for {resource}: {reason}")]
46    PdfPageDecode {
47        resource: crate::ResourceId,
48        reason: &'static str,
49    },
50    #[error("PDF parser panicked for {resource}: {reason}")]
51    ParserPanicked {
52        resource: crate::ResourceId,
53        reason: &'static str,
54    },
55    #[error("I/O error at {path}: {source}")]
56    Io {
57        path: PathBuf,
58        #[source]
59        source: std::io::Error,
60    },
61
62    #[error(
63        "cache changes committed, but scrub is pending; run seyk cache scrub once other readers close (using the same cache path), or rerun the one-shot command; cache clear is a fallback"
64    )]
65    CacheCleanupPending,
66
67    #[error("cache error: {0}")]
68    Cache(#[from] rusqlite::Error),
69
70    #[error("embedding backend error: {0}")]
71    Embedding(String),
72
73    #[error("model files not found: {0}")]
74    ModelNotFound(String),
75
76    #[error("{modality} resources are not supported by this embedder")]
77    UnsupportedModality { modality: crate::Modality },
78
79    #[error("task {task:?} is not supported for {modality}")]
80    UnsupportedTask {
81        task: seyk_embed::EmbeddingTask,
82        modality: crate::Modality,
83    },
84
85    #[error("resource changed since search: {resource} (expected {expected}, got {actual})")]
86    StaleResource {
87        resource: crate::ResourceId,
88        expected: String,
89        actual: String,
90    },
91
92    #[error("extraction is unsupported for {modality} resource {resource} at {locator:?}{note}", note = if *modality == crate::Modality::Audio { "; configure an audio extractor (feature `audio-stt`)" } else { "" })]
93    UnsupportedExtraction {
94        resource: crate::ResourceId,
95        modality: crate::Modality,
96        locator: crate::ResourceLocator,
97    },
98
99    #[error("invalid locator for {resource}: {reason}")]
100    InvalidLocator {
101        resource: crate::ResourceId,
102        locator: crate::ResourceLocator,
103        reason: &'static str,
104    },
105
106    #[error("resource {resource} is not valid UTF-8: {source}")]
107    InvalidText {
108        resource: crate::ResourceId,
109        #[source]
110        source: std::str::Utf8Error,
111    },
112
113    #[error("dimension mismatch: expected {expected}, got {actual}")]
114    DimensionMismatch { expected: usize, actual: usize },
115
116    #[error("invalid configuration: {0}")]
117    Config(String),
118
119    #[error("invalid glob pattern: {0}")]
120    Glob(#[from] globset::Error),
121}
122
123impl Error {
124    pub(crate) fn io(path: impl Into<PathBuf>, source: std::io::Error) -> Self {
125        Error::Io {
126            path: path.into(),
127            source,
128        }
129    }
130}
131
132pub type Result<T, E = Error> = std::result::Result<T, E>;
133
134// Preserve the established search error variants for downstream matches.
135impl From<seyk_embed::Error> for Error {
136    fn from(error: seyk_embed::Error) -> Self {
137        match error {
138            seyk_embed::Error::Io { path, source } => Self::Io { path, source },
139            seyk_embed::Error::Embedding(message) => Self::Embedding(message),
140            seyk_embed::Error::ModelNotFound(message) => Self::ModelNotFound(message),
141            seyk_embed::Error::UnsupportedModality { modality } => {
142                Self::UnsupportedModality { modality }
143            }
144            seyk_embed::Error::DimensionMismatch { expected, actual } => {
145                Self::DimensionMismatch { expected, actual }
146            }
147            seyk_embed::Error::Config(message) => Self::Config(message),
148            seyk_embed::Error::UnsupportedTask { task, modality } => {
149                Self::UnsupportedTask { task, modality }
150            }
151            other => Self::Embedding(other.to_string()),
152        }
153    }
154}