1#![allow(unused_assignments)]
5
6use miette::Diagnostic;
7use thiserror::Error;
8
9pub type SchemaResult<T> = Result<T, SchemaError>;
11
12#[derive(Error, Debug, Diagnostic)]
14pub enum SchemaError {
15 #[error("failed to read file: {path}")]
17 #[diagnostic(code(prax::schema::io_error))]
18 IoError {
19 path: String,
20 #[source]
21 source: std::io::Error,
22 },
23
24 #[error("syntax error in schema")]
26 #[diagnostic(code(prax::schema::syntax_error))]
27 SyntaxError {
28 #[source_code]
29 src: String,
30 #[label("error here")]
31 span: miette::SourceSpan,
32 message: String,
33 },
34
35 #[error("invalid model `{name}`: {message}")]
37 #[diagnostic(code(prax::schema::invalid_model))]
38 InvalidModel { name: String, message: String },
39
40 #[error("invalid field `{model}.{field}`: {message}")]
42 #[diagnostic(code(prax::schema::invalid_field))]
43 InvalidField {
44 model: String,
45 field: String,
46 message: String,
47 },
48
49 #[error("invalid relation `{model}.{field}`: {message}")]
51 #[diagnostic(code(prax::schema::invalid_relation))]
52 InvalidRelation {
53 model: String,
54 field: String,
55 message: String,
56 },
57
58 #[error("duplicate {kind} `{name}`")]
60 #[diagnostic(code(prax::schema::duplicate))]
61 Duplicate { kind: String, name: String },
62
63 #[error("unknown type `{type_name}` in `{model}.{field}`")]
65 #[diagnostic(code(prax::schema::unknown_type))]
66 UnknownType {
67 model: String,
68 field: String,
69 type_name: String,
70 },
71
72 #[error("invalid attribute `@{attribute}`: {message}")]
74 #[diagnostic(code(prax::schema::invalid_attribute))]
75 InvalidAttribute { attribute: String, message: String },
76
77 #[error("model `{model}` is missing required `@id` field")]
79 #[diagnostic(code(prax::schema::missing_id))]
80 MissingId { model: String },
81
82 #[error("configuration error: {message}")]
84 #[diagnostic(code(prax::schema::config_error))]
85 ConfigError { message: String },
86
87 #[error("failed to parse TOML")]
89 #[diagnostic(code(prax::schema::toml_error))]
90 TomlError {
91 #[source]
92 source: toml::de::Error,
93 },
94
95 #[error("schema validation failed with {count} error(s)")]
97 #[diagnostic(code(prax::schema::validation_failed))]
98 ValidationFailed {
99 count: usize,
100 #[related]
101 errors: Vec<SchemaError>,
102 },
103
104 #[error("field '{field}' of type Vector is missing required @dim attribute")]
106 #[diagnostic(code(prax::schema::missing_vector_dimension))]
107 MissingVectorDimension {
108 field: String,
110 },
111
112 #[error(
114 "invalid vector element type '{value}' (expected one of: float2, float4, float8, int1, int2, int4)"
115 )]
116 #[diagnostic(code(prax::schema::invalid_vector_type))]
117 InvalidVectorType {
118 value: String,
120 },
121
122 #[error("invalid vector metric '{value}' (expected one of: cosine, l2, inner)")]
124 #[diagnostic(code(prax::schema::invalid_vector_metric))]
125 InvalidVectorMetric {
126 value: String,
128 },
129
130 #[error("invalid vector index '{value}' (expected: hnsw)")]
132 #[diagnostic(code(prax::schema::invalid_vector_index))]
133 InvalidVectorIndex {
134 value: String,
136 },
137}
138
139impl SchemaError {
140 pub fn syntax(
142 src: impl Into<String>,
143 offset: usize,
144 len: usize,
145 message: impl Into<String>,
146 ) -> Self {
147 Self::SyntaxError {
148 src: src.into(),
149 span: (offset, len).into(),
150 message: message.into(),
151 }
152 }
153
154 pub fn invalid_model(name: impl Into<String>, message: impl Into<String>) -> Self {
156 Self::InvalidModel {
157 name: name.into(),
158 message: message.into(),
159 }
160 }
161
162 pub fn invalid_field(
164 model: impl Into<String>,
165 field: impl Into<String>,
166 message: impl Into<String>,
167 ) -> Self {
168 Self::InvalidField {
169 model: model.into(),
170 field: field.into(),
171 message: message.into(),
172 }
173 }
174
175 pub fn invalid_relation(
177 model: impl Into<String>,
178 field: impl Into<String>,
179 message: impl Into<String>,
180 ) -> Self {
181 Self::InvalidRelation {
182 model: model.into(),
183 field: field.into(),
184 message: message.into(),
185 }
186 }
187
188 pub fn duplicate(kind: impl Into<String>, name: impl Into<String>) -> Self {
190 Self::Duplicate {
191 kind: kind.into(),
192 name: name.into(),
193 }
194 }
195
196 pub fn unknown_type(
198 model: impl Into<String>,
199 field: impl Into<String>,
200 type_name: impl Into<String>,
201 ) -> Self {
202 Self::UnknownType {
203 model: model.into(),
204 field: field.into(),
205 type_name: type_name.into(),
206 }
207 }
208}
209
210#[cfg(test)]
211#[allow(unused_assignments)]
212mod tests {
213 use super::*;
214
215 #[test]
216 fn test_schema_result_type() {
217 let ok_result: SchemaResult<i32> = Ok(42);
218 assert!(ok_result.is_ok());
219 assert_eq!(ok_result.unwrap(), 42);
220
221 let err_result: SchemaResult<i32> = Err(SchemaError::ConfigError {
222 message: "test".to_string(),
223 });
224 assert!(err_result.is_err());
225 }
226
227 #[test]
230 fn test_syntax_error() {
231 let err = SchemaError::syntax("model User { }", 6, 4, "unexpected token");
232
233 match err {
234 SchemaError::SyntaxError { src, span, message } => {
235 assert_eq!(src, "model User { }");
236 assert_eq!(span.offset(), 6);
237 assert_eq!(span.len(), 4);
238 assert_eq!(message, "unexpected token");
239 }
240 _ => panic!("Expected SyntaxError"),
241 }
242 }
243
244 #[test]
245 fn test_invalid_model_error() {
246 let err = SchemaError::invalid_model("User", "missing id field");
247
248 match err {
249 SchemaError::InvalidModel { name, message } => {
250 assert_eq!(name, "User");
251 assert_eq!(message, "missing id field");
252 }
253 _ => panic!("Expected InvalidModel"),
254 }
255 }
256
257 #[test]
258 fn test_invalid_field_error() {
259 let err = SchemaError::invalid_field("User", "email", "invalid type");
260
261 match err {
262 SchemaError::InvalidField {
263 model,
264 field,
265 message,
266 } => {
267 assert_eq!(model, "User");
268 assert_eq!(field, "email");
269 assert_eq!(message, "invalid type");
270 }
271 _ => panic!("Expected InvalidField"),
272 }
273 }
274
275 #[test]
276 fn test_invalid_relation_error() {
277 let err = SchemaError::invalid_relation("Post", "author", "missing foreign key");
278
279 match err {
280 SchemaError::InvalidRelation {
281 model,
282 field,
283 message,
284 } => {
285 assert_eq!(model, "Post");
286 assert_eq!(field, "author");
287 assert_eq!(message, "missing foreign key");
288 }
289 _ => panic!("Expected InvalidRelation"),
290 }
291 }
292
293 #[test]
294 fn test_duplicate_error() {
295 let err = SchemaError::duplicate("model", "User");
296
297 match err {
298 SchemaError::Duplicate { kind, name } => {
299 assert_eq!(kind, "model");
300 assert_eq!(name, "User");
301 }
302 _ => panic!("Expected Duplicate"),
303 }
304 }
305
306 #[test]
307 fn test_unknown_type_error() {
308 let err = SchemaError::unknown_type("Post", "category", "Category");
309
310 match err {
311 SchemaError::UnknownType {
312 model,
313 field,
314 type_name,
315 } => {
316 assert_eq!(model, "Post");
317 assert_eq!(field, "category");
318 assert_eq!(type_name, "Category");
319 }
320 _ => panic!("Expected UnknownType"),
321 }
322 }
323
324 #[test]
327 fn test_io_error_display() {
328 let io_err = std::io::Error::new(std::io::ErrorKind::NotFound, "file not found");
329 let err = SchemaError::IoError {
330 path: "schema.prax".to_string(),
331 source: io_err,
332 };
333
334 let display = format!("{}", err);
335 assert!(display.contains("schema.prax"));
336 }
337
338 #[test]
339 fn test_syntax_error_display() {
340 let err = SchemaError::syntax("model", 0, 5, "unexpected");
341 let display = format!("{}", err);
342 assert!(display.contains("syntax error"));
343 }
344
345 #[test]
346 fn test_invalid_model_display() {
347 let err = SchemaError::invalid_model("User", "test message");
348 let display = format!("{}", err);
349 assert!(display.contains("User"));
350 assert!(display.contains("test message"));
351 }
352
353 #[test]
354 fn test_invalid_field_display() {
355 let err = SchemaError::invalid_field("User", "email", "test");
356 let display = format!("{}", err);
357 assert!(display.contains("User.email"));
358 }
359
360 #[test]
361 fn test_invalid_relation_display() {
362 let err = SchemaError::invalid_relation("Post", "author", "test");
363 let display = format!("{}", err);
364 assert!(display.contains("Post.author"));
365 }
366
367 #[test]
368 fn test_duplicate_display() {
369 let err = SchemaError::duplicate("model", "User");
370 let display = format!("{}", err);
371 assert!(display.contains("duplicate"));
372 assert!(display.contains("model"));
373 assert!(display.contains("User"));
374 }
375
376 #[test]
377 fn test_unknown_type_display() {
378 let err = SchemaError::unknown_type("Post", "author", "UserType");
379 let display = format!("{}", err);
380 assert!(display.contains("UserType"));
381 assert!(display.contains("Post.author"));
382 }
383
384 #[test]
385 fn test_missing_id_display() {
386 let err = SchemaError::MissingId {
387 model: "User".to_string(),
388 };
389 let display = format!("{}", err);
390 assert!(display.contains("User"));
391 assert!(display.contains("@id"));
392 }
393
394 #[test]
395 fn test_config_error_display() {
396 let err = SchemaError::ConfigError {
397 message: "invalid URL".to_string(),
398 };
399 let display = format!("{}", err);
400 assert!(display.contains("invalid URL"));
401 }
402
403 #[test]
404 fn test_validation_failed_display() {
405 let err = SchemaError::ValidationFailed {
406 count: 3,
407 errors: vec![],
408 };
409 let display = format!("{}", err);
410 assert!(display.contains("3"));
411 }
412
413 #[test]
416 fn test_error_debug() {
417 let err = SchemaError::invalid_model("User", "test");
418 let debug = format!("{:?}", err);
419 assert!(debug.contains("InvalidModel"));
420 assert!(debug.contains("User"));
421 }
422
423 #[test]
426 fn test_syntax_from_strings() {
427 let src = String::from("content");
428 let msg = String::from("message");
429 let err = SchemaError::syntax(src, 0, 7, msg);
430
431 if let SchemaError::SyntaxError { src, message, .. } = err {
432 assert_eq!(src, "content");
433 assert_eq!(message, "message");
434 } else {
435 panic!("Expected SyntaxError");
436 }
437 }
438
439 #[test]
440 fn test_invalid_model_from_strings() {
441 let name = String::from("Model");
442 let msg = String::from("error");
443 let err = SchemaError::invalid_model(name, msg);
444
445 if let SchemaError::InvalidModel { name, message } = err {
446 assert_eq!(name, "Model");
447 assert_eq!(message, "error");
448 } else {
449 panic!("Expected InvalidModel");
450 }
451 }
452
453 #[test]
454 fn test_invalid_field_from_strings() {
455 let model = String::from("User");
456 let field = String::from("email");
457 let msg = String::from("error");
458 let err = SchemaError::invalid_field(model, field, msg);
459
460 if let SchemaError::InvalidField {
461 model,
462 field,
463 message,
464 } = err
465 {
466 assert_eq!(model, "User");
467 assert_eq!(field, "email");
468 assert_eq!(message, "error");
469 } else {
470 panic!("Expected InvalidField");
471 }
472 }
473}