use serde_json::Value;
use std::sync::Arc;
use crate::error::{IssueCode, ValidationIssue, VldError};
pub trait Rule<T: ?Sized>: Send + Sync + 'static {
fn key(&self) -> &str;
fn check(&self, value: &T) -> bool;
fn message(&self) -> Option<String> {
None
}
#[cfg(feature = "openapi")]
fn json_schema(&self) -> Option<Value> {
None
}
}
pub(crate) fn failure<T: ?Sized, R: Rule<T> + ?Sized>(
rule: &R,
msg: Option<String>,
) -> ValidationIssue {
let code = IssueCode::Custom {
code: rule.key().to_string(),
};
let msg = msg.or_else(|| rule.message());
ValidationIssue::with_override(code, msg.as_ref())
}
pub fn from_fn<T: ?Sized, F>(key: impl Into<String>, check: F) -> FnRule<F>
where
F: Fn(&T) -> bool + Send + Sync + 'static,
{
FnRule {
key: key.into(),
check,
message: None,
#[cfg(feature = "openapi")]
schema: None,
}
}
pub struct FnRule<F> {
key: String,
check: F,
message: Option<String>,
#[cfg(feature = "openapi")]
schema: Option<Value>,
}
impl<F> FnRule<F> {
pub fn message(mut self, msg: impl Into<String>) -> Self {
self.message = Some(msg.into());
self
}
#[cfg(feature = "openapi")]
pub fn json_schema(mut self, keywords: Value) -> Self {
self.schema = Some(keywords);
self
}
}
impl<T: ?Sized, F> Rule<T> for FnRule<F>
where
F: Fn(&T) -> bool + Send + Sync + 'static,
{
fn key(&self) -> &str {
&self.key
}
fn check(&self, value: &T) -> bool {
(self.check)(value)
}
fn message(&self) -> Option<String> {
self.message.clone()
}
#[cfg(feature = "openapi")]
fn json_schema(&self) -> Option<Value> {
self.schema.clone()
}
}
impl<T: ?Sized, R: Rule<T> + ?Sized> Rule<T> for Arc<R> {
fn key(&self) -> &str {
(**self).key()
}
fn check(&self, value: &T) -> bool {
(**self).check(value)
}
fn message(&self) -> Option<String> {
(**self).message()
}
#[cfg(feature = "openapi")]
fn json_schema(&self) -> Option<Value> {
(**self).json_schema()
}
}
#[cfg(feature = "regex")]
#[cfg_attr(docsrs, doc(cfg(feature = "regex")))]
#[derive(Clone)]
pub struct Pattern(regex_lite::Regex);
#[cfg(feature = "regex")]
impl Pattern {
pub fn new(pattern: &str) -> Result<Self, PatternError> {
regex_lite::Regex::new(pattern)
.map(Self)
.map_err(|e| PatternError(e.to_string()))
}
pub fn as_str(&self) -> &str {
self.0.as_str()
}
pub fn is_match(&self, s: &str) -> bool {
self.0.is_match(s)
}
}
#[cfg(feature = "regex")]
impl std::fmt::Debug for Pattern {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_tuple("Pattern").field(&self.as_str()).finish()
}
}
#[cfg(feature = "regex")]
#[cfg_attr(docsrs, doc(cfg(feature = "regex")))]
pub trait IntoPattern {
fn into_pattern(self) -> Pattern;
}
#[cfg(feature = "regex")]
impl IntoPattern for Pattern {
fn into_pattern(self) -> Pattern {
self
}
}
#[cfg(feature = "regex")]
impl IntoPattern for &str {
#[track_caller]
fn into_pattern(self) -> Pattern {
Pattern::new(self).unwrap_or_else(|e| panic!("vld: {e} (`{self}`)"))
}
}
#[cfg(feature = "regex")]
impl IntoPattern for String {
#[track_caller]
fn into_pattern(self) -> Pattern {
self.as_str().into_pattern()
}
}
#[cfg(feature = "regex")]
#[cfg_attr(docsrs, doc(cfg(feature = "regex")))]
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct PatternError(String);
#[cfg(feature = "regex")]
impl std::fmt::Display for PatternError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "invalid pattern: {}", self.0)
}
}
#[cfg(feature = "regex")]
impl std::error::Error for PatternError {}
pub struct ZRule<S, R> {
inner: S,
rule: R,
msg: Option<String>,
}
impl<S, R> ZRule<S, R> {
pub(crate) fn new(inner: S, rule: R, msg: Option<String>) -> Self {
Self { inner, rule, msg }
}
}
impl<S: crate::schema::VldSchema, R: Rule<S::Output>> ZRule<S, R> {
fn finish(&self, out: S::Output) -> Result<S::Output, VldError> {
if self.rule.check(&out) {
Ok(out)
} else {
Err(failure::<S::Output, R>(&self.rule, self.msg.clone()).into())
}
}
}
impl<S, R> crate::schema::VldSchema for ZRule<S, R>
where
S: crate::schema::VldSchema,
R: Rule<S::Output>,
{
type Output = S::Output;
fn parse_value(&self, value: &Value) -> Result<S::Output, VldError> {
self.finish(self.inner.parse_value(value)?)
}
fn parse_missing(&self) -> Result<S::Output, VldError> {
self.finish(self.inner.parse_missing()?)
}
fn output_json(&self, o: &Self::Output) -> Option<Value> {
self.inner.output_json(o)
}
}
#[cfg(feature = "openapi")]
impl<S, R> crate::openapi::JsonSchema for ZRule<S, R>
where
S: crate::openapi::JsonSchema,
R: Rule<<S as crate::schema::VldSchema>::Output>,
S: crate::schema::VldSchema,
{
fn json_schema(&self) -> Value {
let mut schema = self.inner.json_schema();
if let Some(extra) = self.rule.json_schema() {
merge_keywords(&mut schema, extra);
}
schema
}
}
#[cfg(feature = "openapi")]
impl<S: crate::openapi::CollectNestedSchemas, R> crate::openapi::CollectNestedSchemas
for ZRule<S, R>
{
fn collect_nested_schemas(&self, out: &mut Vec<crate::openapi::NestedSchemaEntry>) {
self.inner.collect_nested_schemas(out);
}
}
#[cfg(feature = "openapi")]
pub(crate) fn merge_keywords(schema: &mut Value, extra: Value) {
let (Some(base), Value::Object(extra)) = (schema.as_object_mut(), extra) else {
return;
};
let mut clash = serde_json::Map::new();
for (k, v) in extra {
if base.contains_key(&k) {
clash.insert(k, v);
} else {
base.insert(k, v);
}
}
if !clash.is_empty() {
let all_of = base
.entry("allOf")
.or_insert_with(|| Value::Array(Vec::new()));
if let Value::Array(items) = all_of {
items.push(Value::Object(clash));
}
}
}