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
105impl SchemaError {
106 pub fn syntax(
108 src: impl Into<String>,
109 offset: usize,
110 len: usize,
111 message: impl Into<String>,
112 ) -> Self {
113 Self::SyntaxError {
114 src: src.into(),
115 span: (offset, len).into(),
116 message: message.into(),
117 }
118 }
119
120 pub fn invalid_model(name: impl Into<String>, message: impl Into<String>) -> Self {
122 Self::InvalidModel {
123 name: name.into(),
124 message: message.into(),
125 }
126 }
127
128 pub fn invalid_field(
130 model: impl Into<String>,
131 field: impl Into<String>,
132 message: impl Into<String>,
133 ) -> Self {
134 Self::InvalidField {
135 model: model.into(),
136 field: field.into(),
137 message: message.into(),
138 }
139 }
140
141 pub fn invalid_relation(
143 model: impl Into<String>,
144 field: impl Into<String>,
145 message: impl Into<String>,
146 ) -> Self {
147 Self::InvalidRelation {
148 model: model.into(),
149 field: field.into(),
150 message: message.into(),
151 }
152 }
153
154 pub fn duplicate(kind: impl Into<String>, name: impl Into<String>) -> Self {
156 Self::Duplicate {
157 kind: kind.into(),
158 name: name.into(),
159 }
160 }
161
162 pub fn unknown_type(
164 model: impl Into<String>,
165 field: impl Into<String>,
166 type_name: impl Into<String>,
167 ) -> Self {
168 Self::UnknownType {
169 model: model.into(),
170 field: field.into(),
171 type_name: type_name.into(),
172 }
173 }
174}
175
176#[cfg(test)]
177#[allow(unused_assignments)]
178mod tests {
179 use super::*;
180
181 #[test]
182 fn test_schema_result_type() {
183 let ok_result: SchemaResult<i32> = Ok(42);
184 assert!(ok_result.is_ok());
185 assert_eq!(ok_result.unwrap(), 42);
186
187 let err_result: SchemaResult<i32> = Err(SchemaError::ConfigError {
188 message: "test".to_string(),
189 });
190 assert!(err_result.is_err());
191 }
192
193 #[test]
196 fn test_syntax_error() {
197 let err = SchemaError::syntax("model User { }", 6, 4, "unexpected token");
198
199 match err {
200 SchemaError::SyntaxError { src, span, message } => {
201 assert_eq!(src, "model User { }");
202 assert_eq!(span.offset(), 6);
203 assert_eq!(span.len(), 4);
204 assert_eq!(message, "unexpected token");
205 }
206 _ => panic!("Expected SyntaxError"),
207 }
208 }
209
210 #[test]
211 fn test_invalid_model_error() {
212 let err = SchemaError::invalid_model("User", "missing id field");
213
214 match err {
215 SchemaError::InvalidModel { name, message } => {
216 assert_eq!(name, "User");
217 assert_eq!(message, "missing id field");
218 }
219 _ => panic!("Expected InvalidModel"),
220 }
221 }
222
223 #[test]
224 fn test_invalid_field_error() {
225 let err = SchemaError::invalid_field("User", "email", "invalid type");
226
227 match err {
228 SchemaError::InvalidField {
229 model,
230 field,
231 message,
232 } => {
233 assert_eq!(model, "User");
234 assert_eq!(field, "email");
235 assert_eq!(message, "invalid type");
236 }
237 _ => panic!("Expected InvalidField"),
238 }
239 }
240
241 #[test]
242 fn test_invalid_relation_error() {
243 let err = SchemaError::invalid_relation("Post", "author", "missing foreign key");
244
245 match err {
246 SchemaError::InvalidRelation {
247 model,
248 field,
249 message,
250 } => {
251 assert_eq!(model, "Post");
252 assert_eq!(field, "author");
253 assert_eq!(message, "missing foreign key");
254 }
255 _ => panic!("Expected InvalidRelation"),
256 }
257 }
258
259 #[test]
260 fn test_duplicate_error() {
261 let err = SchemaError::duplicate("model", "User");
262
263 match err {
264 SchemaError::Duplicate { kind, name } => {
265 assert_eq!(kind, "model");
266 assert_eq!(name, "User");
267 }
268 _ => panic!("Expected Duplicate"),
269 }
270 }
271
272 #[test]
273 fn test_unknown_type_error() {
274 let err = SchemaError::unknown_type("Post", "category", "Category");
275
276 match err {
277 SchemaError::UnknownType {
278 model,
279 field,
280 type_name,
281 } => {
282 assert_eq!(model, "Post");
283 assert_eq!(field, "category");
284 assert_eq!(type_name, "Category");
285 }
286 _ => panic!("Expected UnknownType"),
287 }
288 }
289
290 #[test]
293 fn test_io_error_display() {
294 let io_err = std::io::Error::new(std::io::ErrorKind::NotFound, "file not found");
295 let err = SchemaError::IoError {
296 path: "schema.prax".to_string(),
297 source: io_err,
298 };
299
300 let display = format!("{}", err);
301 assert!(display.contains("schema.prax"));
302 }
303
304 #[test]
305 fn test_syntax_error_display() {
306 let err = SchemaError::syntax("model", 0, 5, "unexpected");
307 let display = format!("{}", err);
308 assert!(display.contains("syntax error"));
309 }
310
311 #[test]
312 fn test_invalid_model_display() {
313 let err = SchemaError::invalid_model("User", "test message");
314 let display = format!("{}", err);
315 assert!(display.contains("User"));
316 assert!(display.contains("test message"));
317 }
318
319 #[test]
320 fn test_invalid_field_display() {
321 let err = SchemaError::invalid_field("User", "email", "test");
322 let display = format!("{}", err);
323 assert!(display.contains("User.email"));
324 }
325
326 #[test]
327 fn test_invalid_relation_display() {
328 let err = SchemaError::invalid_relation("Post", "author", "test");
329 let display = format!("{}", err);
330 assert!(display.contains("Post.author"));
331 }
332
333 #[test]
334 fn test_duplicate_display() {
335 let err = SchemaError::duplicate("model", "User");
336 let display = format!("{}", err);
337 assert!(display.contains("duplicate"));
338 assert!(display.contains("model"));
339 assert!(display.contains("User"));
340 }
341
342 #[test]
343 fn test_unknown_type_display() {
344 let err = SchemaError::unknown_type("Post", "author", "UserType");
345 let display = format!("{}", err);
346 assert!(display.contains("UserType"));
347 assert!(display.contains("Post.author"));
348 }
349
350 #[test]
351 fn test_missing_id_display() {
352 let err = SchemaError::MissingId {
353 model: "User".to_string(),
354 };
355 let display = format!("{}", err);
356 assert!(display.contains("User"));
357 assert!(display.contains("@id"));
358 }
359
360 #[test]
361 fn test_config_error_display() {
362 let err = SchemaError::ConfigError {
363 message: "invalid URL".to_string(),
364 };
365 let display = format!("{}", err);
366 assert!(display.contains("invalid URL"));
367 }
368
369 #[test]
370 fn test_validation_failed_display() {
371 let err = SchemaError::ValidationFailed {
372 count: 3,
373 errors: vec![],
374 };
375 let display = format!("{}", err);
376 assert!(display.contains("3"));
377 }
378
379 #[test]
382 fn test_error_debug() {
383 let err = SchemaError::invalid_model("User", "test");
384 let debug = format!("{:?}", err);
385 assert!(debug.contains("InvalidModel"));
386 assert!(debug.contains("User"));
387 }
388
389 #[test]
392 fn test_syntax_from_strings() {
393 let src = String::from("content");
394 let msg = String::from("message");
395 let err = SchemaError::syntax(src, 0, 7, msg);
396
397 if let SchemaError::SyntaxError { src, message, .. } = err {
398 assert_eq!(src, "content");
399 assert_eq!(message, "message");
400 } else {
401 panic!("Expected SyntaxError");
402 }
403 }
404
405 #[test]
406 fn test_invalid_model_from_strings() {
407 let name = String::from("Model");
408 let msg = String::from("error");
409 let err = SchemaError::invalid_model(name, msg);
410
411 if let SchemaError::InvalidModel { name, message } = err {
412 assert_eq!(name, "Model");
413 assert_eq!(message, "error");
414 } else {
415 panic!("Expected InvalidModel");
416 }
417 }
418
419 #[test]
420 fn test_invalid_field_from_strings() {
421 let model = String::from("User");
422 let field = String::from("email");
423 let msg = String::from("error");
424 let err = SchemaError::invalid_field(model, field, msg);
425
426 if let SchemaError::InvalidField {
427 model,
428 field,
429 message,
430 } = err
431 {
432 assert_eq!(model, "User");
433 assert_eq!(field, "email");
434 assert_eq!(message, "error");
435 } else {
436 panic!("Expected InvalidField");
437 }
438 }
439}