use std::ffi::OsString;
use std::path::{MAIN_SEPARATOR_STR, Path, PathBuf};
use crate::entity::DocumentType;
use crate::error::CpeValidatorError;
use crate::options::Limits;
use crate::xml::XmlDocument;
pub trait TypeResolver {
fn resolve(&self, document: &XmlDocument) -> Result<Option<DocumentType>, CpeValidatorError>;
}
#[derive(Clone, Copy, Debug, Default)]
pub struct XmlTypeResolver;
impl XmlTypeResolver {
pub fn resolve_xml(
&self,
xml: impl AsRef<[u8]>,
) -> Result<Option<DocumentType>, CpeValidatorError> {
self.resolve(&XmlDocument::parse_with_limits(xml, &Limits::default())?)
}
}
impl TypeResolver for XmlTypeResolver {
fn resolve(&self, document: &XmlDocument) -> Result<Option<DocumentType>, CpeValidatorError> {
let Some(name) = document.root_name() else {
return Ok(None);
};
Ok(Some(match name.as_str() {
"Invoice" => match document.doc.root_text("cbc:InvoiceTypeCode").as_deref() {
Some("03") => DocumentType::Boleta,
_ => DocumentType::Factura,
},
"CreditNote" => DocumentType::NotaCredito,
"DebitNote" => DocumentType::NotaDebito,
"SummaryDocuments" => DocumentType::ResumenDiario,
"VoidedDocuments" => match document.doc.root_text("cbc:ID") {
Some(id) if id.starts_with(DocumentType::ResumenReversion.code()) => {
DocumentType::ResumenReversion
}
Some(_) => DocumentType::ComunicacionBaja,
None => return Err(CpeValidatorError::VoidedWithoutId),
},
"DespatchAdvice" => DocumentType::GuiaRemision,
"Retention" => DocumentType::Retencion,
"Perception" => DocumentType::Percepcion,
_ => return Ok(None),
}))
}
}
pub trait RuleResolver {
fn rule_path(&self, document_type: DocumentType) -> Option<PathBuf>;
fn rules_root(&self) -> Option<&Path> {
None
}
}
#[derive(Clone, Debug)]
pub struct XslPathResolver {
base: PathBuf,
root: PathBuf,
}
impl XslPathResolver {
#[must_use]
pub fn new(base: impl Into<PathBuf>) -> Self {
let base = base.into();
Self {
root: base.clone(),
base,
}
}
#[must_use]
pub fn with_rules_root(mut self, root: impl Into<PathBuf>) -> Self {
self.root = root.into();
self
}
#[must_use]
pub const fn relative_path(document_type: DocumentType) -> &'static str {
match document_type {
DocumentType::Factura => "2.X/ValidaExprRegFactura-2.0.1.xsl",
DocumentType::Boleta => "2.X/ValidaExprRegBoleta-2.0.1.xsl",
DocumentType::NotaCredito => "2.X/ValidaExprRegNC-2.0.1.xsl",
DocumentType::NotaDebito => "2.X/ValidaExprRegND-2.0.1.xsl",
DocumentType::ResumenDiario => "1.X/ValidaExprRegSummary-1.1.0.xsl",
DocumentType::ComunicacionBaja => "1.X/ValidaExprRegSummaryVoided-1.0.1.xsl",
DocumentType::GuiaRemision => "2.X/ValidaExprRegGreRemitente-2.0.1.xsl",
DocumentType::Retencion => "1.X/ValidaExprRegRetencion-1.0.3.xsl",
DocumentType::Percepcion => "1.X/ValidaExprRegPercepcion-1.0.1.xsl",
DocumentType::ResumenReversion => "1.X/ValidaExprRegOtrosVoided-1.0.1.xsl",
}
}
#[must_use]
pub fn path_for_code(&self, code: &str) -> Option<PathBuf> {
DocumentType::from_code(code).and_then(|t| self.rule_path(t))
}
}
impl RuleResolver for XslPathResolver {
fn rule_path(&self, document_type: DocumentType) -> Option<PathBuf> {
let mut path = OsString::from(self.base.as_os_str());
path.push(MAIN_SEPARATOR_STR);
path.push(Self::relative_path(document_type));
Some(PathBuf::from(path))
}
fn rules_root(&self) -> Option<&Path> {
Some(&self.root)
}
}