use std::fmt;
use crate::ErrorCode;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ViolationKind {
Format,
Occurrence,
Property,
Type,
}
impl ViolationKind {
pub fn error_code(self) -> ErrorCode {
match self {
ViolationKind::Format => ErrorCode::FormatViolation,
ViolationKind::Occurrence => ErrorCode::OccurrenceConstraintViolation,
ViolationKind::Property => ErrorCode::PropertyConstraintViolation,
ViolationKind::Type => ErrorCode::TypeConstraintViolation,
}
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct Violation {
pub kind: ViolationKind,
pub message: String,
}
impl Violation {
pub fn new(kind: ViolationKind, message: impl Into<String>) -> Self {
Violation {
kind,
message: message.into(),
}
}
pub fn from_serde(e: serde_json::Error) -> Self {
use serde_json::error::Category;
let msg = e.to_string();
let kind = match e.classify() {
Category::Syntax | Category::Eof | Category::Io => ViolationKind::Format,
Category::Data => {
if msg.starts_with("missing field") {
ViolationKind::Occurrence
} else if msg.starts_with("invalid type") {
ViolationKind::Type
} else if msg.starts_with("unknown variant")
|| msg.starts_with("invalid value")
|| msg.starts_with("invalid length")
{
ViolationKind::Property
} else {
ViolationKind::Format
}
}
};
Violation { kind, message: msg }
}
}
impl fmt::Display for Violation {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(&self.message)
}
}
impl std::error::Error for Violation {}
#[derive(Clone, Copy)]
pub struct Path<'a> {
parent: Option<&'a Path<'a>>,
seg: Seg<'a>,
}
#[derive(Clone, Copy)]
enum Seg<'a> {
Root,
Field(&'a str),
Index(usize),
}
impl<'a> Path<'a> {
pub const fn root() -> Path<'static> {
Path {
parent: None,
seg: Seg::Root,
}
}
pub fn field(&'a self, name: &'a str) -> Path<'a> {
Path {
parent: Some(self),
seg: Seg::Field(name),
}
}
pub fn index(&'a self, i: usize) -> Path<'a> {
Path {
parent: Some(self),
seg: Seg::Index(i),
}
}
}
impl fmt::Display for Path<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if let Some(p) = self.parent {
fmt::Display::fmt(p, f)?;
}
match self.seg {
Seg::Root => f.write_str("$"),
Seg::Field(n) => write!(f, ".{n}"),
Seg::Index(i) => write!(f, "[{i}]"),
}
}
}
pub trait Validate {
fn validate(&self, path: &Path<'_>) -> Result<(), Violation>;
}
impl<T: Validate> Validate for Option<T> {
fn validate(&self, path: &Path<'_>) -> Result<(), Violation> {
match self {
Some(v) => v.validate(path),
None => Ok(()),
}
}
}
impl<T: Validate> Validate for Vec<T> {
fn validate(&self, path: &Path<'_>) -> Result<(), Violation> {
for (i, v) in self.iter().enumerate() {
v.validate(&path.index(i))?;
}
Ok(())
}
}
impl<T: Validate> Validate for Box<T> {
fn validate(&self, path: &Path<'_>) -> Result<(), Violation> {
(**self).validate(path)
}
}
macro_rules! no_constraints {
($($t:ty),*) => { $(impl Validate for $t { #[inline] fn validate(&self, _: &Path<'_>) -> Result<(), Violation> { Ok(()) } })* };
}
no_constraints!(String, i64, f64, bool, crate::DateTime, serde_json::Value);
#[inline]
pub fn max_length(v: &str, max: usize, path: &Path<'_>) -> Result<(), Violation> {
if v.len() > max && v.chars().count() > max {
return Err(Violation::new(
ViolationKind::Property,
format!("{path}: string longer than {max} characters"),
));
}
Ok(())
}
#[inline]
pub fn minimum<T: PartialOrd + fmt::Display>(
v: T,
min: T,
path: &Path<'_>,
) -> Result<(), Violation> {
if v < min {
return Err(Violation::new(
ViolationKind::Property,
format!("{path}: value {v} is below minimum {min}"),
));
}
Ok(())
}
#[inline]
pub fn maximum<T: PartialOrd + fmt::Display>(
v: T,
max: T,
path: &Path<'_>,
) -> Result<(), Violation> {
if v > max {
return Err(Violation::new(
ViolationKind::Property,
format!("{path}: value {v} is above maximum {max}"),
));
}
Ok(())
}
#[inline]
pub fn items<T>(
v: &[T],
min: Option<usize>,
max: Option<usize>,
path: &Path<'_>,
) -> Result<(), Violation> {
if let Some(min) = min
&& v.len() < min
{
return Err(Violation::new(
ViolationKind::Occurrence,
format!("{path}: expected at least {min} items, got {}", v.len()),
));
}
if let Some(max) = max
&& v.len() > max
{
return Err(Violation::new(
ViolationKind::Occurrence,
format!("{path}: expected at most {max} items, got {}", v.len()),
));
}
Ok(())
}
#[inline]
pub fn multiple_of(v: f64, step: f64, path: &Path<'_>) -> Result<(), Violation> {
let q = v / step;
if (q - q.round()).abs() > 1e-6 {
return Err(Violation::new(
ViolationKind::Property,
format!("{path}: value {v} is not a multiple of {step}"),
));
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn path_rendering() {
let r = Path::root();
let a = r.field("a");
let b = a.index(3);
let c = b.field("c");
assert_eq!(c.to_string(), "$.a[3].c");
}
#[test]
fn serde_classification() {
#[derive(serde::Deserialize, Debug)]
#[serde(deny_unknown_fields)]
#[allow(dead_code)]
struct T {
a: i64,
e: Option<E>,
}
#[derive(serde::Deserialize, Debug)]
enum E {
X,
}
let k = |s: &str| Violation::from_serde(serde_json::from_str::<T>(s).unwrap_err()).kind;
assert_eq!(k("{}"), ViolationKind::Occurrence);
assert_eq!(k(r#"{"a":"x"}"#), ViolationKind::Type);
assert_eq!(k(r#"{"a":1,"e":"Y"}"#), ViolationKind::Property);
assert_eq!(k(r#"{"a":1,"z":1}"#), ViolationKind::Format);
assert_eq!(k(r#"{"a":1"#), ViolationKind::Format);
}
#[test]
fn helpers() {
let p = Path::root();
assert!(max_length("ééé", 3, &p).is_ok());
assert!(max_length("éééé", 3, &p).is_err());
assert!(multiple_of(8.1, 0.1, &p).is_ok());
assert!(multiple_of(8.15, 0.1, &p).is_err());
assert!(items(&[1, 2], Some(1), Some(2), &p).is_ok());
assert!(items::<i32>(&[], Some(1), None, &p).is_err());
}
}