use eure_document::document::EureDocument;
use eure_document::document::node::NodeValue;
use eure_document::parse::{DocumentParser, ParseContext};
use eure_document::value::PrimitiveValue;
use num_bigint::BigInt;
use crate::{Bound, FloatSchema, IntegerSchema, SchemaNodeId, TextSchema};
use super::context::ValidationContext;
use super::error::{ValidationError, ValidatorError};
pub(crate) fn node_type_name(content: &NodeValue) -> String {
match content {
NodeValue::Hole(_) => "hole".to_string(),
NodeValue::Primitive(p) => match p {
PrimitiveValue::Null => "null".to_string(),
PrimitiveValue::Bool(_) => "boolean".to_string(),
PrimitiveValue::Integer(_) => "integer".to_string(),
PrimitiveValue::F32(_) | PrimitiveValue::F64(_) => "float".to_string(),
PrimitiveValue::Text(_) => "text".to_string(),
},
NodeValue::Array(_) => "array".to_string(),
NodeValue::Tuple(_) => "tuple".to_string(),
NodeValue::Map(_) => "map".to_string(),
NodeValue::PartialMap(_) => "partial-map".to_string(),
}
}
pub(crate) fn actual_type_from_error(kind: &eure_document::parse::ParseErrorKind) -> String {
use eure_document::parse::ParseErrorKind;
match kind {
ParseErrorKind::TypeMismatch { actual, .. } => value_kind_to_name(*actual),
ParseErrorKind::NotPrimitive { actual } => value_kind_to_name(*actual),
ParseErrorKind::UnexpectedHole => "hole".to_string(),
_ => format!("{}", kind),
}
}
fn value_kind_to_name(kind: eure_document::value::ValueKind) -> String {
use eure_document::value::ValueKind;
match kind {
ValueKind::Null => "null".to_string(),
ValueKind::Bool => "boolean".to_string(),
ValueKind::Integer => "integer".to_string(),
ValueKind::F32 | ValueKind::F64 => "float".to_string(),
ValueKind::Text => "text".to_string(),
ValueKind::Array => "array".to_string(),
ValueKind::Tuple => "tuple".to_string(),
ValueKind::Map => "map".to_string(),
ValueKind::PartialMap => "partial-map".to_string(),
ValueKind::Hole => "hole".to_string(),
}
}
pub struct AnyValidator;
impl<'doc> DocumentParser<'doc> for AnyValidator {
type Output = ();
type Error = ValidatorError;
fn parse(&mut self, _parse_ctx: &ParseContext<'doc>) -> Result<(), ValidatorError> {
Ok(())
}
}
pub struct TextValidator<'a, 'doc, 's> {
pub ctx: &'a ValidationContext<'doc>,
pub schema: &'s TextSchema,
pub schema_node_id: SchemaNodeId,
}
impl<'a, 'doc, 's> DocumentParser<'doc> for TextValidator<'a, 'doc, 's> {
type Output = ();
type Error = ValidatorError;
fn parse(&mut self, parse_ctx: &ParseContext<'doc>) -> Result<(), ValidatorError> {
let node_id = parse_ctx.node_id();
let primitive = match parse_ctx.parse_primitive() {
Ok(p) => p,
Err(e) => {
self.ctx.record_error(ValidationError::TypeMismatch {
expected: "text".to_string(),
actual: actual_type_from_error(&e.kind),
path: self.ctx.path(),
node_id,
schema_node_id: self.schema_node_id,
});
return Ok(());
}
};
let text = match primitive {
PrimitiveValue::Text(t) => t,
other => {
self.ctx.record_error(ValidationError::TypeMismatch {
expected: "text".to_string(),
actual: primitive_type_name(other),
path: self.ctx.path(),
node_id,
schema_node_id: self.schema_node_id,
});
return Ok(());
}
};
if let Some(expected_lang) = &self.schema.language {
use eure_document::text::Language;
let actual_lang = &text.language;
let matches = match actual_lang {
Language::Plaintext => expected_lang == "plaintext" || expected_lang == "text",
Language::Implicit => true, Language::Other(lang) => lang == expected_lang,
};
if !matches {
self.ctx.record_error(ValidationError::LanguageMismatch {
expected: expected_lang.clone(),
actual: format!("{:?}", actual_lang),
path: self.ctx.path(),
node_id,
schema_node_id: self.schema_node_id,
});
}
}
let len = text.as_str().chars().count();
if let Some(min) = self.schema.min_length
&& len < min as usize
{
self.ctx
.record_error(ValidationError::StringLengthOutOfBounds {
length: len,
min: Some(min),
max: self.schema.max_length,
path: self.ctx.path(),
node_id,
schema_node_id: self.schema_node_id,
});
}
if let Some(max) = self.schema.max_length
&& len > max as usize
{
self.ctx
.record_error(ValidationError::StringLengthOutOfBounds {
length: len,
min: self.schema.min_length,
max: Some(max),
path: self.ctx.path(),
node_id,
schema_node_id: self.schema_node_id,
});
}
if let Some(regex) = &self.schema.pattern
&& !regex.is_match(text.as_str())
{
self.ctx.record_error(ValidationError::PatternMismatch {
pattern: regex.as_str().to_string(),
path: self.ctx.path(),
node_id,
schema_node_id: self.schema_node_id,
});
}
Ok(())
}
}
fn primitive_type_name(p: &PrimitiveValue) -> String {
match p {
PrimitiveValue::Null => "null".to_string(),
PrimitiveValue::Bool(_) => "boolean".to_string(),
PrimitiveValue::Integer(_) => "integer".to_string(),
PrimitiveValue::F32(_) | PrimitiveValue::F64(_) => "float".to_string(),
PrimitiveValue::Text(_) => "text".to_string(),
}
}
pub struct IntegerValidator<'a, 'doc, 's> {
pub ctx: &'a ValidationContext<'doc>,
pub schema: &'s IntegerSchema,
pub schema_node_id: SchemaNodeId,
}
impl<'a, 'doc, 's> DocumentParser<'doc> for IntegerValidator<'a, 'doc, 's> {
type Output = ();
type Error = ValidatorError;
fn parse(&mut self, parse_ctx: &ParseContext<'doc>) -> Result<(), ValidatorError> {
let node_id = parse_ctx.node_id();
let int_val: BigInt = match parse_ctx.parse() {
Ok(v) => v,
Err(e) => {
self.ctx.record_error(ValidationError::TypeMismatch {
expected: "integer".to_string(),
actual: actual_type_from_error(&e.kind),
path: self.ctx.path(),
node_id,
schema_node_id: self.schema_node_id,
});
return Ok(());
}
};
let in_range = match (&self.schema.min, &self.schema.max) {
(Bound::Unbounded, Bound::Unbounded) => true,
(Bound::Inclusive(min), Bound::Unbounded) => &int_val >= min,
(Bound::Exclusive(min), Bound::Unbounded) => &int_val > min,
(Bound::Unbounded, Bound::Inclusive(max)) => &int_val <= max,
(Bound::Unbounded, Bound::Exclusive(max)) => &int_val < max,
(Bound::Inclusive(min), Bound::Inclusive(max)) => &int_val >= min && &int_val <= max,
(Bound::Inclusive(min), Bound::Exclusive(max)) => &int_val >= min && &int_val < max,
(Bound::Exclusive(min), Bound::Inclusive(max)) => &int_val > min && &int_val <= max,
(Bound::Exclusive(min), Bound::Exclusive(max)) => &int_val > min && &int_val < max,
};
if !in_range {
self.ctx.record_error(ValidationError::OutOfRange {
value: int_val.to_string(),
path: self.ctx.path(),
node_id,
schema_node_id: self.schema_node_id,
});
}
if let Some(divisor) = &self.schema.multiple_of
&& &int_val % divisor != BigInt::from(0)
{
self.ctx.record_error(ValidationError::NotMultipleOf {
divisor: divisor.to_string(),
path: self.ctx.path(),
node_id,
schema_node_id: self.schema_node_id,
});
}
Ok(())
}
}
pub struct FloatValidator<'a, 'doc, 's> {
pub ctx: &'a ValidationContext<'doc>,
pub schema: &'s FloatSchema,
pub schema_node_id: SchemaNodeId,
}
impl<'a, 'doc, 's> DocumentParser<'doc> for FloatValidator<'a, 'doc, 's> {
type Output = ();
type Error = ValidatorError;
fn parse(&mut self, parse_ctx: &ParseContext<'doc>) -> Result<(), ValidatorError> {
let node_id = parse_ctx.node_id();
let float_val: f64 = match parse_ctx.parse() {
Ok(v) => v,
Err(e) => {
if let Ok(int_val) = parse_ctx.parse::<BigInt>() {
int_val.to_string().parse::<f64>().unwrap_or(f64::NAN)
} else {
self.ctx.record_error(ValidationError::TypeMismatch {
expected: "float".to_string(),
actual: actual_type_from_error(&e.kind),
path: self.ctx.path(),
node_id,
schema_node_id: self.schema_node_id,
});
return Ok(());
}
}
};
let in_range = match (&self.schema.min, &self.schema.max) {
(Bound::Unbounded, Bound::Unbounded) => true,
(Bound::Inclusive(min), Bound::Unbounded) => float_val >= *min,
(Bound::Exclusive(min), Bound::Unbounded) => float_val > *min,
(Bound::Unbounded, Bound::Inclusive(max)) => float_val <= *max,
(Bound::Unbounded, Bound::Exclusive(max)) => float_val < *max,
(Bound::Inclusive(min), Bound::Inclusive(max)) => {
float_val >= *min && float_val <= *max
}
(Bound::Inclusive(min), Bound::Exclusive(max)) => float_val >= *min && float_val < *max,
(Bound::Exclusive(min), Bound::Inclusive(max)) => float_val > *min && float_val <= *max,
(Bound::Exclusive(min), Bound::Exclusive(max)) => float_val > *min && float_val < *max,
};
if !in_range {
self.ctx.record_error(ValidationError::OutOfRange {
value: float_val.to_string(),
path: self.ctx.path(),
node_id,
schema_node_id: self.schema_node_id,
});
}
if let Some(divisor) = self.schema.multiple_of
&& (float_val % divisor).abs() > f64::EPSILON
{
self.ctx.record_error(ValidationError::NotMultipleOf {
divisor: divisor.to_string(),
path: self.ctx.path(),
node_id,
schema_node_id: self.schema_node_id,
});
}
Ok(())
}
}
pub struct BooleanValidator<'a, 'doc> {
pub ctx: &'a ValidationContext<'doc>,
pub schema_node_id: SchemaNodeId,
}
impl<'a, 'doc> DocumentParser<'doc> for BooleanValidator<'a, 'doc> {
type Output = ();
type Error = ValidatorError;
fn parse(&mut self, parse_ctx: &ParseContext<'doc>) -> Result<(), ValidatorError> {
let node_id = parse_ctx.node_id();
if let Err(e) = parse_ctx.parse::<bool>() {
self.ctx.record_error(ValidationError::TypeMismatch {
expected: "boolean".to_string(),
actual: actual_type_from_error(&e.kind),
path: self.ctx.path(),
node_id,
schema_node_id: self.schema_node_id,
});
}
Ok(())
}
}
pub struct NullValidator<'a, 'doc> {
pub ctx: &'a ValidationContext<'doc>,
pub schema_node_id: SchemaNodeId,
}
impl<'a, 'doc> DocumentParser<'doc> for NullValidator<'a, 'doc> {
type Output = ();
type Error = ValidatorError;
fn parse(&mut self, parse_ctx: &ParseContext<'doc>) -> Result<(), ValidatorError> {
let node_id = parse_ctx.node_id();
if !parse_ctx.is_null() {
self.ctx.record_error(ValidationError::TypeMismatch {
expected: "null".to_string(),
actual: node_type_name(&parse_ctx.node().content),
path: self.ctx.path(),
node_id,
schema_node_id: self.schema_node_id,
});
}
Ok(())
}
}
pub struct LiteralValidator<'a, 'doc, 's> {
pub ctx: &'a ValidationContext<'doc>,
pub expected: &'s EureDocument,
pub schema_node_id: SchemaNodeId,
}
impl<'a, 'doc, 's> DocumentParser<'doc> for LiteralValidator<'a, 'doc, 's> {
type Output = ();
type Error = ValidatorError;
fn parse(&mut self, parse_ctx: &ParseContext<'doc>) -> Result<(), ValidatorError> {
let node_id = parse_ctx.node_id();
let actual = parse_ctx.node_subtree_to_document_excluding_accessed();
if actual != *self.expected {
self.ctx.record_error(ValidationError::LiteralMismatch {
expected: format!("{:?}", self.expected),
actual: format!("{:?}", actual),
path: self.ctx.path(),
node_id,
schema_node_id: self.schema_node_id,
});
}
Ok(())
}
}