#![allow(dead_code)]
mod rc_list;
use std::borrow::Cow;
use compact_str::{CompactString, ToCompactString};
use smallvec::SmallVec;
use self::rc_list::List;
#[derive(Clone, Debug)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct Report {
errors: Vec<(Path, Error)>,
}
impl Report {
#[allow(clippy::new_without_default)]
pub fn new() -> Self {
Self { errors: Vec::new() }
}
pub fn append(&mut self, path: Path, error: Error) {
self.errors.push((path, error));
}
pub fn iter(&self) -> impl Iterator<Item = &(Path, Error)> {
self.errors.iter()
}
pub fn is_empty(&self) -> bool {
self.errors.is_empty()
}
}
impl std::fmt::Display for Report {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
for (path, error) in self.iter() {
if path.is_empty() {
writeln!(f, "{error}")?;
} else {
writeln!(f, "{path}: {error}")?;
}
}
Ok(())
}
}
impl std::error::Error for Report {}
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct Error {
message: CompactString,
}
impl Error {
pub fn new(message: impl ToCompactString) -> Self {
Self {
message: message.to_compact_string(),
}
}
pub fn message(&self) -> &str {
self.message.as_ref()
}
}
impl std::fmt::Display for Error {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(f, "{}", self.message)
}
}
impl std::error::Error for Error {}
#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct Path {
components: List<(Kind, CompactString)>,
}
#[doc(hidden)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))]
pub enum Kind {
None,
Key,
Index,
}
#[derive(Default)]
pub struct NoKey(());
impl std::fmt::Display for NoKey {
fn fmt(&self, _: &mut std::fmt::Formatter) -> std::fmt::Result {
Ok(())
}
}
pub trait PathComponentKind: std::fmt::Display + ToCompactString {
fn component_kind() -> Kind;
}
macro_rules! impl_path_component_kind {
($(@$($G:lifetime)*;)? $T:ty => $which:ident) => {
impl $(<$($G),*>)? PathComponentKind for $T {
fn component_kind() -> Kind {
Kind::$which
}
}
}
}
impl_path_component_kind!(usize => Index);
impl_path_component_kind!(@'a; &'a str => Key);
impl_path_component_kind!(@'a; Cow<'a, str> => Key);
impl_path_component_kind!(String => Key);
impl_path_component_kind!(CompactString => Key);
impl_path_component_kind!(NoKey => None);
impl<'a, T: PathComponentKind> PathComponentKind for &'a T {
fn component_kind() -> Kind {
T::component_kind()
}
}
impl Path {
pub fn empty() -> Self {
Self {
components: List::new(),
}
}
pub fn len(&self) -> usize {
self.components.len()
}
pub fn is_empty(&self) -> bool {
self.components.is_empty()
}
pub fn new<C: PathComponentKind>(component: C) -> Self {
Self {
components: List::new().append((C::component_kind(), component.to_compact_string())),
}
}
pub fn join<C: PathComponentKind>(&self, component: C) -> Self {
Self {
components: self
.components
.append((C::component_kind(), component.to_compact_string())),
}
}
#[doc(hidden)]
pub fn __iter(
&self,
) -> impl DoubleEndedIterator<Item = (Kind, &CompactString)> + ExactSizeIterator {
let mut components = TempComponents::with_capacity(self.components.len());
for (kind, component) in self.components.iter() {
components.push((*kind, component));
}
components.into_iter()
}
}
type TempComponents<'a> = SmallVec<[(Kind, &'a CompactString); 8]>;
impl std::fmt::Debug for Path {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
struct Components<'a> {
path: &'a Path,
}
impl<'a> std::fmt::Debug for Components<'a> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let mut list = f.debug_list();
list.entries(self.path.__iter().rev().map(|(_, c)| c))
.finish()
}
}
f.debug_struct("Path")
.field("components", &Components { path: self })
.finish()
}
}
impl std::fmt::Display for Path {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
let mut components = self.__iter().rev().peekable();
let mut first = true;
while let Some((kind, component)) = components.next() {
if first && kind == Kind::Index {
f.write_str("[")?;
}
first = false;
f.write_str(component.as_str())?;
if kind == Kind::Index {
f.write_str("]")?;
}
if let Some((kind, _)) = components.peek() {
match kind {
Kind::None => {}
Kind::Key => f.write_str(".")?,
Kind::Index => f.write_str("[")?,
}
}
}
Ok(())
}
}
#[cfg(feature = "serde")]
impl serde::Serialize for Path {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
use serde::ser::SerializeSeq as _;
let components = self.__iter().rev();
let mut seq = serializer.serialize_seq(Some(components.len()))?;
for component in components {
seq.serialize_element(&component)?;
}
seq.end()
}
}
#[cfg(feature = "serde")]
impl<'de> serde::Deserialize<'de> for Path {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
let mut components = List::new();
for v in SmallVec::<[(Kind, CompactString); 8]>::deserialize(deserializer)? {
components = components.append(v);
}
Ok(Path { components })
}
}
#[cfg(test)]
mod tests {
use super::*;
const _: () = {
fn assert<T: Send>() {}
let _ = assert::<Report>;
};
#[test]
fn path_join() {
let path = Path::new("a").join("b").join("c");
assert_eq!(path.to_string(), "a.b.c");
}
#[test]
fn report_select() {
let mut report = Report::new();
report.append(Path::new("a").join("b"), Error::new("lol"));
report.append(
Path::new("a").join("b").join("c"),
Error::new("that seems wrong"),
);
report.append(Path::new("a").join("b").join("c"), Error::new("pog"));
report.append(Path::new("array").join("0").join("c"), Error::new("pog"));
assert_eq!(
crate::select!(report, a.b.c).collect::<Vec<_>>(),
[&Error::new("that seems wrong"), &Error::new("pog")]
);
assert_eq!(
crate::select!(report, array[0].c).collect::<Vec<_>>(),
[&Error::new("pog")]
);
}
#[cfg(feature = "serde")]
mod serde {
use super::*;
#[test]
fn roundtrip_serde() {
let mut report = Report::new();
report.append(Path::new("a").join(0), Error::new("lorem"));
report.append(Path::new("a").join(1), Error::new("ispum"));
report.append(Path::new("a").join(2), Error::new("dolor"));
report.append(Path::new("b").join("c"), Error::new("dolor"));
let de: Report =
serde_json::from_str(&serde_json::to_string(&report).unwrap()).unwrap();
assert_eq!(report.errors, de.errors);
}
}
}