atproto blogging
1use std::path::PathBuf;
2
3use miette::Diagnostic;
4use thiserror::Error;
5
6/// Top-level error type for weaver-index operations
7#[derive(Debug, Error, Diagnostic)]
8pub enum IndexError {
9 #[error(transparent)]
10 #[diagnostic(transparent)]
11 ClickHouse(#[from] ClickHouseError),
12
13 #[error(transparent)]
14 #[diagnostic(transparent)]
15 Firehose(#[from] FirehoseError),
16
17 #[error(transparent)]
18 #[diagnostic(transparent)]
19 Car(#[from] CarError),
20
21 #[error(transparent)]
22 #[diagnostic(transparent)]
23 Config(#[from] ConfigError),
24
25 #[error(transparent)]
26 #[diagnostic(transparent)]
27 Server(#[from] ServerError),
28
29 #[error(transparent)]
30 #[diagnostic(transparent)]
31 Sqlite(#[from] SqliteError),
32
33 #[error("resource not found: {resource}")]
34 #[diagnostic(code(index::not_found))]
35 NotFound { resource: String },
36}
37
38/// HTTP server errors
39#[derive(Debug, Error, Diagnostic)]
40pub enum ServerError {
41 #[error("failed to bind to {addr}")]
42 #[diagnostic(code(server::bind))]
43 Bind {
44 addr: std::net::SocketAddr,
45 #[source]
46 source: std::io::Error,
47 },
48
49 #[error("server terminated unexpectedly")]
50 #[diagnostic(code(server::serve))]
51 Serve {
52 #[source]
53 source: std::io::Error,
54 },
55}
56
57/// SQLite shard errors
58#[derive(Debug, Error, Diagnostic)]
59pub enum SqliteError {
60 #[error("failed to open database at {}", path.display())]
61 #[diagnostic(code(sqlite::open))]
62 Open {
63 path: PathBuf,
64 #[source]
65 source: rusqlite::Error,
66 },
67
68 #[error("failed to create directory {}", path.display())]
69 #[diagnostic(code(sqlite::io))]
70 Io {
71 path: PathBuf,
72 #[source]
73 source: std::io::Error,
74 },
75
76 #[error("failed to set pragma {pragma}")]
77 #[diagnostic(code(sqlite::pragma))]
78 Pragma {
79 pragma: &'static str,
80 #[source]
81 source: rusqlite::Error,
82 },
83
84 #[error("migration failed: {message}")]
85 #[diagnostic(code(sqlite::migration))]
86 Migration { message: String },
87
88 #[error("query failed: {message}")]
89 #[diagnostic(code(sqlite::query))]
90 Query { message: String },
91
92 #[error("shard lock poisoned")]
93 #[diagnostic(code(sqlite::lock))]
94 LockPoisoned,
95}
96
97/// ClickHouse database errors
98#[derive(Debug, Error, Diagnostic)]
99pub enum ClickHouseError {
100 #[error("failed to connect to ClickHouse: {message}: {source}")]
101 #[diagnostic(code(clickhouse::connection))]
102 Connection {
103 message: String,
104 #[source]
105 source: clickhouse::error::Error,
106 },
107
108 #[error("ClickHouse query failed: {message}: {source}")]
109 #[diagnostic(code(clickhouse::query))]
110 Query {
111 message: String,
112 #[source]
113 source: clickhouse::error::Error,
114 },
115
116 #[error("failed to insert batch: {message}: {source}")]
117 #[diagnostic(code(clickhouse::insert))]
118 Insert {
119 message: String,
120 #[source]
121 source: clickhouse::error::Error,
122 },
123
124 #[error("schema migration failed: {message}")]
125 #[diagnostic(code(clickhouse::schema))]
126 Schema { message: String },
127}
128
129/// Firehose/subscription stream errors
130#[derive(Debug, Error, Diagnostic)]
131pub enum FirehoseError {
132 #[error("failed to connect to relay at {url}")]
133 #[diagnostic(code(firehose::connection))]
134 Connection { url: String, message: String },
135
136 #[error("websocket stream error: {message}")]
137 #[diagnostic(code(firehose::stream))]
138 Stream { message: String },
139
140 #[error("failed to parse event header")]
141 #[diagnostic(code(firehose::header))]
142 HeaderParse {
143 #[source]
144 source: ciborium::de::Error<std::io::Error>,
145 },
146
147 #[error("failed to decode event body: {event_type}")]
148 #[diagnostic(code(firehose::decode))]
149 BodyDecode { event_type: String, message: String },
150
151 #[error("unknown event type: {event_type}")]
152 #[diagnostic(code(firehose::unknown_event))]
153 UnknownEvent { event_type: String },
154}
155
156/// CAR file parsing errors
157#[derive(Debug, Error, Diagnostic)]
158pub enum CarError {
159 #[error("failed to parse CAR data")]
160 #[diagnostic(code(car::parse))]
161 Parse { message: String },
162
163 #[error("block not found for CID: {cid}")]
164 #[diagnostic(code(car::block_not_found))]
165 BlockNotFound { cid: String },
166
167 #[error("failed to decode record from block: {message}")]
168 #[diagnostic(code(car::record_decode))]
169 RecordDecode { message: String },
170}
171
172/// Configuration errors
173#[derive(Debug, Error, Diagnostic)]
174pub enum ConfigError {
175 #[error("missing required environment variable: {var}")]
176 #[diagnostic(
177 code(config::missing_env),
178 help("Set the {var} environment variable or add it to your .env file")
179 )]
180 MissingEnv { var: &'static str },
181
182 #[error("invalid configuration value for {field}: {message}")]
183 #[diagnostic(code(config::invalid))]
184 Invalid {
185 field: &'static str,
186 message: String,
187 },
188
189 #[error("failed to parse URL: {url}")]
190 #[diagnostic(code(config::url_parse))]
191 UrlParse { url: String, message: String },
192}
193
194pub type Result<T> = std::result::Result<T, IndexError>;