use std::{borrow::Cow, fmt, slice};
use tracing::error;
use crate::json;
#[doc(hidden)]
#[macro_export]
macro_rules! into_caveat {
($kind:ident<$life:lifetime>) => {
impl<$life> IntoCaveat for $kind<$life> {
fn into_caveat<K: $crate::warning::Kind>(
self,
warnings: $crate::warning::Set<K>,
) -> $crate::Caveat<Self, K> {
$crate::Caveat::new(self, warnings)
}
}
};
($kind:path) => {
impl IntoCaveat for $kind {
fn into_caveat<K: $crate::warning::Kind>(
self,
warnings: $crate::warning::Set<K>,
) -> $crate::Caveat<Self, K> {
$crate::Caveat::new(self, warnings)
}
}
};
}
macro_rules! into_caveat_all {
($($kind:ty),+) => {
$(impl IntoCaveat for $kind {
fn into_caveat<K: Kind>(
self,
warnings: Set<K>,
) -> $crate::Caveat<Self, K> {
$crate::Caveat::new(self, warnings)
}
})+
};
}
pub type Verdict<T, K> = Result<Caveat<T, K>, Set<K>>;
#[derive(Debug)]
pub struct Caveat<T, K: Kind> {
value: T,
warnings: Set<K>,
}
impl<T, K> Caveat<T, K>
where
T: IntoCaveat,
K: Kind,
{
pub(crate) fn new(value: T, warnings: Set<K>) -> Self {
Self { value, warnings }
}
}
impl<T, K> Caveat<T, K>
where
K: Kind,
{
pub fn into_parts(self) -> (T, Set<K>) {
let Self { value, warnings } = self;
(value, warnings)
}
pub fn ignore_warnings(self) -> T {
self.value
}
pub fn map<U, F: FnOnce(T) -> U>(self, op: F) -> Caveat<U, K> {
let Self { value, warnings } = self;
Caveat {
value: op(value),
warnings,
}
}
}
pub(crate) trait GatherWarnings<T, K>
where
K: Kind,
{
type Output;
fn gather_warnings_into<KA>(self, warnings: &mut Set<KA>) -> Self::Output
where
K: Into<KA>,
KA: Kind;
}
impl<T, K> GatherWarnings<T, K> for Caveat<T, K>
where
K: Kind,
{
type Output = T;
fn gather_warnings_into<KA>(self, warnings: &mut Set<KA>) -> Self::Output
where
K: Into<KA>,
KA: Kind,
{
let Self {
value,
warnings: inner_warnings,
} = self;
warnings.0.extend(inner_warnings.0.into_iter().map(|warn| {
let Warning { kind, elem_id } = warn;
Warning {
kind: kind.into(),
elem_id,
}
}));
value
}
}
impl<T, K> GatherWarnings<T, K> for Option<Caveat<T, K>>
where
K: Kind,
{
type Output = Option<T>;
fn gather_warnings_into<KA>(self, warnings: &mut Set<KA>) -> Self::Output
where
K: Into<KA>,
KA: Kind,
{
match self {
Some(cv) => Some(cv.gather_warnings_into(warnings)),
None => None,
}
}
}
impl<T, K, E> GatherWarnings<T, K> for Result<Caveat<T, K>, E>
where
K: Kind,
E: std::error::Error,
{
type Output = Result<T, E>;
fn gather_warnings_into<KA>(self, warnings: &mut Set<KA>) -> Self::Output
where
K: Into<KA>,
KA: Kind,
{
match self {
Ok(cv) => Ok(cv.gather_warnings_into(warnings)),
Err(err) => Err(err),
}
}
}
impl<T, K> GatherWarnings<T, K> for Vec<Caveat<T, K>>
where
K: Kind,
{
type Output = Vec<T>;
fn gather_warnings_into<KA>(self, warnings: &mut Set<KA>) -> Self::Output
where
K: Into<KA>,
KA: Kind,
{
self.into_iter()
.map(|cv| cv.gather_warnings_into(warnings))
.collect()
}
}
pub trait IntoCaveat: Sized {
fn into_caveat<K: Kind>(self, warnings: Set<K>) -> Caveat<Self, K>;
}
into_caveat_all!(
(),
bool,
char,
u8,
u16,
u32,
u64,
u128,
i8,
i16,
i32,
i64,
i128
);
impl IntoCaveat for Cow<'_, str> {
fn into_caveat<K: Kind>(self, warnings: Set<K>) -> Caveat<Self, K> {
Caveat::new(self, warnings)
}
}
impl<T> IntoCaveat for Option<T>
where
T: IntoCaveat,
{
fn into_caveat<K: Kind>(self, warnings: Set<K>) -> Caveat<Self, K> {
Caveat::new(self, warnings)
}
}
pub trait VerdictExt<T, K: Kind> {
fn ok_caveat(self) -> Caveat<Option<T>, K>;
}
impl<T, K: Kind> VerdictExt<T, K> for Verdict<T, K>
where
T: IntoCaveat,
{
fn ok_caveat(self) -> Caveat<Option<T>, K> {
match self {
Ok(v) => {
let (v, warnings) = v.into_parts();
Some(v).into_caveat(warnings)
}
Err(warnings) => None.into_caveat(warnings),
}
}
}
#[macro_export]
#[doc(hidden)]
macro_rules! from_warning_set_to {
($kind_a:path => $kind_b:path) => {
impl From<$crate::warning::Set<$kind_a>> for $crate::warning::Set<$kind_b> {
fn from(set_a: warning::Set<$kind_a>) -> Self {
set_a.into_set()
}
}
};
}
pub trait OptionExt<T, K>
where
K: Kind,
{
fn exit_with_warning<F>(self, warnings: Set<K>, f: F) -> Verdict<T, K>
where
F: FnOnce() -> Warning<K>;
}
impl<T, K> OptionExt<T, K> for Option<T>
where
T: IntoCaveat,
K: Kind,
{
fn exit_with_warning<F>(self, mut warnings: Set<K>, f: F) -> Verdict<T, K>
where
F: FnOnce() -> Warning<K>,
{
if let Some(v) = self {
Ok(v.into_caveat(warnings))
} else {
warnings.push(f());
Err(warnings)
}
}
}
#[derive(Debug)]
pub struct Warning<K: Kind> {
kind: K,
elem_id: Option<json::ElemId>,
}
impl<K: Kind> Warning<K> {
pub(crate) const fn with_elem(kind: K, elem: &json::Element<'_>) -> Warning<K> {
Warning {
kind,
elem_id: Some(elem.id()),
}
}
pub(crate) const fn only_kind(kind: K) -> Warning<K> {
Warning {
kind,
elem_id: None,
}
}
pub fn id(&self) -> Cow<'static, str> {
self.kind.id()
}
pub fn kind(&self) -> &K {
&self.kind
}
pub fn into_kind(self) -> K {
self.kind
}
}
impl<K: Kind> fmt::Display for Warning<K> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.kind)
}
}
pub trait Kind: Sized + fmt::Debug + fmt::Display {
fn id(&self) -> Cow<'static, str>;
}
#[derive(Debug)]
pub struct Set<K: Kind>(Vec<Warning<K>>);
impl<K: Kind> Set<K> {
pub(crate) fn new() -> Self {
Self(vec![])
}
fn push(&mut self, warning: Warning<K>) {
self.0.push(warning);
}
pub(crate) fn with_elem(&mut self, kind: K, elem: &json::Element<'_>) {
self.push(Warning::with_elem(kind, elem));
}
pub(crate) fn only_kind(&mut self, kind: K) {
self.push(Warning::only_kind(kind));
}
pub(crate) fn into_set<KB>(self) -> Set<KB>
where
KB: Kind + From<K>,
{
let warnings = self
.0
.into_iter()
.map(|warn| {
let Warning { kind, elem_id } = warn;
Warning {
kind: kind.into(),
elem_id,
}
})
.collect();
Set(warnings)
}
pub(crate) fn into_kind_vec(self) -> Vec<K> {
self.0.into_iter().map(Warning::into_kind).collect()
}
pub(crate) fn to_kind_vec(&self) -> Vec<&K> {
self.0.iter().map(Warning::kind).collect()
}
pub fn is_empty(&self) -> bool {
self.0.is_empty()
}
pub fn len(&self) -> usize {
self.0.len()
}
pub fn into_report(self) -> Report<K> {
Report::new(self)
}
}
#[derive(Debug)]
pub struct Report<K>
where
K: Kind,
{
groups: Vec<Group<K>>,
}
#[derive(Debug)]
struct Group<K> {
id: Option<json::ElemId>,
warning_kinds: Vec<K>,
}
impl<K> Report<K>
where
K: Kind,
{
fn new(warnings: Set<K>) -> Self {
let Set(mut warnings) = warnings;
warnings.sort_unstable_by(|a, b| a.elem_id.cmp(&b.elem_id));
let mut warnings = warnings.into_iter();
let Some(first) = warnings.next() else {
return Self { groups: vec![] };
};
let mut groups = vec![Group {
id: first.elem_id,
warning_kinds: vec![first.kind],
}];
for warn in warnings {
if let Some(s) = groups.last_mut() {
if warn.elem_id == s.id {
s.warning_kinds.push(warn.kind);
} else {
groups.push(Group {
id: warn.elem_id,
warning_kinds: vec![warn.kind],
});
}
}
}
Self { groups }
}
pub fn iter<'a, 'bin>(&'a self, root: &'a json::Element<'bin>) -> ReportIter<'a, 'bin, K> {
ReportIter {
walker: json::walk::DepthFirst::new(root),
groups: self.groups.iter(),
}
}
pub fn is_empty(&self) -> bool {
self.groups.is_empty()
}
pub fn len(&self) -> usize {
self.groups.len()
}
}
pub struct ReportIter<'a, 'bin, K: Kind> {
walker: json::walk::DepthFirst<'a, 'bin>,
groups: slice::Iter<'a, Group<K>>,
}
impl<'a, 'bin, K: Kind> Iterator for ReportIter<'a, 'bin, K> {
type Item = ElementReport<'a, 'bin, K>;
fn next(&mut self) -> Option<Self::Item> {
let group = self.groups.next()?;
loop {
let Some(element) = self.walker.next() else {
if let Some(id) = group.id {
error!("An Element with id: `{id}` was not found");
} else {
error!("An Element without an id was not found");
}
return None;
};
if let Some(id) = group.id {
if element.id() == id {
return Some(ElementReport {
element,
warnings: &group.warning_kinds,
});
}
}
}
}
}
pub struct ElementReport<'a, 'bin, K: Kind> {
pub element: &'a json::Element<'bin>,
pub warnings: &'a [K],
}
impl<K: Kind> fmt::Debug for ElementReport<'_, '_, K> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("ElementReport")
.field("element", &self.element.path())
.field("warnings", &self.warnings)
.finish()
}
}
#[cfg(test)]
mod test {
use std::{ops, slice};
use assert_matches::assert_matches;
use crate::json;
use super::{Caveat, Kind, Set, Warning};
impl<K: Kind> Warning<K> {
pub(crate) fn elem_id(&self) -> Option<json::ElemId> {
self.elem_id
}
}
impl<K: Kind> Set<K> {
pub fn into_vec(self) -> Vec<Warning<K>> {
self.0
}
pub(crate) fn into_parts_vec(self) -> Vec<(K, Option<json::ElemId>)> {
self.0
.into_iter()
.map(|Warning { kind, elem_id }| (kind, elem_id))
.collect()
}
pub fn as_slice(&self) -> &[Warning<K>] {
self.0.as_slice()
}
pub fn iter(&self) -> slice::Iter<'_, Warning<K>> {
self.0.iter()
}
}
impl<K: Kind> ops::Deref for Set<K> {
type Target = [Warning<K>];
fn deref(&self) -> &[Warning<K>] {
self.as_slice()
}
}
impl<K: Kind> IntoIterator for Set<K> {
type Item = Warning<K>;
type IntoIter = <Vec<Self::Item> as IntoIterator>::IntoIter;
fn into_iter(self) -> Self::IntoIter {
self.0.into_iter()
}
}
impl<'a, K: Kind> IntoIterator for &'a Set<K> {
type Item = &'a Warning<K>;
type IntoIter = slice::Iter<'a, Warning<K>>;
fn into_iter(self) -> Self::IntoIter {
self.0.iter()
}
}
impl<T, K> Caveat<T, K>
where
K: Kind,
{
pub fn unwrap(self) -> T {
let Self { value, warnings } = self;
assert_matches!(warnings.into_vec().as_slice(), []);
value
}
}
}
#[cfg(test)]
mod test_report {
use std::fmt;
use assert_matches::assert_matches;
use crate::{json, test, warning::ElementReport};
use super::{Kind, Report, Set, Warning};
#[derive(Debug)]
enum WarningKind {
Root,
One,
OneAgain,
Three,
}
impl Kind for WarningKind {
fn id(&self) -> std::borrow::Cow<'static, str> {
match self {
WarningKind::Root => "root".into(),
WarningKind::One => "one".into(),
WarningKind::OneAgain => "one_again".into(),
WarningKind::Three => "three".into(),
}
}
}
impl fmt::Display for WarningKind {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
WarningKind::Root => write!(f, "NopeRoot"),
WarningKind::One => write!(f, "NopeOne"),
WarningKind::OneAgain => write!(f, "NopeOneAgain"),
WarningKind::Three => write!(f, "NopeThree"),
}
}
}
#[test]
fn should_group_warnings() {
const JSON: &str = r#"{
"field_one#": "one",
"field_two": "two",
"field_three": "three"
}"#;
test::setup();
let elem = parse(JSON);
let mut warnings = Set::<WarningKind>::new();
warnings.push(Warning {
kind: WarningKind::Root,
elem_id: Some(Into::into(0)),
});
warnings.push(Warning {
kind: WarningKind::Three,
elem_id: Some(Into::into(3)),
});
warnings.push(Warning {
kind: WarningKind::One,
elem_id: Some(Into::into(1)),
});
warnings.push(Warning {
kind: WarningKind::OneAgain,
elem_id: Some(Into::into(1)),
});
let report = Report::new(warnings);
let mut iter = report.iter(&elem);
let ElementReport { element, warnings } = iter.next().unwrap();
assert!(
element.value().is_object(),
"The root object should be reported first"
);
assert_eq!(
element.id(),
json::ElemId::from(0),
"The root object should be reported first"
);
assert_matches!(warnings, [WarningKind::Root]);
let ElementReport { element, warnings } = iter.next().unwrap();
assert_eq!(element.value().as_raw_str().unwrap().as_raw(), "one");
assert_eq!(element.id(), json::ElemId::from(1));
assert_matches!(
warnings,
[WarningKind::One, WarningKind::OneAgain],
"[`json::Element`] 1 should have two warnings"
);
let ElementReport { element, warnings } = iter.next().unwrap();
assert_eq!(element.value().as_raw_str().unwrap().as_raw(), "three");
assert_eq!(element.id(), json::ElemId::from(3));
assert_matches!(warnings, [WarningKind::Three]);
}
fn parse(json: &str) -> json::Element<'_> {
json::parse(json).unwrap()
}
}