use std::fmt;
use crate::{Atom, LineCol, Path, Schema, SyntaxError};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum Problem {
Invalid,
Syntax,
Expected,
NotAReference,
WrongKind,
UnknownField,
MissingField,
OutOfRange,
BadTyped,
BadBinary,
BadId,
Unresolved,
}
impl Problem {
pub const ALL: [Problem; 12] = [
Problem::Invalid,
Problem::Syntax,
Problem::Expected,
Problem::NotAReference,
Problem::WrongKind,
Problem::UnknownField,
Problem::MissingField,
Problem::OutOfRange,
Problem::BadTyped,
Problem::BadBinary,
Problem::BadId,
Problem::Unresolved,
];
#[must_use]
pub const fn code(self) -> &'static str {
match self {
Problem::Invalid => "SD0000",
Problem::Syntax => "SD0001",
Problem::Expected => "SD0002",
Problem::NotAReference => "SD0003",
Problem::WrongKind => "SD0004",
Problem::UnknownField => "SD0005",
Problem::MissingField => "SD0006",
Problem::OutOfRange => "SD0007",
Problem::BadTyped => "SD0008",
Problem::BadBinary => "SD0009",
Problem::BadId => "SD0010",
Problem::Unresolved => "SD0011",
}
}
#[must_use]
pub fn of_code(code: &str) -> Option<Problem> {
Problem::ALL.into_iter().find(|p| p.code() == code)
}
}
impl fmt::Display for Problem {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.code())
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum Code {
Data(Problem),
Tool(Atom),
}
impl Code {
pub fn tool(code: Atom) -> Code {
Problem::of_code(code.as_str()).map_or(Code::Tool(code), Code::Data)
}
#[must_use]
pub fn as_str(self) -> &'static str {
match self {
Code::Data(p) => p.code(),
Code::Tool(a) => a.as_str(),
}
}
fn atom(self) -> Atom {
match self {
Code::Data(p) => Atom::new(p.code()),
Code::Tool(a) => a,
}
}
}
impl From<Problem> for Code {
fn from(p: Problem) -> Code {
Code::Data(p)
}
}
impl From<Atom> for Code {
fn from(a: Atom) -> Code {
Code::tool(a)
}
}
impl fmt::Display for Code {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.as_str())
}
}
impl PartialEq<Problem> for Code {
fn eq(&self, p: &Problem) -> bool {
*self == Code::Data(*p)
}
}
impl Schema for Code {
fn shape() -> crate::Shape {
Atom::shape()
}
fn read(v: &crate::ValueRef<'_>, cx: &mut crate::Reader<'_>) -> Option<Self> {
Atom::read(v, cx).map(Code::tool)
}
fn write(&self) -> crate::Value {
self.atom().write()
}
fn encode(&self, w: &mut crate::binary::Writer) {
self.atom().encode(w);
}
fn decode(d: &mut crate::binary::Decoder<'_>) -> Option<Self> {
Atom::decode(d).map(Code::tool)
}
fn pack(&self, p: &mut crate::frame::Packer) {
self.atom().pack(p);
}
fn unpack(u: &mut crate::frame::Unpacker<'_>) -> Option<Self> {
Atom::unpack(u).map(Code::tool)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Schema)]
pub enum Severity {
#[default]
Error,
Warning,
Note,
}
#[derive(Debug, Clone, PartialEq, Eq, Default, Schema)]
#[data(rename_all = "camelCase")]
pub struct Diagnostic {
pub severity: Severity,
pub code: Option<Code>,
pub message: String,
pub file: Option<Atom>,
#[data(default)]
pub path: Path,
#[data(default)]
pub on_key: bool,
pub line: Option<u32>,
pub col: Option<u32>,
pub end_line: Option<u32>,
pub end_col: Option<u32>,
pub tool: Option<Atom>,
}
impl Diagnostic {
pub fn error(code: impl Into<Code>, message: impl fmt::Display) -> Self {
Self::of(Severity::Error, code, message)
}
pub fn warning(code: impl Into<Code>, message: impl fmt::Display) -> Self {
Self::of(Severity::Warning, code, message)
}
pub fn of(severity: Severity, code: impl Into<Code>, message: impl fmt::Display) -> Self {
Diagnostic {
severity,
code: Some(code.into()),
message: message.to_string(),
..Self::default()
}
}
#[must_use]
pub fn syntax(e: SyntaxError) -> Self {
Self::error(Problem::Syntax, e.message).at(e.at)
}
#[must_use]
pub fn in_file(mut self, file: impl fmt::Display) -> Self {
self.file = Some(Atom::new(&file.to_string()));
self
}
#[must_use]
pub fn at_line(mut self, line: usize) -> Self {
self.line = u32::try_from(line).ok();
self
}
#[must_use]
pub fn at(mut self, at: LineCol) -> Self {
self.line = Some(at.line);
self.col = Some(at.col);
self
}
#[must_use]
pub fn at_path(mut self, path: Path) -> Self {
self.path = path;
self
}
#[must_use]
pub fn by(mut self, tool: Atom) -> Self {
self.tool = Some(tool);
self
}
#[must_use]
pub fn or_code(mut self, code: impl Into<Code>) -> Self {
self.code.get_or_insert(code.into());
self
}
#[must_use]
pub fn map(mut self, wrap: impl FnOnce(String) -> String) -> Self {
self.message = wrap(self.message);
self
}
#[must_use]
pub fn is_error(&self) -> bool {
self.severity == Severity::Error
}
}
impl fmt::Display for Diagnostic {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match (&self.file, self.line, self.col) {
(Some(file), Some(line), Some(col)) => write!(f, "{file}:{line}:{col}: ")?,
(Some(file), Some(line), None) => write!(f, "{file}:{line}: ")?,
(Some(file), None, _) => write!(f, "{file}: ")?,
(None, Some(line), Some(col)) => write!(f, "{line}:{col}: ")?,
(None, Some(line), None) => write!(f, "line {line}: ")?,
(None, None, _) => {}
}
if !self.path.is_root() {
write!(f, "{}: ", self.path)?;
}
f.write_str(&self.message)
}
}
impl std::error::Error for Diagnostic {}
impl From<String> for Diagnostic {
fn from(message: String) -> Self {
Diagnostic {
message,
..Self::default()
}
}
}
impl From<&str> for Diagnostic {
fn from(message: &str) -> Self {
message.to_owned().into()
}
}
impl From<Diagnostic> for String {
fn from(d: Diagnostic) -> Self {
d.to_string()
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Errors(pub Vec<Diagnostic>);
impl fmt::Display for Errors {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
for (i, e) in self.0.iter().enumerate() {
if i > 0 {
f.write_str("\n")?;
}
write!(f, "{e}")?;
}
Ok(())
}
}
impl std::error::Error for Errors {}
impl From<SyntaxError> for Errors {
fn from(e: SyntaxError) -> Self {
Errors(vec![Diagnostic::syntax(e)])
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn said_with_what_is_known_and_a_frame_reads_back() {
let d = Diagnostic::error(Atom::new("XY0002"), "no owner")
.in_file("docs/intro.doc")
.at_line(3)
.at_path(Path::root().key("owner"));
assert_eq!(d.to_string(), "docs/intro.doc:3: owner: no owner");
let mut bytes = Vec::new();
crate::io::write_frame(&mut bytes, &d).unwrap();
let back: Diagnostic = crate::io::read_frame(&mut bytes.as_slice())
.unwrap()
.unwrap();
assert_eq!(back, d);
assert_eq!(
Diagnostic::from("bad")
.or_code(Atom::new("XY0004"))
.code
.map(Code::as_str),
Some("XY0004")
);
let missing = Diagnostic::error(Problem::MissingField, "missing `id`");
let mut framed = Vec::new();
crate::io::write_frame(&mut framed, &missing).unwrap();
let read_back: Diagnostic = crate::io::read_frame(&mut framed.as_slice())
.unwrap()
.unwrap();
assert_eq!(read_back.code, Some(Code::Data(Problem::MissingField)));
assert_eq!(Code::tool(Atom::new("SD0006")), Problem::MissingField);
assert!(
Problem::ALL
.iter()
.all(|p| Problem::of_code(p.code()) == Some(*p))
);
}
}