use std::fmt;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum ValidationKind {
NonFinite,
OutOfRange,
LengthMismatch,
LimitExceeded,
NestedMetadata,
DuplicateIdentifier,
}
impl ValidationKind {
#[must_use]
pub const fn as_str(self) -> &'static str {
match self {
Self::NonFinite => "non_finite",
Self::OutOfRange => "out_of_range",
Self::LengthMismatch => "length_mismatch",
Self::LimitExceeded => "limit_exceeded",
Self::NestedMetadata => "nested_metadata",
Self::DuplicateIdentifier => "duplicate_identifier",
}
}
}
impl fmt::Display for ValidationKind {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.as_str())
}
}
#[derive(Debug, Clone, Copy)]
pub enum ValidationMeasure {
Count(u64),
Bytes(u64),
Float(f32),
Range { min: f32, max: f32 },
}
impl PartialEq for ValidationMeasure {
fn eq(&self, other: &Self) -> bool {
match (self, other) {
(Self::Count(a), Self::Count(b)) | (Self::Bytes(a), Self::Bytes(b)) => a == b,
(Self::Float(a), Self::Float(b)) => a.to_bits() == b.to_bits(),
(
Self::Range {
min: a_min,
max: a_max,
},
Self::Range {
min: b_min,
max: b_max,
},
) => a_min.to_bits() == b_min.to_bits() && a_max.to_bits() == b_max.to_bits(),
_ => false,
}
}
}
impl Eq for ValidationMeasure {}
impl fmt::Display for ValidationMeasure {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Count(n) | Self::Bytes(n) => write!(f, "{n}"),
Self::Float(v) => write!(f, "{v}"),
Self::Range { min, max } => write!(f, "[{min}, {max}]"),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ValidationError {
pub path: String,
pub kind: ValidationKind,
pub actual: Option<ValidationMeasure>,
pub limit: Option<ValidationMeasure>,
}
impl ValidationError {
#[must_use]
pub fn non_finite(path: impl Into<String>, actual: f32) -> Self {
Self {
path: path.into(),
kind: ValidationKind::NonFinite,
actual: Some(ValidationMeasure::Float(actual)),
limit: None,
}
}
#[must_use]
pub fn out_of_range(path: impl Into<String>, actual: f32, min: f32, max: f32) -> Self {
Self {
path: path.into(),
kind: ValidationKind::OutOfRange,
actual: Some(ValidationMeasure::Float(actual)),
limit: Some(ValidationMeasure::Range { min, max }),
}
}
#[must_use]
pub fn length_mismatch(path: impl Into<String>, actual: usize, expected: usize) -> Self {
Self {
path: path.into(),
kind: ValidationKind::LengthMismatch,
actual: Some(ValidationMeasure::Count(usize_as_u64(actual))),
limit: Some(ValidationMeasure::Count(usize_as_u64(expected))),
}
}
#[must_use]
pub fn limit_exceeded(path: impl Into<String>, actual: usize, limit: usize) -> Self {
Self {
path: path.into(),
kind: ValidationKind::LimitExceeded,
actual: Some(ValidationMeasure::Count(usize_as_u64(actual))),
limit: Some(ValidationMeasure::Count(usize_as_u64(limit))),
}
}
#[must_use]
pub fn byte_limit(path: impl Into<String>, actual: usize, limit: usize) -> Self {
Self {
path: path.into(),
kind: ValidationKind::LimitExceeded,
actual: Some(ValidationMeasure::Bytes(usize_as_u64(actual))),
limit: Some(ValidationMeasure::Bytes(usize_as_u64(limit))),
}
}
#[must_use]
pub fn nested_metadata(path: impl Into<String>) -> Self {
Self {
path: path.into(),
kind: ValidationKind::NestedMetadata,
actual: None,
limit: None,
}
}
#[must_use]
pub fn duplicate_identifier(path: impl Into<String>, identifier: impl fmt::Display) -> Self {
Self {
path: path.into(),
kind: ValidationKind::DuplicateIdentifier,
actual: Some(ValidationMeasure::Count(0)),
limit: None,
}
.with_duplicate_display(identifier)
}
fn with_duplicate_display(mut self, identifier: impl fmt::Display) -> Self {
let label = identifier.to_string();
if let Ok(n) = label.parse::<u64>() {
self.actual = Some(ValidationMeasure::Count(n));
} else {
self.actual = None;
if !self.path.contains(&label) {
self.path = format!("{}:{label}", self.path);
}
}
self
}
}
impl fmt::Display for ValidationError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{} at `{}`", self.kind, self.path)?;
match (&self.actual, &self.limit) {
(Some(actual), Some(limit)) => write!(f, ", actual={actual}, limit={limit}"),
(Some(actual), None) => write!(f, ", actual={actual}"),
(None, Some(limit)) => write!(f, ", limit={limit}"),
(None, None) => Ok(()),
}
}
}
impl std::error::Error for ValidationError {}
pub(crate) fn usize_as_u64(n: usize) -> u64 {
u64::try_from(n).unwrap_or(u64::MAX)
}