Ir al contenido
  1. Posts/

Todo CLI en Rust 3. Creando el CLI con clap

·2 mins
Rafael Fernandez
Autor
Rafael Fernandez
Matemáticas, programación y cosas de la vida
Todo CLI en Rust sin humo - Este artículo es parte de una serie.
Parte 3: Este artículo

Con dominio y casos de uso listos, tocaba la capa que el usuario realmente toca: el CLI.

Read this chapter in English: EN

Aquí la pregunta era simple: ¿queremos una CLI que “funcione” o una CLI que la gente quiera usar sin pelearse con ella?

Código de referencia:

Parser fuerte con clap
#

Subcomandos definidos:

  • add {title}
  • list {--status}
  • done {id}
  • todo {id}
  • delete {id}

Además, --output table|json como flag global.

Por qué ValueEnum en vez de parse manual
#

StatusArg y OutputFormat son enums con ValueEnum.

Podríamos recibir strings y validar con if/match propio. El problema es que eso duplica lógica de validación y te obliga a mantener mensajes de error a mano.

Con clap, valores inválidos fallan en borde de entrada, antes de ejecutar lógica de negocio.

UUID como tipo de entrada, no como texto tardío
#

En done/todo/delete, id entra como Uuid directamente.

Esto evita mover validación de formato a use cases, donde ya deberíamos estar hablando de negocio y no de parsing.

Salida dual: table para humanos, json para máquinas
#

En muchas CLI se imprime texto bonito y luego alguien intenta automatizar y descubre que no hay contrato estable.

Aquí resolvimos eso desde diseño:

  • table: lectura rápida en terminal.
  • json: payload estable para scripts.

Ejemplo de uso:

cargo run -- list
cargo run -- --output json list --status done

Caso interesante: delete
#

delete devuelve una semántica idempotente visible:

  • deleted = true si encontró y borró,
  • deleted = false si no existía.

En JSON además devolvemos message estructurado. En table, una salida corta y legible.

Eso hace que el mismo comando sirva para operador humano y para pipeline CI/CD sin hacks de parsing.

Orquestación en main.rs
#

run() hace tres cosas y no más:

  1. parsea comando,
  2. instancia repositorio,
  3. despacha al use case correspondiente.

No hay reglas de negocio ahí. Solo cableado.

Cuando run() falla, main() imprime en stderr y sale con código 1. Contrato de CLI limpio y estándar.

Complemento con historial del repo
#

Este capítulo se conecta muy bien con este commit:

Si lo revisas, se ve claramente el salto de “CLI funcional” a “CLI usable por humanos y scripts”.

Cierre
#

Una CLI robusta no se mide por cuántos comandos tiene, sino por qué tan predecible es su contrato.

En el cierre de la serie vamos al último bloque: persistencia JSON, tests y deuda técnica explícita.

Todo CLI en Rust sin humo - Este artículo es parte de una serie.
Parte 3: Este artículo