use std::io::BufRead;
use std::sync::Arc;
use crate::document::XmlDocument;
use crate::error::{Result, StructuredError};
use crate::schema::fetcher::SchemaFetcher;
use crate::schema::types::CompiledSchema;
use super::ValidationMode;
use super::dom::DomSchemaValidator;
use super::streaming::OnePassSchemaValidator;
enum Source<'a> {
Dom(&'a XmlDocument),
Bytes(&'a [u8]),
Reader(Box<dyn BufRead + 'a>),
}
pub struct Validator<'a> {
source: Source<'a>,
schema: Option<Arc<CompiledSchema>>,
mode: ValidationMode,
max_errors: Option<usize>,
aggregate_errors: bool,
}
impl<'a> From<&'a XmlDocument> for Validator<'a> {
fn from(doc: &'a XmlDocument) -> Self {
Self::with_source(Source::Dom(doc))
}
}
impl<'a> From<&'a str> for Validator<'a> {
fn from(xml: &'a str) -> Self {
Self::with_source(Source::Bytes(xml.as_bytes()))
}
}
impl<'a> From<&'a [u8]> for Validator<'a> {
fn from(xml: &'a [u8]) -> Self {
Self::with_source(Source::Bytes(xml))
}
}
impl<'a> Validator<'a> {
fn with_source(source: Source<'a>) -> Self {
Self {
source,
schema: None,
mode: ValidationMode::default(),
max_errors: None,
aggregate_errors: false,
}
}
pub fn from_reader<R: BufRead + 'a>(reader: R) -> Self {
Self::with_source(Source::Reader(Box::new(reader)))
}
pub fn schema(mut self, schema: impl Into<Arc<CompiledSchema>>) -> Self {
self.schema = Some(schema.into());
self
}
pub fn mode(mut self, mode: ValidationMode) -> Self {
self.mode = mode;
self
}
pub fn max_errors(mut self, max: usize) -> Self {
self.max_errors = Some(max);
self
}
pub fn aggregate_errors(mut self) -> Self {
self.aggregate_errors = true;
self
}
pub fn run(self) -> Result<Report> {
let Self {
source,
schema,
mode,
max_errors,
aggregate_errors,
} = self;
let (entries, counters) = match schema {
Some(schema) => {
validate_with_schema(source, schema, mode, max_errors, aggregate_errors)?
}
None => (run_location_default(source)?, None),
};
Ok(Report::with_counters(entries, counters))
}
pub fn run_with<F: SchemaFetcher + 'static>(self, fetcher: F) -> Result<Report> {
let Self {
source,
schema,
mode,
max_errors,
aggregate_errors,
} = self;
let (entries, counters) = match schema {
Some(schema) => {
validate_with_schema(source, schema, mode, max_errors, aggregate_errors)?
}
None => {
let entries = match source {
Source::Dom(doc) => {
super::api::validate_with_schema_location_and_fetcher(doc, &fetcher)?
}
Source::Bytes(bytes) => {
super::api::streaming_validate_with_schema_location_and_fetcher(
bytes, fetcher,
)?
}
Source::Reader(reader) => {
super::api::streaming_validate_with_schema_location_and_fetcher(
reader, fetcher,
)?
}
};
(entries, None)
}
};
Ok(Report::with_counters(entries, counters))
}
#[cfg(feature = "tokio")]
pub async fn run_async_with<F: crate::schema::fetcher::AsyncSchemaFetcher>(
self,
fetcher: &F,
) -> Result<Report> {
let Self {
source,
schema,
mode,
max_errors,
aggregate_errors,
} = self;
let (entries, counters) = match schema {
Some(schema) => {
validate_with_schema(source, schema, mode, max_errors, aggregate_errors)?
}
None => {
let entries = match source {
Source::Dom(doc) => {
super::api::validate_with_schema_location_with_async_fetcher(doc, fetcher)
.await?
}
_ => {
return Err(crate::error::Error::InvalidOperation(
"async xsi:schemaLocation resolution is only supported for DOM input; \
use run_with for streaming input, or provide a schema"
.to_string(),
));
}
};
(entries, None)
}
};
Ok(Report::with_counters(entries, counters))
}
#[cfg(feature = "tokio")]
pub async fn run_async(self) -> Result<Report> {
let fetcher = crate::schema::fetcher::AsyncDefaultFetcher::new()?;
self.run_async_with(&fetcher).await
}
}
type ValidationOutcome = (Vec<StructuredError>, Option<super::ValidationCounters>);
fn validate_with_schema(
source: Source<'_>,
schema: Arc<CompiledSchema>,
mode: ValidationMode,
max_errors: Option<usize>,
aggregate_errors: bool,
) -> Result<ValidationOutcome> {
match source {
Source::Dom(doc) => {
let mut validator = DomSchemaValidator::new(schema).with_mode(mode);
if let Some(max) = max_errors {
validator = validator.with_max_errors(max);
}
if aggregate_errors {
validator = validator.with_aggregate_errors();
}
Ok((validator.validate(doc)?, None))
}
Source::Bytes(bytes) => {
run_streaming_with_schema(bytes, schema, mode, max_errors, aggregate_errors)
}
Source::Reader(reader) => {
run_streaming_with_schema(reader, schema, mode, max_errors, aggregate_errors)
}
}
}
fn run_streaming_with_schema<R: BufRead>(
reader: R,
schema: Arc<CompiledSchema>,
mode: ValidationMode,
max_errors: Option<usize>,
aggregate_errors: bool,
) -> Result<ValidationOutcome> {
let mut validator = OnePassSchemaValidator::new(schema).set_mode(mode);
if let Some(max) = max_errors {
validator = validator.with_max_errors(max);
}
if aggregate_errors {
validator = validator.with_aggregate_errors();
}
let (entries, counters) = validator.validate_capturing(reader)?;
Ok((entries, Some(counters)))
}
#[cfg(feature = "ureq")]
fn run_location_default(source: Source<'_>) -> Result<Vec<StructuredError>> {
match source {
Source::Dom(doc) => super::api::validate_with_schema_location(doc),
Source::Bytes(bytes) => super::api::streaming_validate_with_schema_location(bytes),
Source::Reader(reader) => super::api::streaming_validate_with_schema_location(reader),
}
}
#[cfg(not(feature = "ureq"))]
fn run_location_default(_source: Source<'_>) -> Result<Vec<StructuredError>> {
Err(crate::error::Error::InvalidOperation(
"resolving xsi:schemaLocation with the default fetcher requires the `ureq` feature; \
enable it, call .schema(...) with an explicit schema, or use .run_with(fetcher)"
.to_string(),
))
}
#[derive(Debug, Clone)]
pub struct Report {
entries: Vec<StructuredError>,
counters: Option<super::ValidationCounters>,
}
impl Report {
fn with_counters(
entries: Vec<StructuredError>,
counters: Option<super::ValidationCounters>,
) -> Self {
Self { entries, counters }
}
#[doc(hidden)]
pub fn counters(&self) -> Option<super::ValidationCounters> {
self.counters
}
pub fn is_valid(&self) -> bool {
!self.entries.iter().any(|e| e.is_error())
}
pub fn is_clean(&self) -> bool {
self.entries.is_empty()
}
pub fn entries(&self) -> &[StructuredError] {
&self.entries
}
pub fn errors(&self) -> Vec<&StructuredError> {
self.entries.iter().filter(|e| e.is_error()).collect()
}
pub fn warnings(&self) -> Vec<&StructuredError> {
self.entries.iter().filter(|e| e.is_warning()).collect()
}
pub fn error_count(&self) -> usize {
self.entries.iter().filter(|e| e.is_error()).count()
}
pub fn warning_count(&self) -> usize {
self.entries.iter().filter(|e| e.is_warning()).count()
}
pub fn into_entries(self) -> Vec<StructuredError> {
self.entries
}
}