torrust_tracker_deployer_lib/presentation/cli/views/render.rs
1//! Render trait and error type for command view rendering.
2//!
3//! This module defines the shared interface that all command view structs implement.
4//! Every `JsonView` and `TextView` across all command modules implements [`Render<T>`],
5//! providing compile-time enforcement of a consistent render signature.
6//!
7//! # Design
8//!
9//! The [`Render<T>`] trait is generic over the DTO type `T` so that each view module
10//! can bind it to its own view-data type:
11//!
12//! ```rust,ignore
13//! impl Render<ConfigureDetailsData> for JsonView { ... }
14//! impl Render<ConfigureDetailsData> for TextView { ... }
15//! ```
16//!
17//! Using a dedicated [`ViewRenderError`] type (rather than `serde_json::Error` directly)
18//! decouples the presentation layer from the serialization backend. If the backend ever
19//! changes, only this module and its `From` impls need updating — not every call site.
20//!
21//! # Error handling
22//!
23//! Text renderers always return `Ok` — they do pure string formatting and never fail.
24//! JSON renderers call `serde_json::to_string_pretty`, which is expected to succeed for
25//! the plain `#[derive(Serialize)]` DTOs used in this project, but serialization errors
26//! are still possible (e.g. non-finite floats, non-string map keys, custom `Serialize`
27//! impls) and are propagated as [`ViewRenderError`].
28
29/// Error produced by a [`Render`] implementation.
30///
31/// Using a dedicated error type decouples the presentation layer from the
32/// serialization library. If the backend ever changes, only this type and
33/// its `From` impls need updating — not every call site.
34#[derive(Debug, thiserror::Error)]
35pub enum ViewRenderError {
36 /// JSON serialization failed.
37 #[error("JSON serialization failed: {0}")]
38 Serialization(#[from] serde_json::Error),
39}
40
41/// Trait for rendering command output data into a string.
42///
43/// Implementors transform a DTO (`T`) into a displayable or parseable string.
44/// The `Result` return type is required even for infallible renderers (e.g., [`TextView`](super::commands::configure::views::text_view::TextView))
45/// so that all renderers share a uniform interface and callers can use `?` unconditionally.
46///
47/// # Examples
48///
49/// ```rust,ignore
50/// use crate::presentation::cli::views::{Render, ViewRenderError};
51///
52/// // JSON view — calls serde_json, theoretically fallible
53/// impl Render<ConfigureDetailsData> for JsonView {
54/// fn render(data: &ConfigureDetailsData) -> Result<String, ViewRenderError> {
55/// Ok(serde_json::to_string_pretty(data)?)
56/// }
57/// }
58///
59/// // Text view — pure string formatting, always Ok
60/// impl Render<ConfigureDetailsData> for TextView {
61/// fn render(data: &ConfigureDetailsData) -> Result<String, ViewRenderError> {
62/// Ok(format!("Configuration completed for '{}'", data.environment_name))
63/// }
64/// }
65/// ```
66pub trait Render<T> {
67 /// Render `data` into a string representation.
68 ///
69 /// # Errors
70 ///
71 /// Returns a [`ViewRenderError`] if rendering fails. Text renderers always
72 /// return `Ok`; JSON renderers return `Err` only if serialization fails
73 /// (which is unreachable for plain `#[derive(Serialize)]` types).
74 fn render(data: &T) -> Result<String, ViewRenderError>;
75}