Skip to main content

fatoora_core/invoice/
validation.rs

1//! XML schema validation helpers.
2use crate::config::Config;
3use libxml::{
4    error::StructuredError,
5    parser::Parser,
6    schemas::{SchemaParserContext, SchemaValidationContext},
7};
8use std::path::PathBuf;
9use thiserror::Error;
10
11pub type ValidationResult = Result<(), XmlValidationError>;
12
13/// Errors emitted during XML schema validation.
14#[derive(Debug, Error)]
15pub enum XmlValidationError {
16    #[error("invalid XSD path: {path}")]
17    InvalidXsdPath { path: String },
18    #[error("schema parser error")]
19    SchemaParse { errors: Vec<StructuredError> },
20    #[error("XML parse error: {message}")]
21    XmlParse { message: String },
22    #[error("schema validation error")]
23    SchemaValidation { errors: Vec<StructuredError> },
24}
25
26fn bundled_xsd_path() -> PathBuf {
27    PathBuf::from(env!("CARGO_MANIFEST_DIR"))
28        .join("assets/schemas/UBL2.1/xsd/maindoc/UBL-Invoice-2.1.xsd")
29}
30
31fn build_validation_context(_config: &Config) -> Result<SchemaValidationContext, XmlValidationError> {
32    let xsd_path_buf = bundled_xsd_path();
33    let xsd_path = xsd_path_buf.to_str().ok_or_else(|| {
34        XmlValidationError::InvalidXsdPath {
35            path: xsd_path_buf.display().to_string(),
36        }
37    })?;
38
39    let mut parser_ctx = SchemaParserContext::from_file(xsd_path);
40    SchemaValidationContext::from_parser(&mut parser_ctx)
41        .map_err(|errors| XmlValidationError::SchemaParse { errors })
42}
43
44/// Validate an XML invoice string against the UBL schema.
45///
46/// # Errors
47/// Returns [`XmlValidationError`] if the XML is invalid or validation fails.
48pub fn validate_xml_invoice_from_str(xml: &str, config: &Config) -> ValidationResult {
49    let mut validation_ctx = build_validation_context(config)?;
50    let document = Parser::default()
51        .parse_string(xml)
52        .map_err(|e| XmlValidationError::XmlParse {
53            message: format!("{e:?}"),
54        })?;
55
56    validation_ctx
57        .validate_document(&document)
58        .map_err(|errors| XmlValidationError::SchemaValidation { errors })
59}