use std::collections::HashMap;
use std::fmt;
use std::sync::Arc;
use std::time::Duration;
use serde_json::Value;
use crate::dialect::Dialect;
pub type DocumentResolver = Arc<dyn Fn(&str) -> Option<Value> + Send + Sync>;
pub type FormatValidator = Arc<dyn Fn(&str) -> bool + Send + Sync>;
#[derive(Clone)]
pub struct CompileOptions {
pub default_dialect: Dialect,
pub assert_format: Option<bool>,
pub assert_format_in_legacy_drafts: bool,
pub assert_content: bool,
pub formats: HashMap<String, FormatValidator>,
pub resolve_document: Option<DocumentResolver>,
pub base_uri: Option<String>,
pub entry_point: Option<String>,
pub max_depth: u32,
pub pattern_match_limit: Option<Duration>,
}
pub const DEFAULT_PATTERN_MATCH_LIMIT: Duration = Duration::from_secs(1);
impl Default for CompileOptions {
fn default() -> Self {
CompileOptions {
default_dialect: Dialect::Draft202012,
assert_format: None,
assert_format_in_legacy_drafts: false,
assert_content: true,
formats: HashMap::new(),
resolve_document: None,
base_uri: None,
entry_point: None,
max_depth: 128,
pattern_match_limit: Some(DEFAULT_PATTERN_MATCH_LIMIT),
}
}
}
impl fmt::Debug for CompileOptions {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("CompileOptions")
.field("default_dialect", &self.default_dialect)
.field("assert_format", &self.assert_format)
.field("assert_format_in_legacy_drafts", &self.assert_format_in_legacy_drafts)
.field("assert_content", &self.assert_content)
.field("formats", &self.formats.keys().collect::<Vec<_>>())
.field("resolve_document", &self.resolve_document.is_some())
.field("base_uri", &self.base_uri)
.field("entry_point", &self.entry_point)
.field("max_depth", &self.max_depth)
.field("pattern_match_limit", &self.pattern_match_limit)
.finish()
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct SchemaCompilationError {
message: String,
kind: SchemaCompilationErrorKind,
}
#[derive(Clone, Debug, PartialEq, Eq)]
#[non_exhaustive]
pub enum SchemaCompilationErrorKind {
Other,
InvalidPattern,
PatternTooLarge(PatternTooLargeError),
PatternMatchLimit(PatternMatchLimitError),
}
impl SchemaCompilationError {
pub(crate) fn new(message: impl Into<String>) -> Self {
SchemaCompilationError { message: message.into(), kind: SchemaCompilationErrorKind::Other }
}
pub(crate) fn of_kind(message: impl Into<String>, kind: SchemaCompilationErrorKind) -> Self {
SchemaCompilationError { message: message.into(), kind }
}
pub fn message(&self) -> &str {
&self.message
}
pub fn kind(&self) -> &SchemaCompilationErrorKind {
&self.kind
}
}
impl fmt::Display for SchemaCompilationError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(&self.message)
}
}
impl std::error::Error for SchemaCompilationError {}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct PatternTooLargeError {
pattern: String,
keyword: &'static str,
limit: &'static str,
value: usize,
reason: String,
}
impl PatternTooLargeError {
pub(crate) fn new(pattern: &str, keyword: &'static str, limit: &'static str, value: usize, reason: String) -> Self {
PatternTooLargeError { pattern: pattern.to_string(), keyword, limit, value, reason }
}
pub fn pattern(&self) -> &str {
&self.pattern
}
pub fn keyword(&self) -> &str {
self.keyword
}
pub fn limit(&self) -> &str {
self.limit
}
pub fn value(&self) -> usize {
self.value
}
}
impl fmt::Display for PatternTooLargeError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"The regular expression {} in {} is too large to read as an ECMA-262 regular expression: {}.",
crate::pattern::shown_pattern(&self.pattern),
self.keyword,
self.reason
)
}
}
impl std::error::Error for PatternTooLargeError {}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct PatternMatchLimitError {
pattern: String,
limit: Duration,
memory: bool,
}
impl PatternMatchLimitError {
pub(crate) fn new(pattern: &str, limit: Duration, memory: bool) -> Self {
PatternMatchLimitError { pattern: pattern.to_string(), limit, memory }
}
pub fn pattern(&self) -> &str {
&self.pattern
}
pub fn limit(&self) -> Duration {
self.limit
}
pub fn is_memory(&self) -> bool {
self.memory
}
}
impl fmt::Display for PatternMatchLimitError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let pattern = crate::pattern::shown_pattern(&self.pattern);
if self.memory {
write!(
f,
"A match of the regular expression {pattern} was abandoned: it needed more than the limit of \
1,048,576 backtracking entries."
)
} else {
write!(
f,
"A match of the regular expression {pattern} was abandoned: it took longer than the limit of {:?}.",
self.limit
)
}
}
}
impl std::error::Error for PatternMatchLimitError {}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct SchemaEvaluationDepthError;
impl fmt::Display for SchemaEvaluationDepthError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(
"The validation was abandoned at a limit: the schema recursed in place beyond the maximum depth, or a \
match of a regular expression reached its limit. The try_ functions say which.",
)
}
}
impl std::error::Error for SchemaEvaluationDepthError {}
#[derive(Clone, Debug, PartialEq, Eq)]
#[non_exhaustive]
pub enum SchemaEvaluationError {
InvalidJson(crate::JsonParseError),
DepthExceeded(SchemaEvaluationDepthError),
PatternMatchLimit(PatternMatchLimitError),
}
impl fmt::Display for SchemaEvaluationError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
SchemaEvaluationError::InvalidJson(e) => write!(f, "invalid JSON: {e}"),
SchemaEvaluationError::DepthExceeded(_) => {
f.write_str("The schema recursed in place beyond the maximum depth.")
}
SchemaEvaluationError::PatternMatchLimit(e) => e.fmt(f),
}
}
}
impl std::error::Error for SchemaEvaluationError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
SchemaEvaluationError::InvalidJson(e) => Some(e),
SchemaEvaluationError::DepthExceeded(e) => Some(e),
SchemaEvaluationError::PatternMatchLimit(e) => Some(e),
}
}
}