Skip to main content

Todo CLI in Rust 4. JSON persistence, testing, and scale-ready decisions

·2 mins
Rafael Fernandez
Author
Rafael Fernandez
Mathematics, programming, and life stuff
Todo CLI in Rust without fluff - This article is part of a series.
Part 4: This Article

Final implementation chapter.

Read this chapter in Spanish: ES

This is where projects either become reliable, or quietly become fragile: persistence and tests.

Code references:

Contract first, implementation second
#

We defined TaskRepository before touching disk.

Why this instead of direct JSON calls from use cases
#

If use cases know JSON details, they become infrastructure scripts.

With a repository port, use cases remain focused on business operations (save, find, list, delete) and adapters handle storage concerns.

Two adapters, two jobs
#

InMemoryTaskRepository:

  • fast,
  • great for behavior tests,
  • no filesystem overhead.

JsonFileTaskRepository:

  • real persistence,
  • serialization,
  • I/O and parsing failure paths.

This split gives faster feedback and clearer fault isolation.

JSON persistence decisions
#

  1. platform-aware data location via directories;
  2. create parent dirs when missing;
  3. missing file means empty initial state;
  4. invalid JSON returns explicit error.

Why not auto-heal corrupted JSON
#

Because silent healing often means silent data loss.

Failing with context is less convenient in the moment, but much safer long term.

Operation semantics
#

  • save: upsert by id.
  • list: All or ByStatus.
  • find_by_id: Option<Task>.
  • delete: bool (deleted vs not found).

This keeps normal outcomes (like “not found” on delete) separate from real technical failures.

Test strategy
#

Coverage includes:

  • save/find by id,
  • list all / list by status,
  • delete existing / non-existing,
  • persistence across repository instances,
  • explicit error on invalid JSON.

JSON tests use tempdir, so user data remains untouched.

Commits that complement this chapter
#

Explicit technical debt
#

Still intentionally open:

  • RepoError is broad (InternalError + message),
  • no file locking for multi-process concurrency,
  • no JSON schema versioning/migration layer.

Reasonable for current scope, important to tackle for heavier usage.

Closing
#

The project started as a “simple todo CLI” and became a full Rust architecture exercise:

  • domain invariants,
  • use-case orchestration,
  • adapter isolation,
  • stable CLI contract,
  • behavior-focused tests.

That is the real payoff: training scalable engineering decisions in a small, controlled project.

Todo CLI in Rust without fluff - This article is part of a series.
Part 4: This Article