use std::borrow::Cow;
use std::cell::Cell;
use std::fmt;
use std::string::{String, ToString as _};
use std::thread_local;
use serde::{de, ser};
use serde_json::error::Category;
use super::why::Why;
const MARKER: &str = "an unfit value";
thread_local! {
static RAISED: Cell<Option<Unfit>> = const { Cell::new(None) };
}
fn exchange(next: Option<Unfit>) -> Option<Unfit> {
RAISED.try_with(|slot| slot.replace(next)).ok().flatten()
}
pub(super) struct Previous(Option<Unfit>);
impl Previous {
pub(super) fn empty() -> Previous {
Previous(exchange(None))
}
}
impl Drop for Previous {
fn drop(&mut self) {
exchange(self.0.take());
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Unfit {
pub path: Cow<'static, str>,
pub why: Why,
}
impl Unfit {
pub fn kind(expected: &'static str) -> Unfit {
Unfit::at_root(Why::Kind(expected.into()))
}
pub fn other(reason: &'static str) -> Unfit {
Unfit::at_root(Why::Other(reason.into()))
}
pub fn range(reason: &'static str) -> Unfit {
Unfit::at_root(Why::Range(reason.into()))
}
pub fn absent() -> Unfit {
Unfit::at_root(Why::Absent)
}
pub fn unnamed() -> Unfit {
Unfit::at_root(Why::Unnamed)
}
fn at_root(why: Why) -> Unfit {
Unfit {
path: Cow::Borrowed(""),
why,
}
}
fn lead(&mut self, steps: &[&str]) {
lead(&mut self.path, steps);
}
pub fn is_kind(&self) -> bool {
matches!(self.why, Why::Kind(_))
}
pub fn field(mut self, name: &str) -> Unfit {
let dot = if name.is_empty() { "" } else { "." };
self.lead(&[dot, name]);
self
}
pub fn step(mut self, step: &'static str) -> Unfit {
match self.path.is_empty() {
true => self.path = Cow::Borrowed(step),
false => lead(&mut self.path, &[step]),
}
self
}
pub fn index(mut self, index: usize) -> Unfit {
self.lead(&["[", &index.to_string(), "]"]);
self
}
pub fn model(mut self, name: &str) -> Unfit {
self.lead(&[name]);
self
}
pub fn either(first: Option<Unfit>, next: Unfit) -> Unfit {
match first {
Some(f) if f.reach() >= next.reach() => f,
_ => next,
}
}
fn reach(&self) -> u8 {
let one_step = self.path.starts_with('.')
&& !self.path.get(1..).unwrap_or_default().contains(['.', '[']);
match &self.why {
Why::Kind(_) if self.path.is_empty() => 0,
Why::Kind(_) => 2,
_ if self.path.is_empty() => 1,
Why::Absent | Why::Unnamed if one_step => 1,
_ => 2,
}
}
pub fn raise<E: de::Error>(self) -> E {
exchange(Some(self));
E::custom(MARKER)
}
pub fn refuse<E: ser::Error>(self) -> E {
exchange(Some(self));
E::custom(MARKER)
}
pub fn raised() -> Option<Unfit> {
exchange(None)
}
pub fn from_json(e: &serde_json::Error) -> Unfit {
Unfit::other(match e.classify() {
Category::Io => "text that can't be read",
Category::Syntax => "text that isn't JSON",
Category::Data => "JSON that doesn't fit",
Category::Eof => "JSON that ends too soon",
})
}
}
fn lead(path: &mut Cow<'static, str>, steps: &[&str]) {
let mut led = String::with_capacity(
steps
.iter()
.map(|s| s.len())
.fold(path.len(), usize::saturating_add),
);
steps.iter().for_each(|s| led.push_str(s));
led.push_str(path);
*path = Cow::Owned(led);
}
impl fmt::Display for Unfit {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let what = match &self.why {
Why::Kind(_) => "expected ",
_ => "",
};
let why = self.why.text();
match &*self.path {
"" => write!(f, "{what}{why}"),
path => write!(f, "{path}: {what}{why}"),
}
}
}
impl std::error::Error for Unfit {}