#![cfg_attr(
not(test),
deny(
clippy::unwrap_used,
clippy::expect_used,
clippy::panic,
clippy::unreachable,
clippy::todo,
clippy::unimplemented,
clippy::indexing_slicing,
clippy::string_slice,
clippy::arithmetic_side_effects,
)
)]
use std::fmt;
pub mod codes {
pub const LIMIT: &str = "constela.limit";
pub const SYNTAX: &str = "constela.syntax";
pub const VERSION: &str = "constela.version";
pub const STATE_TYPE: &str = "constela.state.type";
pub const UNKNOWN_REF: &str = "constela.unknown_ref";
pub const DUPLICATE: &str = "constela.duplicate";
pub const TAG_NOT_ALLOWED: &str = "constela.tag_not_allowed";
pub const ATTR_NOT_ALLOWED: &str = "constela.attr_not_allowed";
pub const URL_SCHEME: &str = "constela.url_scheme";
pub const MISPLACED: &str = "constela.misplaced";
pub const CYCLE: &str = "constela.cycle";
pub const STEP_SHAPE: &str = "constela.step_shape";
pub const FEATURE_REQUIRED: &str = "constela.feature_required";
pub const EVAL: &str = "constela.eval";
pub const RENDER_LIMIT: &str = "constela.render_limit";
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Diagnostic {
pub path: String,
pub code: &'static str,
pub message: String,
}
impl Diagnostic {
pub fn new(path: impl Into<String>, code: &'static str, message: impl Into<String>) -> Self {
Self {
path: path.into(),
code,
message: message.into(),
}
}
}
impl fmt::Display for Diagnostic {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}: {} [{}]", self.path, self.message, self.code)
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ConstelaError {
Limit(Diagnostic),
Syntax(Diagnostic),
Invalid(Vec<Diagnostic>),
Render(Diagnostic),
}
impl ConstelaError {
#[must_use]
pub fn diagnostics(&self) -> &[Diagnostic] {
match self {
Self::Limit(d) | Self::Syntax(d) | Self::Render(d) => std::slice::from_ref(d),
Self::Invalid(ds) => ds,
}
}
#[must_use]
pub fn to_json(&self) -> serde_json::Value {
serde_json::Value::Array(
self.diagnostics()
.iter()
.map(|d| {
serde_json::json!({
"path": d.path,
"code": d.code,
"message": d.message,
})
})
.collect(),
)
}
}
impl fmt::Display for ConstelaError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let diagnostics = self.diagnostics();
let label = match self {
Self::Limit(_) => "constela document rejected",
Self::Syntax(_) => "constela document could not be parsed",
Self::Invalid(_) => "constela document is invalid",
Self::Render(_) => "constela document could not be rendered",
};
write!(f, "{label} ({} problem", diagnostics.len())?;
if diagnostics.len() != 1 {
f.write_str("s")?;
}
f.write_str(")")?;
for diagnostic in diagnostics {
write!(f, "\n - {diagnostic}")?;
}
Ok(())
}
}
impl std::error::Error for ConstelaError {}
impl ConstelaError {
#[must_use]
pub const fn http_status(&self) -> http::StatusCode {
http::StatusCode::UNPROCESSABLE_ENTITY
}
}