enderecobr_rs/
lib.rs

1#![doc = include_str!("../README.md")]
2
3use std::borrow::Cow;
4use unicode_normalization::UnicodeNormalization;
5
6use itertools::Itertools;
7use regex::{Regex, RegexSet};
8
9pub mod bairro;
10pub mod cep;
11pub mod complemento;
12pub mod estado;
13pub mod logradouro;
14pub mod metaphone;
15pub mod municipio;
16pub mod numero;
17pub mod numero_extenso;
18pub mod separador_endereco;
19pub mod tipo_logradouro;
20
21/// Representa um endereço separado em seus atributos constituintes.
22#[derive(Debug, PartialEq, Default)]
23pub struct Endereco {
24    pub logradouro: Option<String>,
25    pub numero: Option<String>,
26    pub complemento: Option<String>,
27    pub localidade: Option<String>,
28}
29
30impl Endereco {
31    /// Obtém o logradouro padronizado, utilizando a função [padronizar_logradouros].
32    pub fn logradouro_padronizado(&self) -> Option<String> {
33        self.logradouro
34            .as_ref()
35            .map(|x| padronizar_logradouros(x.as_str()))
36    }
37
38    /// Obtém o número padronizado, utilizando a função [padronizar_numeros].
39    pub fn numero_padronizado(&self) -> Option<String> {
40        self.numero.as_ref().map(|x| padronizar_numeros(x.as_str()))
41    }
42
43    /// Obtém o complemento padronizado, utilizando a função [padronizar_complementos].
44    pub fn complemento_padronizado(&self) -> Option<String> {
45        self.complemento
46            .as_ref()
47            .map(|x| padronizar_complementos(x.as_str()))
48    }
49
50    /// Obtém a localidade padronizada, utilizando a função [padronizar_bairros].
51    pub fn localidade_padronizada(&self) -> Option<String> {
52        self.localidade
53            .as_ref()
54            .map(|x| padronizar_bairros(x.as_str()))
55    }
56
57    /// Obtém uma nova struct [Endereco] com todos os campos padronizados,
58    /// utilizando os métodos anteriores.
59    pub fn endereco_padronizado(&self) -> Endereco {
60        Endereco {
61            logradouro: self.logradouro_padronizado(),
62            numero: self.numero_padronizado(),
63            complemento: self.complemento_padronizado(),
64            localidade: self.localidade_padronizada(),
65        }
66    }
67
68    /// Obtém uma representação textual dos atributos desta struct,
69    /// separados por vírgula, caso existam.
70    pub fn formatar(&self) -> String {
71        [
72            &self.logradouro,
73            &self.numero,
74            &self.complemento,
75            &self.localidade,
76        ]
77        .iter()
78        .filter_map(|opt| opt.as_deref())
79        .map(|x| x.trim())
80        .join(", ")
81    }
82}
83
84/// Representa um par de "regexp replace". Usado internamente no [Padronizador].
85#[derive(Debug)]
86pub struct ParSubstituicao {
87    regexp: Regex,
88    substituicao: String,
89    regexp_ignorar: Option<Regex>,
90}
91
92impl ParSubstituicao {
93    fn new(regex: &str, substituicao: &str, regex_ignorar: Option<&str>) -> Self {
94        ParSubstituicao {
95            regexp: Regex::new(regex).unwrap(),
96            substituicao: substituicao.to_uppercase().to_string(),
97            regexp_ignorar: regex_ignorar.map(|r| Regex::new(r).unwrap()),
98        }
99    }
100}
101
102/// Estrutura responsável por padronizar textos de endereços com base em regras de substituição
103/// regulares condicionais.
104///
105/// O `Padronizador` permite definir regras de substituição com expressões regulares, incluindo
106/// condições de exclusão (`regexp_ignorar`). Ele otimiza o processamento usando um [`RegexSet`]
107/// para identificar rapidamente quais regras se aplicam a cada estágio da padronização.
108#[derive(Default)]
109pub struct Padronizador {
110    substituicoes: Vec<ParSubstituicao>,
111    grupo_regex: RegexSet,
112}
113
114impl Padronizador {
115    /// Adiciona múltiplas regras de substituição a partir de uma lista de triplas.
116    ///
117    /// Cada entrada é um slice de até três elementos: `[regex, substituição, ignorar]`.
118    /// Valores ausentes (`None`) são descartados e as triplas serão interpretados da seguinte forma:
119    /// - Se existir um elemento não nulo: equivalente a `[regex, '']`;
120    /// - Se existirem dois elementos não nulos: equivalente a `[regex, substituição]`;
121    /// - Se existirem três ou mais elementos não nulos: equivalente a `[regex, substituição,
122    /// ignorar]`;
123    ///
124    /// O método [`preparar`](Self::preparar) é chamado automaticamente no término da execução.
125    ///
126    /// Este método é projetado para interoperabilidade com linguagens dinâmicas (ex: Python),
127    /// onde estruturas heterogêneas são comuns.
128    pub fn adicionar_pares(&mut self, pares: &[&[Option<&str>]]) {
129        for p in pares
130            .iter()
131            .map(|p| p.iter().filter_map(|i| i.as_ref()).collect::<Vec<_>>())
132        {
133            if p.is_empty() {
134                continue;
135            }
136            if p.len() == 1 {
137                self.adicionar(p[0], "");
138            }
139            if p.len() == 2 {
140                self.adicionar(p[0], p[1]);
141            }
142            if p.len() >= 3 {
143                self.adicionar_com_ignorar(p[0], p[1], p[2]);
144            }
145        }
146        self.preparar();
147    }
148
149    /// Adiciona regras de substituição a partir de três vetores paralelos: regexes, substituições
150    /// e regexes de exclusão opcional.
151    ///
152    /// O método [`preparar`](Self::preparar) é chamado automaticamente no término da execução.
153    ///
154    /// Todos os vetores devem ter o mesmo comprimento. O terceiro vetor pode conter `None`
155    /// para indicar ausência de condição de exclusão.
156    ///
157    /// Este formato facilita a integração com R, onde dados tabulares são naturais.
158    ///
159    /// # Panics
160    ///
161    /// Panic se os vetores não tiverem o mesmo tamanho.
162    pub fn adicionar_vetores(
163        &mut self,
164        regexes: &[&str],
165        substituicao: &[&str],
166        regex_ignorar: &[Option<&str>],
167    ) {
168        assert!(
169            regexes.len() == substituicao.len() && regexes.len() == regex_ignorar.len(),
170            "O tamanho dos três vetores devem ser iguais."
171        );
172
173        for ((r, s), i) in regexes.iter().zip(substituicao).zip(regex_ignorar) {
174            if let Some(regex_ignorar) = i {
175                self.adicionar_com_ignorar(r, s, regex_ignorar);
176            } else {
177                self.adicionar(r, s);
178            }
179        }
180        self.preparar();
181    }
182
183    /// Adiciona uma regra simples de substituição: toda ocorrência de `regex` será substituída
184    /// por `substituicao`.
185    ///
186    /// A expressão regular é compilada imediatamente. Use [`preparar`](Self::preparar) após
187    /// adicionar as regras para o correto funcionamento da padronização.
188    ///
189    /// Retorna uma referência mutável para encadeamento (builder pattern).
190    pub fn adicionar(&mut self, regex: &str, substituicao: &str) -> &mut Self {
191        self.substituicoes
192            .push(ParSubstituicao::new(regex, substituicao, None));
193        self
194    }
195
196    /// Adiciona uma regra condicional de substituição: `regex` será substituído por `substituicao`
197    /// apenas se `regexp_ignorar` **não** corresponder ao texto.
198    ///
199    /// Ambas as expressões regulares são compiladas imediatamente.
200    ///
201    /// Útil para evitar substituições em contextos indesejados (ex: não converter "R" em "RUA"
202    /// dentro de "APT R 10").
203    ///
204    /// Use [`preparar`](Self::preparar) após adicionar as regras para o correto funcionamento da padronização.
205    ///
206    /// Retorna uma referência mutável para encadeamento (padrão builder).
207    pub fn adicionar_com_ignorar(
208        &mut self,
209        regex: &str,
210        substituicao: &str,
211        regexp_ignorar: &str,
212    ) -> &mut Self {
213        self.substituicoes.push(ParSubstituicao::new(
214            regex,
215            substituicao,
216            Some(regexp_ignorar),
217        ));
218        self
219    }
220
221    /// Compila o conjunto de expressões regulares principais em um [`RegexSet`] para acelerar
222    /// a detecção de matches durante a padronização. Essencial para o correto funcionamento
223    /// da função de padronização.
224    ///
225    /// Deve ser chamado após adicionar, antes de usar [`padronizar`](Self::padronizar).
226    pub fn preparar(&mut self) {
227        let regexes: Vec<&str> = self
228            .substituicoes
229            .iter()
230            .map(|par| par.regexp.as_str())
231            .collect();
232
233        self.grupo_regex = RegexSet::new(regexes).unwrap();
234    }
235
236    /// Aplica todas as regras de substituição ao texto de entrada até que nenhuma nova
237    /// substituição seja possível.
238    ///
239    /// O texto é primeiro normalizado (remoção de acentos, conversão para maiúsculas e
240    /// remoção de espaços extras). As regras são aplicadas em ordem, mas apenas
241    /// se a condição de exclusão (se presente) não for satisfeita.
242    ///
243    /// Retorna uma nova `String` com o texto padronizado.
244    pub fn padronizar(&self, valor: &str) -> String {
245        return self.padronizar_cow(valor).to_string();
246    }
247
248    // Função otimizada para não re-alocar strings quando desnecessário.
249    // TODO: Adaptar demais métodos para aceitar e retornar Cow<str>
250    fn padronizar_cow<'a>(&self, valor: &'a str) -> Cow<'a, str> {
251        let mut preproc = normalizar(valor);
252        let mut ultimo_idx: Option<usize> = None;
253
254        while self.grupo_regex.is_match(&preproc) {
255            let idx_substituicao = self
256                .grupo_regex
257                .matches(&preproc)
258                .iter()
259                .find(|idx| ultimo_idx.is_none_or(|ultimo| *idx > ultimo));
260
261            let Some(idx) = idx_substituicao else {
262                break;
263            };
264
265            ultimo_idx = idx_substituicao;
266            let par = &self.substituicoes[idx];
267
268            // FIXME: essa solução dá problema quando eu tenho mais de um match da regexp
269            // original. Precisaria de uma heurística melhor.
270            if par
271                .regexp_ignorar
272                .as_ref()
273                .map(|r| r.is_match(&preproc))
274                .unwrap_or(false)
275            {
276                continue;
277            }
278
279            let novo_valor = par.regexp.replace_all(&preproc, par.substituicao.as_str());
280            // Se chegou aqui, é porque a string deveria sofrer modificação e, consequentemente,
281            // retornar um Cow::Owned.
282            preproc = match novo_valor {
283                Cow::Owned(novo) => Cow::Owned(novo),
284                Cow::Borrowed(_) => preproc, // Não deveria acontecer
285            };
286        }
287
288        preproc
289    }
290
291    /// Retorna todas as regras atuais como um vetor de triplas.
292    ///
293    /// Cada tripla contém: `(regex, substituicao, regex_ignorar)`.
294    ///
295    /// Útil para inspeção ou incremento dos padrões.
296    ///
297    pub fn obter_pares(&self) -> Vec<(&str, &str, Option<&str>)> {
298        self.substituicoes
299            .iter()
300            .map(|par| {
301                (
302                    par.regexp.as_str(),
303                    par.substituicao.as_str(),
304                    par.regexp_ignorar.as_ref().map(Regex::as_str),
305                )
306            })
307            .collect()
308    }
309
310    /// Retorna as regras como três vetores paralelos: regexes, substituições e regexes de exclusão.
311    ///
312    /// Ideal para uso em R, onde estruturas vetoriais são preferidas.
313    ///
314    /// Retorna uma tupla de vetores `(regex, substituicao, ignorar)`, onde `ignorar` é um vetor de `Option<&str>`.
315    pub fn obter_vetores(&self) -> (Vec<&str>, Vec<&str>, Vec<Option<&str>>) {
316        let regex = self
317            .substituicoes
318            .iter()
319            .map(|par| par.regexp.as_str())
320            .collect();
321        let subst = self
322            .substituicoes
323            .iter()
324            .map(|par| par.substituicao.as_str())
325            .collect();
326        let ignorar = self
327            .substituicoes
328            .iter()
329            .map(|par| par.regexp_ignorar.as_ref().map(Regex::as_str))
330            .collect();
331        (regex, subst, ignorar)
332    }
333}
334
335/// Função utilitária usada internamente para normalizar uma string para processamento posterior,
336/// removendo seus diacríticos e caracteres especiais.
337///
338/// # Exemplo
339/// ```
340/// use enderecobr_rs::normalizar;
341/// assert_eq!(normalizar("Olá, mundo"), "OLA, MUNDO");
342/// assert_eq!(normalizar("R. DO AÇAÍ 15º"), "R. DO ACAI 15O");
343/// ```
344///
345pub fn normalizar(valor: &str) -> Cow<'_, str> {
346    let valor = valor.trim();
347
348    if valor.is_ascii() {
349        if valor
350            .bytes()
351            .all(|c| !c.is_ascii_alphabetic() || c.is_ascii_uppercase())
352        {
353            return Cow::Borrowed(valor.trim());
354        }
355        return Cow::Owned(valor.trim().to_ascii_uppercase());
356    }
357
358    valor
359        .nfkd()
360        .filter(|c| c.is_ascii())
361        .map(|c| c.to_ascii_uppercase())
362        .collect()
363}
364
365pub use bairro::padronizar_bairros;
366pub use cep::padronizar_cep;
367pub use cep::padronizar_cep_leniente;
368pub use cep::padronizar_cep_numerico;
369pub use complemento::padronizar_complementos;
370pub use estado::padronizar_estados_para_codigo;
371pub use estado::padronizar_estados_para_nome;
372pub use estado::padronizar_estados_para_sigla;
373pub use logradouro::padronizar_logradouros;
374pub use municipio::padronizar_municipios;
375pub use numero::padronizar_numeros;
376pub use numero::padronizar_numeros_para_int;
377pub use numero::padronizar_numeros_para_string;
378pub use tipo_logradouro::padronizar_tipo_logradouro;
379
380#[cfg(feature = "experimental")]
381pub use separador_endereco::padronizar_endereco_bruto;
382
383#[cfg(feature = "experimental")]
384pub use separador_endereco::separar_endereco;
385
386/// Função utilitária utilizada nas ferramentas de CLI para selecionar um padronizador facilmente
387/// via uma string descritiva.
388pub fn obter_padronizador_por_tipo(tipo: &str) -> Result<fn(&str) -> String, &str> {
389    match tipo {
390        "logradouro" | "logr" => Ok(padronizar_logradouros),
391        "tipo_logradouro" | "tipo_logr" => Ok(padronizar_tipo_logradouro),
392        "numero" | "num" => Ok(padronizar_numeros),
393        "bairro" => Ok(padronizar_bairros),
394        "complemento" | "comp" => Ok(padronizar_complementos),
395        "estado" => Ok(|x| padronizar_estados_para_sigla(x).to_string()),
396        "estado_nome" => Ok(|x| padronizar_estados_para_nome(x).to_string()),
397        "estado_codigo" => Ok(|x| padronizar_estados_para_codigo(x).to_string()),
398        "municipio" | "mun" => Ok(padronizar_municipios),
399        "cep" => Ok(|cep| padronizar_cep(cep).unwrap_or("".to_string())),
400        "cep_leniente" => Ok(padronizar_cep_leniente),
401        "metaphone" => Ok(metaphone::metaphone),
402
403        #[cfg(feature = "experimental")]
404        "completo" => Ok(padronizar_endereco_bruto),
405
406        #[cfg(feature = "experimental")]
407        "separar" => Ok(|val| format!("{:?}", separar_endereco(val))),
408
409        #[cfg(feature = "experimental")]
410        "separar_padronizar" => {
411            Ok(|val| format!("{:?}", separar_endereco(val).endereco_padronizado()))
412        }
413
414        _ => Err("Nenhum padronizador encontrado"),
415    }
416}
417
418/////////////////
419
420#[cfg(test)]
421mod tests {
422    use super::*;
423
424    #[test]
425    fn test_obter_pares_e_vetores_vazios() {
426        let pad = Padronizador::default();
427        assert_eq!(pad.obter_pares(), vec![]);
428        assert_eq!(pad.obter_vetores(), (vec![], vec![], vec![]));
429    }
430
431    #[test]
432    fn test_adicionar_pares() {
433        let mut pad = Padronizador::default();
434        pad.adicionar_pares(&[
435            &[Some("R"), Some("RUA")],                         // par (regex, subst)
436            &[Some("AV"), Some("AVENIDA"), Some("COMERCIAL")], // tripla com ignorar
437            &[Some("ESC"), None, Some("ESCOLA")],              // ignora None
438            &[None],                                           // ignora totalmente
439        ]);
440
441        let pares = pad.obter_pares();
442        assert_eq!(
443            pares,
444            vec![
445                ("R", "RUA", None),
446                ("AV", "AVENIDA", Some("COMERCIAL")),
447                ("ESC", "ESCOLA", None),
448            ]
449        );
450
451        let (regex, subst, ignorar) = pad.obter_vetores();
452        assert_eq!(regex, vec!["R", "AV", "ESC"]);
453        assert_eq!(subst, vec!["RUA", "AVENIDA", "ESCOLA"]);
454        assert_eq!(ignorar, vec![None, Some("COMERCIAL"), None]);
455    }
456
457    #[test]
458    fn test_adicionar_vetores() {
459        let mut pad = Padronizador::default();
460        pad.adicionar_vetores(
461            &["NUM", "R", "AV"],
462            &["NUMERO", "RUA", "AVENIDA"],
463            &[None, Some("R$"), Some("AVENIDA COMERCIAL")],
464        );
465
466        let pares = pad.obter_pares();
467        assert_eq!(
468            pares,
469            vec![
470                ("NUM", "NUMERO", None),
471                ("R", "RUA", Some("R$")),
472                ("AV", "AVENIDA", Some("AVENIDA COMERCIAL")),
473            ]
474        );
475
476        let (regex, subst, ignorar) = pad.obter_vetores();
477        assert_eq!(regex, vec!["NUM", "R", "AV"]);
478        assert_eq!(subst, vec!["NUMERO", "RUA", "AVENIDA"]);
479        assert_eq!(ignorar, vec![None, Some("R$"), Some("AVENIDA COMERCIAL")]);
480    }
481
482    #[test]
483    #[should_panic(expected = "O tamanho dos três vetores devem ser iguais.")]
484    fn test_adicionar_vetores_tamanho_diferente() {
485        let mut pad = Padronizador::default();
486        pad.adicionar_vetores(&["a"], &["b"], &[Some("x"), Some("y")]);
487    }
488}