AnchoFijo.Diagnostico (ancho_fijo v0.1.0)

Copy Markdown View Source

Un error que se puede leer, pegar en un correo y mandarle al banco.

Esta librería no devuelve {:error, :invalid}. Cada falla es un %AnchoFijo.Diagnostico{} que responde cuatro preguntas: dónde pasó (línea, campo, posiciones), qué se esperaba, qué llegó y cuál es la causa probable.

iex> alias AnchoFijo.Diagnostico
iex> d = Diagnostico.nuevo(
...>   tipo: :largo_de_linea,
...>   linea: 42,
...>   esperado: "120 caracteres",
...>   recibido: "118",
...>   causa_probable: "posible campo faltante o archivo delimitado"
...> )
iex> Diagnostico.mensaje(d)
"línea 42: se esperaban 120 caracteres, llegaron 118; posible campo faltante o archivo delimitado"

La causa probable es una hipótesis, no un veredicto. Existe porque en una integración bancaria el ciclo de feedback es de días: quien lee el error a las 3 AM necesita una pista de dónde mirar, no solo la constatación de que algo falló.

Summary

Types

t()

Familia del problema, para poder filtrar o agrupar sin parsear el mensaje.

Functions

Redacta el diagnóstico como una sola línea de texto en español.

Construye un diagnóstico desde una keyword list o mapa de atributos.

Redacta una lista de diagnósticos como texto multilínea, uno por renglón.

Types

t()

@type t() :: %AnchoFijo.Diagnostico{
  campo: atom() | nil,
  causa_probable: String.t() | nil,
  contenido: String.t() | nil,
  esperado: String.t() | nil,
  linea: pos_integer() | nil,
  posicion: {pos_integer(), pos_integer()} | nil,
  recibido: String.t() | nil,
  tipo: tipo()
}

tipo()

@type tipo() :: :layout | :entrada | :largo_de_linea | :encoding | :campo_invalido

Familia del problema, para poder filtrar o agrupar sin parsear el mensaje.

  • :layout — la definición del formato es inconsistente consigo misma.
  • :entrada — no se pudo leer el archivo o el argumento no es un binario.
  • :largo_de_linea — la línea no mide lo que el layout declara.
  • :encoding — hay bytes que no corresponden al encoding declarado.
  • :campo_invalido — el contenido del campo no calza con su tipo.

Functions

mensaje(diagnostico)

@spec mensaje(t()) :: String.t()

Redacta el diagnóstico como una sola línea de texto en español.

iex> alias AnchoFijo.Diagnostico
iex> d = Diagnostico.nuevo(
...>   tipo: :campo_invalido,
...>   linea: 7,
...>   campo: :monto,
...>   posicion: {21, 32},
...>   esperado: "12 dígitos",
...>   recibido: "\"0001234A567\"",
...>   causa_probable: "hay un carácter no numérico en el monto"
...> )
iex> Diagnostico.mensaje(d)
"línea 7, campo :monto (posiciones 21-32): se esperaban 12 dígitos, llegaron \"0001234A567\"; hay un carácter no numérico en el monto"

Sin causa probable el mensaje simplemente la omite:

iex> AnchoFijo.Diagnostico.nuevo(tipo: :entrada, esperado: "un archivo legible", recibido: ":enoent")
...> |> AnchoFijo.Diagnostico.mensaje()
"entrada: se esperaba un archivo legible, llegó :enoent"

nuevo(atributos)

@spec nuevo(Enumerable.t()) :: t()

Construye un diagnóstico desde una keyword list o mapa de atributos.

Los valores de :esperado y :recibido se normalizan a texto: lo que no sea binario pasa por inspect/1, para que un diagnóstico nunca falle al armarse.

iex> AnchoFijo.Diagnostico.nuevo(tipo: :campo_invalido, campo: :monto, esperado: 12, recibido: nil)
%AnchoFijo.Diagnostico{tipo: :campo_invalido, campo: :monto, esperado: "12", recibido: nil}

reporte(diagnosticos)

@spec reporte([t()]) :: String.t()

Redacta una lista de diagnósticos como texto multilínea, uno por renglón.

iex> alias AnchoFijo.Diagnostico
iex> [
...>   Diagnostico.nuevo(tipo: :largo_de_linea, linea: 3, esperado: "40 bytes", recibido: "38"),
...>   Diagnostico.nuevo(tipo: :largo_de_linea, linea: 9, esperado: "40 bytes", recibido: "41")
...> ]
...> |> Diagnostico.reporte()
...> |> String.split("\n")
["línea 3: se esperaban 40 bytes, llegaron 38", "línea 9: se esperaban 40 bytes, llegaron 41"]