use std::path::{Path, PathBuf};
use std::sync::Arc;
use crate::error::Result;
use crate::schema::export::{ExportResult, export_schemas_from_xml};
use crate::schema::fetcher::SchemaFetcher;
use crate::schema::types::CompiledSchema;
use crate::schema::xsd::{create_builtin_schema, parse_xsd_multiple};
#[derive(Debug)]
pub struct ResolvedSchema {
pub compiled: Arc<CompiledSchema>,
pub export_dir: Option<PathBuf>,
pub entry_filename: Option<String>,
pub export_result: Option<ExportResult>,
}
impl ResolvedSchema {
pub fn entry_schema_path(&self) -> Option<PathBuf> {
match (&self.export_dir, &self.entry_filename) {
(Some(dir), Some(filename)) => Some(dir.join(filename)),
_ => None,
}
}
pub fn is_builtin(&self) -> bool {
self.export_dir.is_none()
}
}
#[derive(Debug, Clone, Default)]
pub struct ResolveOptions {
pub base_dir: Option<PathBuf>,
pub export_dir: Option<PathBuf>,
pub keep_export_dir: bool,
}
impl ResolveOptions {
pub fn with_base_dir(base_dir: impl AsRef<Path>) -> Self {
Self {
base_dir: Some(base_dir.as_ref().to_path_buf()),
..Default::default()
}
}
pub fn export_dir(mut self, dir: impl AsRef<Path>) -> Self {
self.export_dir = Some(dir.as_ref().to_path_buf());
self
}
pub fn keep_export_dir(mut self) -> Self {
self.keep_export_dir = true;
self
}
}
pub fn resolve_schema_from_xml<F: SchemaFetcher>(
xml_content: &[u8],
fetcher: &F,
options: &ResolveOptions,
) -> Result<ResolvedSchema> {
let export_dir = options.export_dir.clone().unwrap_or_else(|| {
std::env::temp_dir().join(format!("fastxml_schemas_{}", std::process::id()))
});
let _ = std::fs::remove_dir_all(&export_dir);
let export_result = export_schemas_from_xml(xml_content, &export_dir, fetcher)?;
if export_result.schema_count == 0 {
return Ok(ResolvedSchema {
compiled: Arc::new(create_builtin_schema()),
export_dir: None,
entry_filename: None,
export_result: None,
});
}
let mut xsd_contents: Vec<(String, Vec<u8>)> = Vec::new();
for (uri, filename) in &export_result.uri_to_filename {
let path = export_dir.join(filename);
if let Ok(content) = std::fs::read(&path) {
xsd_contents.push((uri.clone(), content));
}
}
let xsd_refs: Vec<(&str, &[u8])> = xsd_contents
.iter()
.map(|(uri, content)| (uri.as_str(), content.as_slice()))
.collect();
let compiled = match parse_xsd_multiple(&xsd_refs) {
Ok(schema) => Arc::new(schema),
Err(_) => {
Arc::new(create_builtin_schema())
}
};
Ok(ResolvedSchema {
compiled,
export_dir: Some(export_dir),
entry_filename: export_result.entry_filename.clone(),
export_result: Some(export_result),
})
}
pub fn resolve_schema_from_file<F: SchemaFetcher>(
xml_path: impl AsRef<Path>,
fetcher: &F,
) -> Result<ResolvedSchema> {
let path = xml_path.as_ref();
let content = std::fs::read(path)?;
let options = if let Some(parent) = path.parent() {
ResolveOptions::with_base_dir(parent)
} else {
ResolveOptions::default()
};
resolve_schema_from_xml(&content, fetcher, &options)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::schema::fetcher::NoopFetcher;
#[test]
fn test_resolve_no_schema_location() {
let xml = br#"<?xml version="1.0"?><root>content</root>"#;
let fetcher = NoopFetcher;
let options = ResolveOptions::default();
let result = resolve_schema_from_xml(xml, &fetcher, &options).unwrap();
assert!(result.is_builtin());
assert!(result.export_dir.is_none());
assert!(result.entry_filename.is_none());
}
#[test]
fn test_resolve_options_builder() {
let options = ResolveOptions::with_base_dir("/some/path")
.export_dir("/export/dir")
.keep_export_dir();
assert_eq!(options.base_dir, Some(PathBuf::from("/some/path")));
assert_eq!(options.export_dir, Some(PathBuf::from("/export/dir")));
assert!(options.keep_export_dir);
}
#[test]
fn test_entry_schema_path() {
let resolved = ResolvedSchema {
compiled: Arc::new(create_builtin_schema()),
export_dir: Some(PathBuf::from("/tmp/schemas")),
entry_filename: Some("main.xsd".to_string()),
export_result: None,
};
assert_eq!(
resolved.entry_schema_path(),
Some(PathBuf::from("/tmp/schemas/main.xsd"))
);
}
}