tiss-hash (Rust)
Calcula a "impressão digital" do trecho final de um documento TISS/ANS. Antes do código, os termos essenciais:
- XML: formato de arquivo de texto que organiza dados em etiquetas (tags) aninhadas, como caixas dentro de caixas. O Padrão TISS é o XML usado por operadoras de saúde e consultórios no Brasil para trocar dados de atendimento.
- Hash: uma sequência curta e fixa de caracteres calculada a partir de um texto, como uma impressão digital. Mude uma letra, o hash muda inteiro.
- MD5: a receita (algoritmo) que produz esse hash; sempre 32 caracteres
hexadecimais (
0-9ea-f). - Epílogo: a parte final do documento TISS, a etiqueta
<ans:hash>, onde o hash precisa ser gravado. - Byte: a menor unidade de dado do computador; um arquivo de texto é uma fila de bytes.
Em uma frase: você passa os bytes de um XML TISS e recebe os 32 caracteres do
hash. Este é o port Rust ("port" = a mesma lib reescrita em outra linguagem) da
biblioteca lib_hash_ans. Para
entender o problema que a lib resolve, veja
docs/USAGE.md (guia de uso) e
docs/ARCHITECTURE.md (conceitos e visão geral).
Status: alpha. 20/20 vetores sintéticos PASS contra a referência Python (18 positivos + 2 negativos).
Antes de começar: instalar o Rust
Rust é a linguagem deste port. A forma oficial de instalar é o rustup, que
traz o compilador (rustc) e o gerenciador de pacotes/build (cargo):
- Siga o site oficial: https://www.rust-lang.org/tools/install
(no Linux/macOS é um único comando que o site mostra; no Windows há um
instalador
.exe). - Confira a instalação:
Precisa do rustc 1.75 ou mais novo. (O MSRV, "Minimum Supported Rust
Version", é a versão mínima de Rust suportada.)
Quickstart
Uma dependência ("crate", no jargão Rust) é uma biblioteca de terceiros que o seu projeto usa. O comando abaixo adiciona esta lib como dependência do seu projeto:
use ;
API pública
| Símbolo | Tipo | Descrição |
|---|---|---|
hash_tiss(xml: &[u8]) -> Result<String, TissHashError> |
função | Hash MD5 (hex, 32 chars lowercase) a partir dos bytes do XML. |
hash_tiss_file<P: AsRef<Path>>(path: P) -> Result<String, TissHashError> |
função | Atalho que lê arquivo do disco e delega para hash_tiss. |
TissHashError |
enum | InvalidXml(String) para XML malformado, Io(std::io::Error) para falha de leitura. |
TISS_NAMESPACE |
const | URI do namespace TISS: http://www.ans.gov.br/padroes/tiss/schemas. |
TissHashError implementa std::error::Error, Display, Debug e
conversões From<io::Error> / From<roxmltree::Error>.
Algoritmo
Resumo (spec canônica em
docs/SPEC.md):
- Parse do XML.
- Zera o conteúdo de
<ans:hash>(namespace TISS). - Concatena o
.textde cada nó-folha (elemento ou comentário sem filhosElement/Comment/PI), em ordem de documento. - MD5 sobre os bytes UTF-8 da string concatenada.
- Retorna
hexdigest()em minúsculo (32 chars).
Crítico: o encoding dos bytes que alimentam o MD5 é UTF-8, não ISO-8859-1, apesar do que diz o Componente Organizacional do TISS (pág 53, item 146). Ver
docs/SPEC.md §4.
Decisões fixadas pela conformance
15 comportamentos canônicos documentados em
conformance/AMBIGUITY_NOTES.md.
Resumo do que esta crate reproduz:
- Comentários XML
<!---->entram no concat (subproduto delxml.iter()na referência; replicado aqui viaroxmltree::Node::is_comment()). - CDATA tratado como texto literal.
- Entidades XML predefinidas decodificadas pelo parser antes do concat.
- Atributos e prefixos de namespace não entram.
- Whitespace dentro de valor preservado literalmente.
- Indentação entre tags não entra (não-folhas são puladas).
- Valores numéricos com zeros à esquerda preservados como string.
- BOM UTF-8 aceito e descartado pelo parser.
- Múltiplos
<ans:hash>no documento são rejeitados (erro), não tolerados. - Encodings suportados: ISO-8859-1 e UTF-8. UTF-16/UTF-32 são rejeitados (fora de escopo).
Encoding ISO-8859-1
Roxmltree exige &str UTF-8 (não aceita ISO-8859-1 nativamente). Esta crate
detecta a declaração encoding="iso-8859-1" no prólogo e converte os bytes
para UTF-8 via mapping bijetivo (byte n vira U+00n), reescrevendo a
declaração para encoding="utf-8" antes de passar ao parser. A semântica é
idêntica à da referência Python que delega ao lxml.
Parser escolhido: roxmltree
Decisão: roxmltree (não quick-xml, não xmltree).
Justificativa registrada no topo de src/lib.rs:
- roxmltree é DOM puro com API próxima de
ElementTree/lxml. Iteraçãodescendants()inclui nósCommentpor padrão, batendo com a semântica da referência sem ginástica. - quick-xml seria mais rápido (SAX), mas exige reconstruir manualmente o conceito de "folha" e tracking de pilha de profundidade. XMLs TISS reais geralmente < 5 MB; o ganho não justifica complexidade.
- xmltree é DOM básico, sem
descendants()ergonômico.
Conformance
Esta crate passa 20/20 vetores sintéticos públicos em
conformance/vectors.json: 18 positivos (mínimo, acento, vazio, CR/LF,
multi-guia, entidades, CDATA, comentário, atributos, namespace, whitespace,
leading zeros, ISO-8859-1 símbolos, namespace default, sem hash, entidade
numérica, perf grande ~600 KB, BOM UTF-8) e 2 negativos rejeitados
(syn_multi_hash.xml = múltiplos <ans:hash>; syn_utf16.xml = UTF-16
fora de escopo). A lista canônica vive em conformance/vectors.json.
Além dos vetores sintéticos públicos, o algoritmo foi validado contra
goldens reais (privados, fora do repo, em _private_tiss_real_xmls/).
Esses arquivos contêm PII e não são distribuídos; rodá-los exige acesso ao
diretório privado.
Rodar localmente
A partir da raiz do repositório (a pasta que você baixou com git clone):
Esperado: 20/20 no teste todos_vetores_passam, sem warnings de clippy.
(Um vetor de conformidade é um par "arquivo de entrada -> hash esperado";
positivo = deve produzir um hash, negativo = deve ser rejeitado.)
Comparação com o port Python
| Aspecto | Python (langs/python) |
Rust (langs/rust) |
|---|---|---|
| Parser | defusedxml (sobre stdlib) |
roxmltree |
| Decode ISO-8859-1 | feito pelo parser nativo | conversão manual (byte vira codepoint) |
| Comentários entram no concat | sim (lxml-like) |
sim (is_comment() + filtro) |
| API | hash_tiss, hash_tiss_file |
idem (Result<String, _>) |
| Erro | InvalidTissXml |
TissHashError::InvalidXml/Io |
| Vetores | 20/20 PASS | 20/20 PASS |
| MSRV / Python | Python 3.10+ | rustc 1.75+ |
| Dependências runtime | defusedxml>=0.7.1 |
roxmltree, md-5 |
Dependências e licenças
Dependências de runtime:
| Dependência | Licença | Uso |
|---|---|---|
roxmltree |
MIT OR Apache-2.0 | parser DOM |
md-5 (RustCrypto) |
MIT OR Apache-2.0 | MD5 |
Atribuição consolidada de todos os ports em
THIRD_PARTY_LICENSES.md na raiz do repo.
Licença
MIT (c) 2026 Petrus Silva Costa.
Ver também
docs/USAGE.md: guia de uso, receitas e perguntas frequentes (comece por aqui se você quer só usar a lib).docs/ARCHITECTURE.md: conceitos e visão geral de como tudo se encaixa.docs/SPEC.md: especificação canônica do algoritmo.conformance/AMBIGUITY_NOTES.md: 15 decisões canônicas fixadas pela referência.conformance/reference.py: implementação canônica em Python (o "oráculo" que define a resposta certa).docs/PORTING_GUIDE.md: guia para portar para outras linguagens.