#![allow(unused_assignments)]
use miette::Diagnostic;
use thiserror::Error;
pub type SchemaResult<T> = Result<T, SchemaError>;
#[derive(Error, Debug, Diagnostic)]
pub enum SchemaError {
#[error("failed to read file: {path}")]
#[diagnostic(code(prax::schema::io_error))]
IoError {
path: String,
#[source]
source: std::io::Error,
},
#[error("syntax error in schema")]
#[diagnostic(code(prax::schema::syntax_error))]
SyntaxError {
#[source_code]
src: String,
#[label("error here")]
span: miette::SourceSpan,
message: String,
},
#[error("invalid model `{name}`: {message}")]
#[diagnostic(code(prax::schema::invalid_model))]
InvalidModel { name: String, message: String },
#[error("invalid field `{model}.{field}`: {message}")]
#[diagnostic(code(prax::schema::invalid_field))]
InvalidField {
model: String,
field: String,
message: String,
},
#[error("invalid relation `{model}.{field}`: {message}")]
#[diagnostic(code(prax::schema::invalid_relation))]
InvalidRelation {
model: String,
field: String,
message: String,
},
#[error("duplicate {kind} `{name}`")]
#[diagnostic(code(prax::schema::duplicate))]
Duplicate { kind: String, name: String },
#[error("unknown type `{type_name}` in `{model}.{field}`")]
#[diagnostic(code(prax::schema::unknown_type))]
UnknownType {
model: String,
field: String,
type_name: String,
},
#[error("invalid attribute `@{attribute}`: {message}")]
#[diagnostic(code(prax::schema::invalid_attribute))]
InvalidAttribute { attribute: String, message: String },
#[error("model `{model}` is missing required `@id` field")]
#[diagnostic(code(prax::schema::missing_id))]
MissingId { model: String },
#[error("configuration error: {message}")]
#[diagnostic(code(prax::schema::config_error))]
ConfigError { message: String },
#[error("failed to parse TOML")]
#[diagnostic(code(prax::schema::toml_error))]
TomlError {
#[source]
source: toml::de::Error,
},
#[error("schema validation failed with {count} error(s)")]
#[diagnostic(code(prax::schema::validation_failed))]
ValidationFailed {
count: usize,
#[related]
errors: Vec<SchemaError>,
},
}
impl SchemaError {
pub fn syntax(
src: impl Into<String>,
offset: usize,
len: usize,
message: impl Into<String>,
) -> Self {
Self::SyntaxError {
src: src.into(),
span: (offset, len).into(),
message: message.into(),
}
}
pub fn invalid_model(name: impl Into<String>, message: impl Into<String>) -> Self {
Self::InvalidModel {
name: name.into(),
message: message.into(),
}
}
pub fn invalid_field(
model: impl Into<String>,
field: impl Into<String>,
message: impl Into<String>,
) -> Self {
Self::InvalidField {
model: model.into(),
field: field.into(),
message: message.into(),
}
}
pub fn invalid_relation(
model: impl Into<String>,
field: impl Into<String>,
message: impl Into<String>,
) -> Self {
Self::InvalidRelation {
model: model.into(),
field: field.into(),
message: message.into(),
}
}
pub fn duplicate(kind: impl Into<String>, name: impl Into<String>) -> Self {
Self::Duplicate {
kind: kind.into(),
name: name.into(),
}
}
pub fn unknown_type(
model: impl Into<String>,
field: impl Into<String>,
type_name: impl Into<String>,
) -> Self {
Self::UnknownType {
model: model.into(),
field: field.into(),
type_name: type_name.into(),
}
}
}
#[cfg(test)]
#[allow(unused_assignments)]
mod tests {
use super::*;
#[test]
fn test_schema_result_type() {
let ok_result: SchemaResult<i32> = Ok(42);
assert!(ok_result.is_ok());
assert_eq!(ok_result.unwrap(), 42);
let err_result: SchemaResult<i32> = Err(SchemaError::ConfigError {
message: "test".to_string(),
});
assert!(err_result.is_err());
}
#[test]
fn test_syntax_error() {
let err = SchemaError::syntax("model User { }", 6, 4, "unexpected token");
match err {
SchemaError::SyntaxError { src, span, message } => {
assert_eq!(src, "model User { }");
assert_eq!(span.offset(), 6);
assert_eq!(span.len(), 4);
assert_eq!(message, "unexpected token");
}
_ => panic!("Expected SyntaxError"),
}
}
#[test]
fn test_invalid_model_error() {
let err = SchemaError::invalid_model("User", "missing id field");
match err {
SchemaError::InvalidModel { name, message } => {
assert_eq!(name, "User");
assert_eq!(message, "missing id field");
}
_ => panic!("Expected InvalidModel"),
}
}
#[test]
fn test_invalid_field_error() {
let err = SchemaError::invalid_field("User", "email", "invalid type");
match err {
SchemaError::InvalidField {
model,
field,
message,
} => {
assert_eq!(model, "User");
assert_eq!(field, "email");
assert_eq!(message, "invalid type");
}
_ => panic!("Expected InvalidField"),
}
}
#[test]
fn test_invalid_relation_error() {
let err = SchemaError::invalid_relation("Post", "author", "missing foreign key");
match err {
SchemaError::InvalidRelation {
model,
field,
message,
} => {
assert_eq!(model, "Post");
assert_eq!(field, "author");
assert_eq!(message, "missing foreign key");
}
_ => panic!("Expected InvalidRelation"),
}
}
#[test]
fn test_duplicate_error() {
let err = SchemaError::duplicate("model", "User");
match err {
SchemaError::Duplicate { kind, name } => {
assert_eq!(kind, "model");
assert_eq!(name, "User");
}
_ => panic!("Expected Duplicate"),
}
}
#[test]
fn test_unknown_type_error() {
let err = SchemaError::unknown_type("Post", "category", "Category");
match err {
SchemaError::UnknownType {
model,
field,
type_name,
} => {
assert_eq!(model, "Post");
assert_eq!(field, "category");
assert_eq!(type_name, "Category");
}
_ => panic!("Expected UnknownType"),
}
}
#[test]
fn test_io_error_display() {
let io_err = std::io::Error::new(std::io::ErrorKind::NotFound, "file not found");
let err = SchemaError::IoError {
path: "schema.prax".to_string(),
source: io_err,
};
let display = format!("{}", err);
assert!(display.contains("schema.prax"));
}
#[test]
fn test_syntax_error_display() {
let err = SchemaError::syntax("model", 0, 5, "unexpected");
let display = format!("{}", err);
assert!(display.contains("syntax error"));
}
#[test]
fn test_invalid_model_display() {
let err = SchemaError::invalid_model("User", "test message");
let display = format!("{}", err);
assert!(display.contains("User"));
assert!(display.contains("test message"));
}
#[test]
fn test_invalid_field_display() {
let err = SchemaError::invalid_field("User", "email", "test");
let display = format!("{}", err);
assert!(display.contains("User.email"));
}
#[test]
fn test_invalid_relation_display() {
let err = SchemaError::invalid_relation("Post", "author", "test");
let display = format!("{}", err);
assert!(display.contains("Post.author"));
}
#[test]
fn test_duplicate_display() {
let err = SchemaError::duplicate("model", "User");
let display = format!("{}", err);
assert!(display.contains("duplicate"));
assert!(display.contains("model"));
assert!(display.contains("User"));
}
#[test]
fn test_unknown_type_display() {
let err = SchemaError::unknown_type("Post", "author", "UserType");
let display = format!("{}", err);
assert!(display.contains("UserType"));
assert!(display.contains("Post.author"));
}
#[test]
fn test_missing_id_display() {
let err = SchemaError::MissingId {
model: "User".to_string(),
};
let display = format!("{}", err);
assert!(display.contains("User"));
assert!(display.contains("@id"));
}
#[test]
fn test_config_error_display() {
let err = SchemaError::ConfigError {
message: "invalid URL".to_string(),
};
let display = format!("{}", err);
assert!(display.contains("invalid URL"));
}
#[test]
fn test_validation_failed_display() {
let err = SchemaError::ValidationFailed {
count: 3,
errors: vec![],
};
let display = format!("{}", err);
assert!(display.contains("3"));
}
#[test]
fn test_error_debug() {
let err = SchemaError::invalid_model("User", "test");
let debug = format!("{:?}", err);
assert!(debug.contains("InvalidModel"));
assert!(debug.contains("User"));
}
#[test]
fn test_syntax_from_strings() {
let src = String::from("content");
let msg = String::from("message");
let err = SchemaError::syntax(src, 0, 7, msg);
if let SchemaError::SyntaxError { src, message, .. } = err {
assert_eq!(src, "content");
assert_eq!(message, "message");
} else {
panic!("Expected SyntaxError");
}
}
#[test]
fn test_invalid_model_from_strings() {
let name = String::from("Model");
let msg = String::from("error");
let err = SchemaError::invalid_model(name, msg);
if let SchemaError::InvalidModel { name, message } = err {
assert_eq!(name, "Model");
assert_eq!(message, "error");
} else {
panic!("Expected InvalidModel");
}
}
#[test]
fn test_invalid_field_from_strings() {
let model = String::from("User");
let field = String::from("email");
let msg = String::from("error");
let err = SchemaError::invalid_field(model, field, msg);
if let SchemaError::InvalidField {
model,
field,
message,
} = err
{
assert_eq!(model, "User");
assert_eq!(field, "email");
assert_eq!(message, "error");
} else {
panic!("Expected InvalidField");
}
}
}