serde_valid_derive 2.0.2

JSON Schema based validation tool using serde.
Documentation
use std::{hash::Hash, str::FromStr};

use proc_macro2::Span;
use proc_macro2::TokenStream;
use quote::{quote_spanned, ToTokens};

#[derive(Debug, Clone)]
pub struct WithWarnings<T> {
    pub data: T,
    pub warnings: Vec<Warning>,
}

impl<T> WithWarnings<T> {
    pub fn new(data: T) -> Self {
        Self {
            data,
            warnings: vec![],
        }
    }

    pub fn new_with_warnings(data: T, warnings: Vec<Warning>) -> Self {
        Self { data, warnings }
    }

    pub fn from_iter(data: impl IntoIterator<Item = WithWarnings<T>>) -> WithWarnings<Vec<T>> {
        let mut warnings = vec![];
        let data = data
            .into_iter()
            .map(|WithWarnings { data, warnings: w }| {
                warnings.extend(w);
                data
            })
            .collect::<Vec<_>>();
        WithWarnings { data, warnings }
    }
}

impl<T> From<WithWarnings<T>> for WithWarnings<Vec<T>> {
    fn from(with_warnings: WithWarnings<T>) -> Self {
        WithWarnings {
            data: vec![with_warnings.data],
            warnings: with_warnings.warnings,
        }
    }
}

impl<T> From<T> for WithWarnings<T> {
    fn from(data: T) -> Self {
        Self::new(data)
    }
}

#[derive(Debug, Clone)]
pub enum Warning {
    Deprecated {
        ident: syn::Ident,
        note: String,
        span: Span,
    },
}

impl Hash for Warning {
    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
        match self {
            Self::Deprecated { ident, note, .. } => {
                ident.hash(state);
                note.hash(state);
            }
        }
    }
}

impl std::cmp::PartialEq for Warning {
    fn eq(&self, other: &Self) -> bool {
        match (self, other) {
            (
                Self::Deprecated {
                    ident: ident1,
                    note: note1,
                    ..
                },
                Self::Deprecated {
                    ident: ident2,
                    note: note2,
                    ..
                },
            ) => ident1 == ident2 && note1 == note2,
        }
    }
}

impl std::cmp::Eq for Warning {}

impl Warning {
    pub fn add_index(&self, index: usize) -> Self {
        match self {
            Self::Deprecated { ident, note, span } => Self::Deprecated {
                ident: syn::Ident::new(&format!("{}_{}", ident, index), ident.span()),
                note: note.clone(),
                span: *span,
            },
        }
    }
}

impl ToTokens for Warning {
    fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
        match self {
            Self::Deprecated { ident, note, span } => {
                let func_name =
                    TokenStream::from_str(&format!("__{}", ident.to_string().to_lowercase()))
                        .unwrap();

                quote_spanned!(*span =>
                    #[allow(dead_code)]
                    #[allow(clippy::let_unit_value)]
                    fn #func_name() {
                        #[deprecated(note = #note)]
                        #[allow(non_upper_case_globals)]
                        const _deprecated: () = ();
                        let _ = _deprecated;
                    }
                )
                .to_tokens(tokens)
            }
        }
    }
}