use crate::{Expression, Identifier, Value};
use derive_more::derive::Display;
use indexmap::IndexMap;
use itertools::Itertools;
use serde::de;
use std::{
fmt::Display,
num::{ParseFloatError, ParseIntError},
str::Utf8Error,
};
use thiserror::Error;
use winnow::error::{ContextError, ParseError};
#[derive(Debug, Error)]
#[error("{error}")]
pub struct TemplateParseError {
error: String,
}
impl From<ParseError<&str, ContextError>> for TemplateParseError {
fn from(error: ParseError<&str, ContextError>) -> Self {
Self {
error: error.to_string(),
}
}
}
#[derive(Debug, Error)]
pub enum RenderError {
#[error("Unknown field `{field}`")]
FieldUnknown { field: Identifier },
#[error("Rendering nested template for field `{field}`")]
FieldNested {
field: String,
#[source]
error: Box<Self>,
},
#[error("Unknown function")]
FunctionUnknown,
#[error(transparent)]
Other(Box<dyn std::error::Error + Send + Sync>),
#[error("Not enough arguments")]
TooFewArguments,
#[error("Extra arguments {}", extra_args(.position, .keyword))]
TooManyArguments {
position: Vec<Value>,
keyword: IndexMap<String, Value>,
},
#[error(transparent)]
Value(#[from] ValueError),
#[error("{context}")]
WithContext {
context: Box<RenderErrorContext>,
#[source]
error: Box<Self>,
},
}
impl RenderError {
pub fn other(
error: impl 'static + Into<Box<dyn std::error::Error + Send + Sync>>,
) -> Self {
Self::Other(error.into())
}
#[must_use]
pub fn context(self, context: RenderErrorContext) -> Self {
Self::WithContext {
context: Box::new(context),
error: Box::new(self),
}
}
}
#[derive(Debug, Display)]
pub enum RenderErrorContext {
#[display("{_0}()")]
Function(Identifier),
#[display("argument {argument}={expression}")]
ArgumentRender {
argument: String,
expression: Expression,
},
#[display("argument {argument}={value}")]
ArgumentConvert { argument: String, value: Value },
}
fn extra_args<'a>(
position: &'a [Value],
keyword: &'a IndexMap<String, Value>,
) -> impl 'a + Display {
position
.iter()
.map(|arg| format!("{arg}"))
.chain(
keyword
.iter()
.map(|(name, value)| format!("{name}={value}")),
)
.format(", ")
}
#[derive(Debug)]
pub struct WithValue<E> {
pub value: Value,
pub error: E,
}
impl<E> WithValue<E> {
pub fn new(value: Value, error: impl Into<E>) -> Self {
Self {
value,
error: error.into(),
}
}
pub fn into_error(self) -> E {
self.error
}
}
#[derive(Debug, Error)]
pub enum ValueError {
#[error(transparent)]
Float(#[from] ParseFloatError),
#[error(transparent)]
Integer(#[from] ParseIntError),
#[error("Integer out of range {expected}")]
IntegerRange { expected: String },
#[error(transparent)]
InvalidUtf8(#[from] Utf8Error),
#[error("Error parsing JSON")]
Json(
#[from]
#[source]
serde_json::Error,
),
#[error(transparent)]
Other(Box<dyn std::error::Error + Send + Sync>),
#[error("Expected {expected}")]
Type { expected: Expected },
}
impl ValueError {
pub fn other(
error: impl 'static + Into<Box<dyn std::error::Error + Send + Sync>>,
) -> Self {
Self::Other(error.into())
}
}
impl de::Error for ValueError {
fn custom<T>(msg: T) -> Self
where
T: Display,
{
Self::Other(msg.to_string().into())
}
}
#[derive(Debug, derive_more::Display)]
pub enum Expected {
#[display("null")]
Null,
#[display("boolean")]
Boolean,
#[display("integer")]
Integer,
#[display("float")]
Float,
#[display("string")]
String,
#[display("bytes")]
Bytes,
#[display("array")]
Array,
#[display("one of {}", display_union(_0))]
OneOf(&'static [&'static Self]),
#[display("{_0}")]
Custom(&'static str),
}
fn display_union(values: &[impl Display]) -> String {
match values {
[] => String::new(),
[value] => value.to_string(),
[a, b] => format!("{a} or {b}"),
[head @ .., tail] => format!("{}, or {tail}", head.iter().join(", ")),
}
}
#[cfg(test)]
mod tests {
use super::*;
use rstest::rstest;
#[rstest]
#[case::empty(&[], "")]
#[case::one(&["a"], "a")]
#[case::two(&["a", "b"], "a or b")]
#[case::three(&["a", "b", "c"], "a, b, or c")]
fn test_display_union(#[case] values: &[&str], #[case] expected: &str) {
assert_eq!(display_union(values), expected);
}
}