Files
comparador-notas/src/infrastructure/csv_reader.rs
T

127 lines
3.8 KiB
Rust

use crate::domain::errors::{ErroArquivo, ResumoAvisos};
use encoding_rs::WINDOWS_1252;
use std::path::Path;
const LIMITE_BYTES: u64 = 50 * 1024 * 1024;
/// Resultado da leitura de um arquivo CSV.
pub struct ResultadoCsv {
/// Linhas de dados (já sem o cabeçalho), cada linha é um vetor de strings.
pub linhas: Vec<Vec<String>>,
/// Avisos coletados durante a leitura.
pub avisos: ResumoAvisos,
}
/// Lê um arquivo CSV e retorna as linhas de dados (sem o cabeçalho).
///
/// # Parâmetros
/// - `caminho`: caminho do arquivo
/// - `delimitador`: caractere delimitador (`,`, `;` ou `\t`)
/// - `encoding`: "utf-8" ou "windows-1252"
/// - `linha_cabecalho`: número da linha do cabeçalho (base 1). 0 = sem cabeçalho.
pub fn ler_csv(
caminho: &Path,
delimitador: u8,
encoding: &str,
linha_cabecalho: usize,
) -> Result<ResultadoCsv, ErroArquivo> {
// Verificar tamanho
let metadata = std::fs::metadata(caminho)
.map_err(|e| ErroArquivo::ErroLeitura(e.to_string()))?;
if metadata.len() > LIMITE_BYTES {
return Err(ErroArquivo::TamanhoExcedido(metadata.len()));
}
// Ler conteúdo bruto
let bytes = std::fs::read(caminho)
.map_err(|e| ErroArquivo::ErroLeitura(e.to_string()))?;
// Decodificar encoding
let conteudo = match encoding.to_lowercase().as_str() {
"windows-1252" | "latin-1" | "iso-8859-1" => {
let (decoded, _, _) = WINDOWS_1252.decode(&bytes);
decoded.into_owned()
}
_ => {
String::from_utf8(bytes)
.map_err(|e| ErroArquivo::ErroLeitura(format!("Encoding inválido: {}", e)))?
}
};
let mut avisos = ResumoAvisos::default();
let mut linhas_dados: Vec<Vec<String>> = Vec::new();
let mut numero_linha_arquivo: usize = 0;
let mut reader = csv::ReaderBuilder::new()
.delimiter(delimitador)
.has_headers(false)
.flexible(true)
.from_reader(conteudo.as_bytes());
for resultado in reader.records() {
numero_linha_arquivo += 1;
// Pular linhas antes ou na linha do cabeçalho
if linha_cabecalho > 0 && numero_linha_arquivo <= linha_cabecalho {
continue;
}
match resultado {
Ok(record) => {
let campos: Vec<String> = record.iter().map(|s| s.to_string()).collect();
// Ignorar linhas completamente em branco
if campos.iter().all(|s| s.trim().is_empty()) {
continue;
}
linhas_dados.push(campos);
}
Err(_) => {
avisos.linhas_malformadas += 1;
}
}
}
Ok(ResultadoCsv {
linhas: linhas_dados,
avisos,
})
}
/// Retorna as primeiras `n` linhas brutas do CSV (sem pular cabeçalho).
/// Usado exclusivamente para pré-visualização na UI.
pub fn preview_csv(
caminho: &Path,
delimitador: u8,
encoding: &str,
n: usize,
) -> Result<Vec<Vec<String>>, ErroArquivo> {
let bytes = std::fs::read(caminho)
.map_err(|e| ErroArquivo::ErroLeitura(e.to_string()))?;
let conteudo = match encoding.to_lowercase().as_str() {
"windows-1252" | "latin-1" | "iso-8859-1" => {
let (decoded, _, _) = WINDOWS_1252.decode(&bytes);
decoded.into_owned()
}
_ => String::from_utf8(bytes)
.map_err(|e| ErroArquivo::ErroLeitura(format!("Encoding inválido: {}", e)))?,
};
let mut reader = csv::ReaderBuilder::new()
.delimiter(delimitador)
.has_headers(false)
.flexible(true)
.from_reader(conteudo.as_bytes());
let linhas = reader
.records()
.take(n)
.filter_map(|r| r.ok())
.map(|r| r.iter().map(|s| s.to_string()).collect())
.collect();
Ok(linhas)
}