Uso interno da biblioteca: não faz parte da API pública.
Conversão, validação, escrita canônica e leitura de qualquer tipo complexo do schema.
Cada módulo gerado a partir do XSD guarda só o struct e o descritor. Este módulo lê o descritor e faz o resto, com as mesmas regras para todos os tipos.
Contrato do módulo de schema
__schema__(:version): a versão do leiaute, como"1.01"__schema__(:fields): os elementos, comoNfse.Schema.Field, na ordem do XSD__schema__(:attributes): os atributos, também comoNfse.Schema.Field__schema__(:choices):%{index: índice, min: 0 | 1}de cadachoice, com ominOccursdele__schema__(:simple_types): o módulo de tipos simples
O módulo de tipos simples responde facets(nome), com as facetas já achatadas (as da base
primeiro, depois as do próprio tipo), e kind(nome): :string, :date, :datetime ou
{:decimal, dígitos_inteiros, casas}.
Conversão
new/2 aceita mapa, lista de palavras-chave ou o próprio struct, também nos campos aninhados. As
chaves são os átomos dos campos. Uma chave que o tipo não declara, inclusive em forma de string, é
:unknown_field, e então a validação nem roda. Inteiro vira string num campo de texto, sem
completar com zeros. Decimal passa a ter exatamente as casas do tipo (49.9 vira 49.90), sem
arredondar. Lista vazia num campo repetido vale como campo ausente.
Validação
A ordem é obrigatoriedade, tipo, facetas, choice e ocorrências. Os erros voltam juntos em
details, cada um com o caminho do campo, como toma.emails[1] (o índice começa em zero), ou
"" quando o erro é do próprio documento.
:stringaceita só string UTF-8 válida com os caracteres que o XML 1.0 permite: tab, LF, CR e de U+0020 em diante, menos os substitutos e U+FFFE e U+FFFF. Isso vale também onde o XSD não tempattern, e também na leitura. Espaço nunca é aparado, porque cadawhiteSpacedo pacote oficial épreserve, e string vazia é um valor, não ausência- um
choicecom dois ramos preenchidos é sempre erro. Sem ramo preenchido, só é erro quando ochoicetemmin: 1e cada ramo temmin: 1, porque um ramo opcional pode ser escolhido vazio :dateaceita sóDate, e:datetimesóDateTime- decimal aceita só
Decimal; casas a mais são:fraction_digitse dígitos inteiros a mais são:integer_digits - as facetas valem sobre a forma escrita do valor, e cada
patternda lista precisa casar
| Erro | Quando |
|---|---|
:unknown_field | chave, elemento ou atributo que o tipo não declara |
:required | campo obrigatório ausente |
:type | valor de outro tipo, caractere que o XML proíbe, ou texto solto entre elementos |
:length, :min_length, :max_length | comprimento fora da faceta, em caracteres Unicode |
:enumeration | valor fora da lista do tipo |
:pattern | algum pattern do tipo não casa |
:integer_digits, :fraction_digits | decimal com dígitos inteiros ou casas a mais |
:choice | mais de um ramo de um choice, ou nenhum quando algum é obrigatório |
:occurrences | mais repetições do que o maxOccurs permite |
Escrita
encode/4 escreve na forma canônica (C14N): os atributos extras, que são as declarações de
namespace, saem primeiro, na ordem dada, e depois os atributos do struct, em ordem de nome. Não há
espaço entre tags, elemento vazio sai <x></x> e nunca <x/>, campo ausente é omitido e o
escaping é o de Nfse.Schema.Xml. Data e hora saem com o offset do próprio DateTime
(2026-11-03T10:00:00-03:00), sem frações de segundo e nunca com Z. Decimal sai com exatamente
as casas do tipo. A assinatura (:signature) sai como veio.
encode/4 espera um struct já validado e levanta ArgumentError diante de um valor que só daria
para escrever arredondando ou truncando.
Leitura
decode/2 lê a árvore do Saxy.SimpleForm. As facetas são conferidas no texto antes de qualquer
conversão, o que mantém um decimal com expoente longe do Decimal. Data e hora voltam em UTC.
Elemento ou atributo que o tipo não declara é :unknown_field, e xmlns e xmlns:* são
ignorados. Espaço entre elementos (XML indentado) é ignorado, e texto de verdade nesse lugar é
:type. A assinatura volta como o XML do elemento, escrito pelo Saxy.encode!/1. Depois da
leitura, o documento passa pela mesma validação de new/2.
Onde o runtime diverge do XSD
- O padrão do
TSSerieDPSé^0{0,4}\d{1,5}$. No padrão XSD,^e$são caracteres literais, e oxmllintrecusa a série"1"e aceita"^1$". A intenção evidente é ancorar, e é o que se espera do validador da Sefin, ainda a confirmar na produção restrita. Por isso o runtime tira um^inicial e um$final antes de compilar o padrão. - Cada padrão vira uma regex
\A(?:...)\zem Unicode, em que[!-ÿ]é a faixa de U+0021 a U+00FF, e o ponto não casa CR nem LF, como no XSD. - Os decimais não passam por
Decimal.round/2, que segue a precisão do contexto do processo de quem chama. As contas são feitas direto sobre o coeficiente e o expoente.
Summary
Functions
Lê o elemento como um struct de module e valida o resultado.
Escreve o struct como o elemento tag, na forma canônica, com extra_attributes antes dos
atributos do struct.
Converte attrs no struct de module e valida o resultado.
Valida um struct de module sem convertê-lo.
Types
Functions
@spec decode(module(), Saxy.SimpleForm.t()) :: {:ok, struct()} | {:error, Nfse.Error.t()}
Lê o elemento como um struct de module e valida o resultado.
Escreve o struct como o elemento tag, na forma canônica, com extra_attributes antes dos
atributos do struct.
Converte attrs no struct de module e valida o resultado.
@spec validate(module(), struct()) :: :ok | {:error, Nfse.Error.t()}
Valida um struct de module sem convertê-lo.