jsonschema-rs/jsonschema/src/keywords/pattern.rs

106 lines
2.9 KiB
Rust

use crate::{
compilation::{context::CompilationContext, JSONSchema},
error::{error, no_error, CompilationError, ErrorIterator, ValidationError},
keywords::CompilationResult,
validator::Validate,
};
use regex::{Captures, Regex};
use serde_json::{Map, Value};
use std::ops::Index;
lazy_static::lazy_static! {
static ref CONTROL_GROUPS_RE: Regex = Regex::new(r"\\c[A-Za-z]").expect("Is a valid regex");
}
pub(crate) struct PatternValidator {
original: String,
pattern: Regex,
}
impl PatternValidator {
#[inline]
pub(crate) fn compile(pattern: &Value) -> CompilationResult {
match pattern {
Value::String(item) => {
let pattern = convert_regex(item)?;
Ok(Box::new(PatternValidator {
original: item.clone(),
pattern,
}))
}
_ => Err(CompilationError::SchemaError),
}
}
}
impl Validate for PatternValidator {
fn validate<'a>(&self, _: &'a JSONSchema, instance: &'a Value) -> ErrorIterator<'a> {
if let Value::String(item) = instance {
if !self.pattern.is_match(item) {
return error(ValidationError::pattern(instance, self.original.clone()));
}
}
no_error()
}
fn is_valid(&self, _: &JSONSchema, instance: &Value) -> bool {
if let Value::String(item) = instance {
if !self.pattern.is_match(item) {
return false;
}
}
true
}
}
impl ToString for PatternValidator {
fn to_string(&self) -> String {
format!("pattern: {}", self.pattern)
}
}
// ECMA 262 has differences
fn convert_regex(pattern: &str) -> Result<Regex, regex::Error> {
// replace control chars
let new_pattern = CONTROL_GROUPS_RE.replace_all(pattern, replace_control_group);
Regex::new(
&new_pattern
.replace(r"\d", "[0-9]")
.replace(r"\D", "[^0-9]")
.replace(r"\w", "[A-Za-z]")
.replace(r"\W", "[^A-Za-z]")
.replace(
r"\s",
"[ \t\n\r\u{000b}\u{000c}\u{2003}\u{feff}\u{2029}\u{00a0}]",
)
.replace(
r"\S",
"[^ \t\n\r\u{000b}\u{000c}\u{2003}\u{feff}\u{2029}\u{00a0}]",
),
)
}
#[allow(clippy::integer_arithmetic)]
fn replace_control_group(captures: &Captures) -> String {
// There will be no overflow, because the minimum value is 65 (char 'A')
((captures
.index(0)
.trim_start_matches(r"\c")
.chars()
.next()
.expect("This is always present because of the regex rule. It has [A-Za-z] next")
.to_ascii_uppercase() as u8
- 64) as char)
.to_string()
}
#[inline]
pub(crate) fn compile(
_: &Map<String, Value>,
schema: &Value,
_: &CompilationContext,
) -> Option<CompilationResult> {
Some(PatternValidator::compile(schema))
}