use std::sync::{Arc, Mutex};
use compact_str::CompactString;
use crate::error::{ErrorLevel, Result, StructuredError, ValidationErrorType};
use crate::event::{XmlEvent, XmlEventHandler};
use crate::schema::fetcher::SchemaFetcher;
use super::streaming::OnePassSchemaValidator;
pub struct LazySchemaValidator<F: SchemaFetcher> {
fetcher: F,
validator: Option<OnePassSchemaValidator>,
initialized: bool,
errors: Vec<StructuredError>,
}
impl<F: SchemaFetcher> LazySchemaValidator<F> {
pub fn new(fetcher: F) -> Self {
Self {
fetcher,
validator: None,
initialized: false,
errors: Vec::new(),
}
}
pub fn errors(&self) -> &[StructuredError] {
if let Some(v) = &self.validator {
v.errors()
} else {
&self.errors
}
}
fn initialize_from_attributes(&mut self, attributes: &[(CompactString, CompactString)]) {
if self.initialized {
return;
}
self.initialized = true;
let schema_location = attributes
.iter()
.find(|(k, _)| k == "xsi:schemaLocation" || k == "schemaLocation")
.map(|(_, v)| v.as_str());
let schema = if let Some(loc_value) = schema_location {
let parts: Vec<&str> = loc_value.split_whitespace().collect();
let mut resolver = crate::schema::xsd::SchemaResolver::new(&self.fetcher);
let mut loaded_any = false;
for chunk in parts.chunks(2) {
if chunk.len() == 2 {
let location = chunk[1];
match self.fetcher.fetch(location) {
Ok(result) => {
match resolver.resolve_entry(&result.content, &result.final_url) {
Ok(()) => {
loaded_any = true;
}
Err(e) => {
self.errors.push(
StructuredError::new(
format!(
"Warning: Failed to parse schema {}: {}",
location, e
),
ValidationErrorType::SchemaNotFound,
)
.with_level(ErrorLevel::Warning),
);
}
}
}
Err(_e) => {
}
}
}
}
if !loaded_any {
self.errors.push(
StructuredError::new(
"No schemas could be loaded from xsi:schemaLocation",
ValidationErrorType::SchemaNotFound,
)
.with_level(ErrorLevel::Warning),
);
crate::schema::xsd::create_builtin_schema()
} else {
let schemas = resolver.take_all_schemas();
match crate::schema::xsd::compile_schemas(schemas) {
Ok(mut compiled) => {
crate::schema::xsd::register_builtin_types(&mut compiled);
compiled
}
Err(e) => {
self.errors.push(
StructuredError::new(
format!("Warning: Failed to compile schemas: {}", e),
ValidationErrorType::SchemaNotFound,
)
.with_level(ErrorLevel::Warning),
);
crate::schema::xsd::create_builtin_schema()
}
}
}
} else {
crate::schema::xsd::create_builtin_schema()
};
self.validator = Some(OnePassSchemaValidator::new(Arc::new(schema)));
}
}
impl<F: SchemaFetcher + 'static> XmlEventHandler for LazySchemaValidator<F> {
fn handle(&mut self, event: &XmlEvent) -> Result<()> {
if let XmlEvent::StartElement { attributes, .. } = event {
if !self.initialized {
self.initialize_from_attributes(attributes);
}
}
if let Some(v) = &mut self.validator {
v.handle(event)?;
}
Ok(())
}
fn as_any(self: Box<Self>) -> Box<dyn std::any::Any> {
self
}
}
pub(crate) struct LazySchemaValidatorWithSharedErrors<F: SchemaFetcher> {
fetcher: F,
validator: Option<OnePassSchemaValidator>,
initialized: bool,
shared_errors: Arc<Mutex<Vec<StructuredError>>>,
}
impl<F: SchemaFetcher> LazySchemaValidatorWithSharedErrors<F> {
pub fn new(fetcher: F, shared_errors: Arc<Mutex<Vec<StructuredError>>>) -> Self {
Self {
fetcher,
validator: None,
initialized: false,
shared_errors,
}
}
fn initialize_from_attributes(&mut self, attributes: &[(CompactString, CompactString)]) {
if self.initialized {
return;
}
self.initialized = true;
let schema_location = attributes
.iter()
.find(|(k, _)| k == "xsi:schemaLocation" || k == "schemaLocation")
.map(|(_, v)| v.as_str());
let schema = if let Some(loc_value) = schema_location {
let parts: Vec<&str> = loc_value.split_whitespace().collect();
let mut resolver = crate::schema::xsd::SchemaResolver::new(&self.fetcher);
let mut loaded_any = false;
for chunk in parts.chunks(2) {
if chunk.len() == 2 {
let location = chunk[1];
match self.fetcher.fetch(location) {
Ok(result) => {
match resolver.resolve_entry(&result.content, &result.final_url) {
Ok(()) => {
loaded_any = true;
}
Err(e) => {
self.shared_errors.lock().unwrap().push(
StructuredError::new(
format!(
"Warning: Failed to parse schema {}: {}",
location, e
),
ValidationErrorType::SchemaNotFound,
)
.with_level(ErrorLevel::Warning),
);
}
}
}
Err(_e) => {
}
}
}
}
if !loaded_any {
self.shared_errors.lock().unwrap().push(
StructuredError::new(
"No schemas could be loaded from xsi:schemaLocation",
ValidationErrorType::SchemaNotFound,
)
.with_level(ErrorLevel::Warning),
);
crate::schema::xsd::create_builtin_schema()
} else {
let schemas = resolver.take_all_schemas();
match crate::schema::xsd::compile_schemas(schemas) {
Ok(mut compiled) => {
crate::schema::xsd::register_builtin_types(&mut compiled);
compiled
}
Err(e) => {
self.shared_errors.lock().unwrap().push(
StructuredError::new(
format!("Warning: Failed to compile schemas: {}", e),
ValidationErrorType::SchemaNotFound,
)
.with_level(ErrorLevel::Warning),
);
crate::schema::xsd::create_builtin_schema()
}
}
}
} else {
crate::schema::xsd::create_builtin_schema()
};
self.validator = Some(OnePassSchemaValidator::new(Arc::new(schema)));
}
}
impl<F: SchemaFetcher + 'static> XmlEventHandler for LazySchemaValidatorWithSharedErrors<F> {
fn handle(&mut self, event: &XmlEvent) -> Result<()> {
if let XmlEvent::StartElement { attributes, .. } = event {
if !self.initialized {
self.initialize_from_attributes(attributes);
}
}
if let Some(v) = &mut self.validator {
v.handle(event)?;
for err in v.errors() {
let mut errors = self.shared_errors.lock().unwrap();
if !errors.iter().any(|e| e.message == err.message) {
errors.push(err.clone());
}
}
}
Ok(())
}
fn as_any(self: Box<Self>) -> Box<dyn std::any::Any> {
self
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::schema::fetcher::NoopFetcher;
#[test]
fn test_lazy_validator_new() {
let fetcher = NoopFetcher;
let validator = LazySchemaValidator::new(fetcher);
assert!(validator.errors().is_empty());
}
#[test]
fn test_lazy_validator_no_schema_location() {
let fetcher = NoopFetcher;
let mut validator = LazySchemaValidator::new(fetcher);
let _ = validator.handle(&XmlEvent::StartElement {
name: "root".into(),
prefix: None,
namespace: None,
attributes: vec![],
namespace_decls: vec![],
line: None,
column: Some(1),
});
assert!(validator.errors().is_empty());
}
#[test]
fn test_lazy_validator_with_shared_errors() {
let fetcher = NoopFetcher;
let shared_errors = Arc::new(Mutex::new(Vec::new()));
let mut validator =
LazySchemaValidatorWithSharedErrors::new(fetcher, Arc::clone(&shared_errors));
let _ = validator.handle(&XmlEvent::StartElement {
name: "root".into(),
prefix: None,
namespace: None,
attributes: vec![],
namespace_decls: vec![],
line: None,
column: Some(1),
});
let errors = shared_errors.lock().unwrap();
assert!(errors.is_empty());
}
#[test]
fn test_lazy_schema_validator_errors_empty() {
let fetcher = NoopFetcher;
let validator = LazySchemaValidator::new(fetcher);
assert!(validator.errors().is_empty());
}
#[test]
fn test_lazy_schema_validator_handle_with_schema_location() {
let fetcher = NoopFetcher;
let mut validator = LazySchemaValidator::new(fetcher);
let _ = validator.handle(&XmlEvent::StartElement {
name: "root".into(),
prefix: None,
namespace: None,
attributes: vec![
(
"xmlns:xsi".into(),
"http://www.w3.org/2001/XMLSchema-instance".into(),
),
(
"xsi:schemaLocation".into(),
"http://example.com http://example.com/schema.xsd".into(),
),
],
namespace_decls: vec![],
line: Some(1),
column: Some(1),
});
let _ = validator.handle(&XmlEvent::EndElement {
name: "root".into(),
prefix: None,
});
let _ = validator.handle(&XmlEvent::Eof);
}
#[test]
fn test_lazy_schema_validator_delegates_to_inner() {
let fetcher = NoopFetcher;
let mut validator = LazySchemaValidator::new(fetcher);
let _ = validator.handle(&XmlEvent::StartElement {
name: "root".into(),
prefix: None,
namespace: None,
attributes: vec![],
namespace_decls: vec![],
line: Some(1),
column: Some(1),
});
let _ = validator.handle(&XmlEvent::StartElement {
name: "child".into(),
prefix: None,
namespace: None,
attributes: vec![],
namespace_decls: vec![],
line: Some(2),
column: Some(1),
});
let _ = validator.handle(&XmlEvent::Text("content".to_string()));
let _ = validator.handle(&XmlEvent::EndElement {
name: "child".into(),
prefix: None,
});
let _ = validator.handle(&XmlEvent::EndElement {
name: "root".into(),
prefix: None,
});
let _ = validator.handle(&XmlEvent::Eof);
}
}