1use std::path::PathBuf;
2
3#[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
134impl 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}