use super::actions::*;
use super::validations::*;
use base64::Engine;
use std::path::Path;
#[derive(Debug, Clone)]
pub struct DanfeBuilder<'a> {
pub xml: Option<&'a str>,
pub paper_size: Option<&'a str>,
pub as_base64: Option<&'a str>,
pub as_file: Option<&'a str>,
pub qr_side: bool,
pub logo: Option<&'a str>,
}
impl<'a> DanfeBuilder<'a> {
pub fn new() -> Self {
Self {
xml: None,
paper_size: None,
as_base64: None,
as_file: None,
qr_side: false,
logo: None,
}
}
pub fn xml(mut self, xml: &'a str) -> Self {
self.xml = Some(xml);
self
}
pub fn paper_size(mut self, size: &'a str) -> Self {
self.paper_size = Some(size);
self
}
pub fn as_base64(mut self) -> Self {
self.as_base64 = Some("base64");
self
}
pub fn as_file(mut self, path: &'a str) -> Self {
self.as_file = Some(path);
self
}
pub fn logo(mut self, logo: &'a str) -> Self {
self.logo = Some(logo);
self
}
pub fn qr_side(mut self) -> Self {
self.qr_side = true;
self
}
pub async fn build(self) -> Result<String, String> {
let nfe_proc = Validations::init(&self)?;
let logo_bytes: Option<Vec<u8>> = match self.logo {
Some(src) => Some(Self::load_logo_bytes(src)?),
None => None,
};
let paper_size = self.paper_size.unwrap_or("a4");
let mod_ = match nfe_proc.nfe.inf_nfe.ide.mod_.clone() {
Some(m) => m,
None => format!("Campo 'mod' ausente no XML"),
};
let pdf_bytes: Vec<u8> = match paper_size {
"a4" => {
match mod_.as_str() {
"55" => DanfeBuilderActions::generate_55_a4(nfe_proc, logo_bytes).await?,
"65" => return Err("Geração de DANFE para modelo 65 em formato A4 ainda não implementada".to_string()),
_ => return Err(format!("Modelo de documento inválido: {}. O DANFE é gerado apenas para o modelo 55 ou 65.", mod_)),
}
}
"80mm" => {
match mod_.as_str() {
"55" => DanfeBuilderActions::generate_55_80mm(nfe_proc).await?,
"65" => DanfeBuilderActions::generate_65_80mm(nfe_proc, self.qr_side).await?,
_ => return Err(format!("Modelo de documento inválido: {}. O DANFE é gerado apenas para o modelo 55 ou 65.", mod_)),
}
}
"54mm" => {
match mod_.as_str() {
"55" => return Err("Geração de DANFE para modelo 55 em formato 54mm ainda não implementada".to_string()),
"65" => return Err("Geração de DANFE para modelo 65 em formato 54mm ainda não implementada".to_string()),
_ => return Err(format!("Modelo de documento inválido: {}. O DANFE é gerado apenas para o modelo 55 ou 65.", mod_)),
}
}
_ => {
return Err(format!(
"Tamanho de papel inválido: {}. Os tamanhos válidos são 'a4', '80mm' ou '54mm'.",
self.paper_size.unwrap_or("a4")
))
}
};
if let Some(file_path) = self.as_file {
std::fs::write(file_path, &pdf_bytes)
.map_err(|e| format!("Erro ao salvar o arquivo PDF: {}", e))?;
Ok(file_path.to_string())
} else {
let base64_pdf = base64::engine::general_purpose::STANDARD.encode(&pdf_bytes);
Ok(base64_pdf)
}
}
fn load_logo_bytes(src: &str) -> Result<Vec<u8>, String> {
if src.starts_with("data:") {
let b64 = src
.splitn(2, ',')
.nth(1)
.ok_or("Data URI inválida: falta vírgula separadora")?;
return base64::engine::general_purpose::STANDARD
.decode(b64)
.map_err(|e| format!("Erro ao decodificar logo data URI: {}", e));
}
let ext = Path::new(src)
.extension()
.and_then(|e| e.to_str())
.unwrap_or("")
.to_lowercase();
if matches!(ext.as_str(), "png" | "jpg" | "jpeg") {
return std::fs::read(src)
.map_err(|e| format!("Erro ao ler arquivo de logo '{}': {}", src, e));
}
base64::engine::general_purpose::STANDARD
.decode(src)
.map_err(|e| format!("Erro ao decodificar logo base64: {}", e))
}
}