Skip to content

refactor: standardize JsonView render API with Render<T> trait and ViewRenderError#402

Merged
josecelano merged 8 commits intomainfrom
refactor/standardize-json-view-render-api
Feb 28, 2026
Merged

refactor: standardize JsonView render API with Render<T> trait and ViewRenderError#402
josecelano merged 8 commits intomainfrom
refactor/standardize-json-view-render-api

Conversation

@josecelano
Copy link
Member

@josecelano josecelano commented Feb 28, 2026

Summary

Implements the full standardize-json-view-render-api refactoring plan, plus a follow-up cleanup that emerged from review.

Changes

Proposal #1 — Add ViewRenderError and Render<T> trait (91798861)

  • Add ViewRenderError enum (wraps serde_json::Error via #[from]) in src/presentation/cli/views/render.rs
  • Add Render<T> trait with fn render(data: &T) -> Result<String, ViewRenderError>
  • Re-export both from src/presentation/cli/views/mod.rs
  • Implement Render<T> for all 13 JsonView structs (with JSON serialization)
  • Implement Render<T> for all 13 TextView structs (infallible, returns Ok)

Proposal #2 — Remove inherent render() from json_views, propagate errors (80616e44)

  • Remove all inherent pub fn render() methods from 13 json_view.rs files — rendering is now exclusively via the Render<T> trait
  • This removes 11 blocks of unreachable unwrap_or_else fallback dead code
  • Update all 13 command handler files to import Render and propagate errors with ?
  • Add OutputFormatting { reason: String } error variant + From<ViewRenderError> impl + help() arm to all 11 command error types
  • Update test modules in all json_view files to use the trait method

Follow-up — Remove inherent render() from text_views too (1c71cd7e)

Proposals #1 and #2 left TextView with an inherent pub fn render() -> String that delegated to the Render<T> trait impl. This created an asymmetry: JsonView was trait-only, TextView had an inherent convenience method. Applying the "least surprise" principle:

  • Remove inherent pub fn render() from all 13 text_view.rs files
  • Inline the method body directly into the Render<T> impl, wrapping the final expression in Ok(...)
  • Add ? to all 15 TextView::render() call sites in handler files
  • Add From<ViewRenderError> impl to CreateEnvironmentCommandError and ProvisionSubcommandError (Pattern 3 handlers not covered by Proposal Scaffolding for main app - 8/9 tasks complete (89%) #2)
  • Update doc examples and tests with .unwrap()

Motivation

Before this refactor:

  • 11 JsonView structs returned String with dead fallback code; 2 returned Result — inconsistent
  • No shared trait enforced the render signature
  • JSON serialization errors were silently swallowed
  • TextView had an inherent method that created an asymmetry with JsonView

After this refactor:

  • All 13 JsonView and 13 TextView structs implement the same Render<T> trait
  • No inherent render methods anywhere — all rendering goes through the trait
  • Compile-time enforcement of the render contract
  • Errors propagate correctly through the handler chain

Testing

  • 413 unit/doc tests pass
  • Clippy: zero warnings
  • All linters pass (cargo run --bin linter all)

Related

  • Closes docs/refactors/plans/standardize-json-view-render-api.md
  • Depends on: standardize-command-view-folder-structure (merged in d9a1c51)

Introduce ViewRenderError and Render<T> trait in presentation/cli/views/render.rs,
providing compile-time enforcement of a consistent render signature across all
command view modules.

Changes:
- Add src/presentation/cli/views/render.rs with ViewRenderError and Render<T>
- Re-export both from src/presentation/cli/views/mod.rs
- Add impl Render<T> for JsonView to all 13 json_view.rs files
  Body: Ok(serde_json::to_string_pretty(data)?)
- Add impl Render<T> for TextView to all 13 text_view.rs files
  Body: Ok(TextView::render(data)) — delegates to inherent render method

No call sites change. The existing inherent render() methods remain untouched.
Trait impls coexist alongside them; inherent methods shadow the trait in UFCS.

Proposal #2 will remove the inherent methods and update all call sites to use
the trait (adding ? propagation).
…ror through handler chain

- Remove inherent pub fn render() from all 13 json_view.rs files
- All json_view files now expose rendering exclusively via the Render<T> trait
- Add 'use crate::presentation::cli::views::Render;' import to test modules
- Update test calls to use trait method (.unwrap() / .expect())
- Add hidden '# use Render;' imports to doc examples in all json_view files
- Add 'use crate::presentation::cli::views::Render;' to all 13 handler files
- Pattern 1 handlers (configure, destroy, list, release, run, show, test):
  propagate render error with '?' on JsonView::render call
- Pattern 2 handlers (validate, register, purge, render):
  propagate render error with inner '?' inside progress.result call
- Pattern 3 handlers (create/environment, provision):
  Render import added; call sites unchanged (already use map_err)
- Add OutputFormatting variant + From<ViewRenderError> + help() arm
  to all 11 error types (configure, destroy, list, release, run, show,
  test, register, purge, validate, render)
- Run cargo fmt to fix formatting on all modified files
Both proposals are implemented:
- Proposal #1 (ViewRenderError + Render<T> trait): commit 9179886
- Proposal #2 (remove inherent render() methods, propagate errors): commit 80616e4

Update progress tracking, implementation checklists, timeline, review
criteria, and overall status to reflect completion.
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR completes the refactor plan to standardize CLI view rendering by introducing a shared Render<T> trait and ViewRenderError, removing JSON view fallback rendering, and propagating render errors through controllers.

Changes:

  • Added ViewRenderError + Render<T> trait and re-exported them from the views module.
  • Migrated command JsonView/TextView types to implement Render<T> and updated unit/doc tests accordingly.
  • Updated controllers to call the trait render API for JSON output and propagate formatting errors via new OutputFormatting error variants.

Reviewed changes

Copilot reviewed 53 out of 53 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
src/presentation/cli/views/render.rs Introduces Render<T> and ViewRenderError shared rendering API.
src/presentation/cli/views/mod.rs Exposes render module and re-exports Render / ViewRenderError.
src/presentation/cli/views/commands/validate/views/text_view.rs Implements Render<ValidateDetailsData> for TextView.
src/presentation/cli/views/commands/validate/views/json_view.rs Switches JsonView to Render<ValidateDetailsData> returning Result.
src/presentation/cli/views/commands/test/views/text_view.rs Implements Render<TestResultData> for TextView.
src/presentation/cli/views/commands/test/views/json_view.rs Switches JsonView to Render<TestResultData> returning Result.
src/presentation/cli/views/commands/show/views/text_view.rs Implements Render<EnvironmentInfo> for TextView.
src/presentation/cli/views/commands/show/views/json_view.rs Switches JsonView to Render<EnvironmentInfo> returning Result.
src/presentation/cli/views/commands/run/views/text_view.rs Implements Render<RunDetailsData> for TextView.
src/presentation/cli/views/commands/run/views/json_view.rs Switches JsonView to Render<RunDetailsData> returning Result.
src/presentation/cli/views/commands/render/views/text_view.rs Implements Render<RenderDetailsData> for TextView.
src/presentation/cli/views/commands/render/views/json_view.rs Switches JsonView to Render<RenderDetailsData> returning Result.
src/presentation/cli/views/commands/release/views/text_view.rs Implements Render<ReleaseDetailsData> for TextView.
src/presentation/cli/views/commands/release/views/json_view.rs Switches JsonView to Render<ReleaseDetailsData> returning Result.
src/presentation/cli/views/commands/register/views/text_view.rs Implements Render<RegisterDetailsData> for TextView.
src/presentation/cli/views/commands/register/views/json_view.rs Switches JsonView to Render<RegisterDetailsData> returning Result.
src/presentation/cli/views/commands/purge/views/text_view.rs Implements Render<PurgeDetailsData> for TextView.
src/presentation/cli/views/commands/purge/views/json_view.rs Switches JsonView to Render<PurgeDetailsData> returning Result.
src/presentation/cli/views/commands/provision/views/text_view.rs Implements Render<ProvisionDetailsData> for TextView.
src/presentation/cli/views/commands/provision/views/json_view.rs Switches JsonView to Render<ProvisionDetailsData> returning Result.
src/presentation/cli/views/commands/list/views/text_view.rs Implements Render<EnvironmentList> for TextView.
src/presentation/cli/views/commands/list/views/json_view.rs Switches JsonView to Render<EnvironmentList> returning Result.
src/presentation/cli/views/commands/destroy/views/text_view.rs Implements Render<DestroyDetailsData> for TextView.
src/presentation/cli/views/commands/destroy/views/json_view.rs Switches JsonView to Render<DestroyDetailsData> returning Result.
src/presentation/cli/views/commands/create/views/text_view.rs Implements Render<EnvironmentDetailsData> for TextView.
src/presentation/cli/views/commands/create/views/json_view.rs Switches JsonView to Render<EnvironmentDetailsData> returning Result.
src/presentation/cli/views/commands/configure/views/text_view.rs Implements Render<ConfigureDetailsData> for TextView.
src/presentation/cli/views/commands/configure/views/json_view.rs Switches JsonView to Render<ConfigureDetailsData> returning Result.
src/presentation/cli/controllers/validate/handler.rs Uses Render trait for JSON output and propagates render errors.
src/presentation/cli/controllers/validate/errors.rs Adds OutputFormatting + From<ViewRenderError> conversion and help text.
src/presentation/cli/controllers/test/handler.rs Propagates JSON render errors via ?.
src/presentation/cli/controllers/test/errors.rs Adds OutputFormatting + From<ViewRenderError> conversion and help text.
src/presentation/cli/controllers/show/handler.rs Propagates JSON render errors via ?.
src/presentation/cli/controllers/show/errors.rs Adds OutputFormatting + From<ViewRenderError> conversion and help text.
src/presentation/cli/controllers/run/handler.rs Propagates JSON render errors via ?.
src/presentation/cli/controllers/run/errors.rs Adds OutputFormatting + From<ViewRenderError> conversion and help text.
src/presentation/cli/controllers/render/handler.rs Uses Render trait for JSON output and propagates render errors.
src/presentation/cli/controllers/render/errors.rs Adds OutputFormatting + From<ViewRenderError> conversion and help text.
src/presentation/cli/controllers/release/handler.rs Propagates JSON render errors via ?.
src/presentation/cli/controllers/release/errors.rs Adds OutputFormatting + From<ViewRenderError> conversion and help text.
src/presentation/cli/controllers/register/handler.rs Uses Render trait for JSON output and propagates render errors.
src/presentation/cli/controllers/register/errors.rs Adds OutputFormatting + From<ViewRenderError> conversion and help text.
src/presentation/cli/controllers/purge/handler.rs Uses Render trait for JSON output and propagates render errors.
src/presentation/cli/controllers/purge/errors.rs Adds OutputFormatting + From<ViewRenderError> conversion and help text.
src/presentation/cli/controllers/provision/handler.rs Imports Render for trait-based JSON rendering usage.
src/presentation/cli/controllers/list/handler.rs Propagates JSON render errors via ?.
src/presentation/cli/controllers/list/errors.rs Adds OutputFormatting + From<ViewRenderError> conversion and help text.
src/presentation/cli/controllers/destroy/handler.rs Propagates JSON render errors via ?.
src/presentation/cli/controllers/destroy/errors.rs Adds OutputFormatting + From<ViewRenderError> conversion and help text.
src/presentation/cli/controllers/create/subcommands/environment/handler.rs Imports Render and maps JSON render errors into controller error type.
src/presentation/cli/controllers/configure/handler.rs Updates docs and propagates JSON render errors via ?.
src/presentation/cli/controllers/configure/errors.rs Adds OutputFormatting + From<ViewRenderError> conversion and help text.
docs/refactors/plans/standardize-json-view-render-api.md Marks proposals as completed and records completion metadata.

Comment on lines +43 to +45
/// Implementors transform a DTO (`T`) into a displayable or parseable string.
/// The `Result` return type is required even for infallible renderers (e.g., [`TextView`](super::commands::configure::views::text_view))
/// so that all renderers share a uniform interface and callers can use `?` unconditionally.
Copy link

Copilot AI Feb 28, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The intra-doc link for TextView points to super::commands::configure::views::text_view (the module), not the TextView type. This is likely a broken/misleading rustdoc link; consider linking to super::commands::configure::TextView (re-exported) or ...::text_view::TextView instead.

Copilot uses AI. Check for mistakes.
Comment on lines +23 to +27
//! Text renderers always return `Ok` — they do pure string formatting and never fail.
//! JSON renderers call `serde_json::to_string_pretty` which is infallible on plain
//! `#[derive(Serialize)]` types, but the `Result` return type models the theoretical
//! failure path and is consistent with the `create` and `provision` commands that already
//! return `Result`.
Copy link

Copilot AI Feb 28, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The docs claim serde_json::to_string_pretty is “infallible”/the error path is “unreachable” for plain #[derive(Serialize)] types. That’s not guaranteed (e.g., non‑finite floats, non‑string map keys, or custom Serialize impls can still error), so this wording is misleading. Consider rephrasing to say serialization is expected to succeed for these DTOs but errors are still possible and are propagated via ViewRenderError.

Copilot uses AI. Check for mistakes.
Eliminate the asymmetry between JsonView (trait-only render) and
TextView (inherent + trait delegation) by inlining all inherent
pub fn render() -> String bodies directly into each Render<T> impl.

Changes:
- Remove inherent pub fn render() from all 13 text_view.rs files
- Inline method body into impl Render<T> for TextView, wrapping
  final expression in Ok(...) and early returns in Ok(...)
- list/text_view.rs: keep render_empty/table helpers in inherent impl;
  only the pub fn render method was removed
- show/text_view.rs: trait impl renamed param 'data' -> 'info' to match
  the inlined body's existing references
- Add '?' to all 15 TextView::render() call sites in handler files
- Add From<ViewRenderError> impl to CreateEnvironmentCommandError and
  ProvisionSubcommandError (Pattern 3 handlers not covered by Proposal #2)
- Update doc examples: add hidden '# use Render;' imports + .unwrap()
- Update tests: add .unwrap() to all TextView::render() calls
- Run cargo fmt
Record that commit 1c71cd7 removed inherent pub fn render() from all
13 text_view files, eliminating the JsonView/TextView asymmetry and
applying the 'least surprise' principle consistently.
@josecelano
Copy link
Member Author

ACK 221c998

@josecelano josecelano merged commit 9e17cdc into main Feb 28, 2026
33 checks passed
@josecelano josecelano deleted the refactor/standardize-json-view-render-api branch February 28, 2026 15:10
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants