use crate::yaml::{
SourceId, SourceIdLocation, SourceLocation, SourceMap, SourcedYaml,
resolve::ReferenceError, yaml_parse_panic,
};
use itertools::Itertools;
use saphyr::{Scalar, ScanError, YamlData};
use std::{error::Error as StdError, io};
use thiserror::Error;
#[derive(Debug, Error)]
pub enum YamlErrorKind {
#[error("Error opening {source}")]
Io {
#[source]
error: io::Error,
source: String,
},
#[error("Expected field `{field}` with {expected}")]
MissingField {
field: &'static str,
expected: Expected,
},
#[error(transparent)]
Other(Box<dyn 'static + StdError + Send + Sync>),
#[error(transparent)]
Reference(ReferenceError),
#[error(transparent)]
Scan(saphyr::ScanError),
#[error("Expected {expected}, received {actual}")]
Unexpected {
expected: Expected,
actual: String,
},
#[error("Unexpected field `{0}`")]
UnexpectedField(String),
#[error("YAML merge syntax `<<` is not supported")]
UnsupportedMerge,
}
#[derive(Debug, Error)]
#[error("Error at {location}")]
pub struct YamlError {
#[source]
pub kind: YamlErrorKind,
pub location: SourceLocation,
}
#[derive(Debug)]
pub struct LocatedError<E> {
pub error: E,
pub location: SourceIdLocation,
}
impl<E> LocatedError<E> {
pub fn into_error(self) -> E {
self.error
}
}
impl LocatedError<YamlErrorKind> {
pub fn other(
error: impl Into<Box<dyn StdError + Send + Sync>>,
location: SourceIdLocation,
) -> Self {
Self {
error: YamlErrorKind::Other(error.into()),
location,
}
}
pub(super) fn scan(error: ScanError, source_id: SourceId) -> Self {
let location =
SourceIdLocation::from_marker(source_id, *error.marker());
Self {
error: YamlErrorKind::Scan(error),
location,
}
}
pub(super) fn resolve(self, source_map: &SourceMap) -> YamlError {
YamlError {
kind: self.error,
location: self.location.resolve(source_map),
}
}
pub fn unexpected(expected: Expected, actual: SourcedYaml) -> Self {
let actual_string = match actual.data {
YamlData::Value(Scalar::Null) => "null".into(),
YamlData::Value(Scalar::Boolean(b)) => format!("`{b}`"),
YamlData::Value(Scalar::Integer(i)) => format!("`{i}`"),
YamlData::Value(Scalar::FloatingPoint(f)) => format!("`{f}`"),
YamlData::Value(Scalar::String(s)) => format!("{s:?}"),
YamlData::Tagged(tag, _) => format!("tag `{tag}`"),
YamlData::Sequence(_) => "sequence".into(),
YamlData::Mapping(_) => "mapping".into(),
YamlData::Representation(_, _, _)
| YamlData::Alias(_)
| YamlData::BadValue => yaml_parse_panic(),
};
Self {
location: actual.location,
error: YamlErrorKind::Unexpected {
expected,
actual: actual_string,
},
}
}
}
#[derive(Debug, derive_more::Display)]
pub enum Expected {
#[display("null")]
Null,
#[display("string")]
String,
#[display("boolean")]
Boolean,
#[display("number")]
Number,
#[display("sequence")]
Sequence,
#[display("mapping")]
Mapping,
#[display("{_0:?}")]
Literal(&'static str),
#[display("one of {}", _0.iter().format(", "))]
OneOf(&'static [&'static Self]),
}