#![doc = include_str!("../README.md")]
#![warn(
elided_lifetimes_in_paths,
explicit_outlives_requirements,
missing_debug_implementations,
missing_docs,
noop_method_call,
single_use_lifetimes,
trivial_casts,
trivial_numeric_casts,
unreachable_pub,
unsafe_code,
unused_qualifications
)]
#![warn(clippy::pedantic, clippy::cargo)]
use std::path::Path;
use kdl_schema::Schema;
use knuffel::{ast::Document, span::Span};
use miette::{Diagnostic, NamedSource};
use thiserror::Error;
mod check;
pub type DocumentAst = Document<Span>;
#[derive(Debug, Error, Diagnostic)]
pub enum CheckFailure {
#[error("could not read file: {0}")]
IoError(#[from] std::io::Error),
#[error("could not parse file: {0}")]
#[diagnostic(transparent)]
ParseError(#[from] knuffel::Error<Span>),
#[error(transparent)]
#[diagnostic(transparent)]
ValidationFailure(#[from] check::CheckFailure),
#[error("{failure}")]
#[diagnostic(forward(failure))]
SourcedValidationFailure {
#[source_code]
source: NamedSource,
#[source]
failure: check::CheckFailure,
},
}
impl CheckFailure {
fn with_named_source(self, name: impl AsRef<str>, source: String) -> Self {
match self {
Self::ValidationFailure(failure) => Self::SourcedValidationFailure {
source: NamedSource::new(name, source),
failure,
},
_ => self,
}
}
}
pub trait CheckExt {
fn check_file_matches(&self, file_path: impl AsRef<Path>) -> Result<(), CheckFailure>;
fn check_text_matches(
&self,
document_name: &str,
document_text: &str,
) -> Result<(), CheckFailure>;
fn check_ast_matches(&self, document_ast: DocumentAst) -> Result<(), CheckFailure>;
}
impl CheckExt for Schema {
fn check_file_matches(&self, file_path: impl AsRef<Path>) -> Result<(), CheckFailure> {
let file_path = file_path.as_ref();
let file_name = file_path.display().to_string();
let file_text = std::fs::read_to_string(file_path)?;
let ast = knuffel::parse_ast(&file_name, &file_text)?;
self.check_ast_matches(ast)
.map_err(|err| err.with_named_source(file_name, file_text))
}
fn check_text_matches(
&self,
document_name: &str,
document_text: &str,
) -> Result<(), CheckFailure> {
let ast = knuffel::parse_ast(document_name, document_text)?;
self.check_ast_matches(ast)
.map_err(|err| err.with_named_source(document_name, document_text.to_string()))
}
fn check_ast_matches(&self, document_ast: DocumentAst) -> Result<(), CheckFailure> {
check::check(&document_ast, self)?;
Ok(())
}
}