Skip to main content

torrust_tracker_deployer_lib/presentation/cli/views/
traits.rs

1//! Core traits for output message handling
2//!
3//! This module defines the traits that enable extensibility and abstraction
4//! in the user output system.
5
6use super::{Channel, Theme, VerbosityLevel};
7
8/// Trait for output messages that can be written to user-facing channels
9///
10/// This trait enables extensibility following the Open/Closed Principle.
11/// Each message type encapsulates its own:
12/// - Formatting logic (how it appears to users)
13/// - Verbosity requirements (when it should be shown)
14/// - Channel routing (stdout vs stderr)
15///
16/// # Design Philosophy
17///
18/// By implementing this trait, message types become self-contained and can be
19/// added without modifying the `UserOutput` struct. This makes the system
20/// extensible - new message types can be defined in external modules.
21///
22/// # Examples
23///
24/// ```rust,ignore
25/// use torrust_tracker_deployer_lib::presentation::cli::views::{OutputMessage, Theme, VerbosityLevel, Channel};
26///
27/// struct CustomMessage {
28///     text: String,
29/// }
30///
31/// impl OutputMessage for CustomMessage {
32///     fn format(&self, theme: &Theme) -> String {
33///         format!("🎉 {}", self.text)
34///     }
35///
36///     fn required_verbosity(&self) -> VerbosityLevel {
37///         VerbosityLevel::Normal
38///     }
39///
40///     fn channel(&self) -> Channel {
41///         Channel::Stderr
42///     }
43///
44///     fn type_name(&self) -> &'static str {
45///         "CustomMessage"
46///     }
47/// }
48/// ```
49pub trait OutputMessage {
50    /// Format this message using the given theme
51    ///
52    /// This method defines how the message appears to users. It should
53    /// incorporate theme symbols and any necessary formatting.
54    ///
55    /// # Arguments
56    ///
57    /// * `theme` - The theme providing symbols for formatting
58    ///
59    /// # Returns
60    ///
61    /// A formatted string ready for display to users
62    fn format(&self, theme: &Theme) -> String;
63
64    /// Get the minimum verbosity level required to show this message
65    ///
66    /// Messages are only displayed if the current verbosity level is
67    /// greater than or equal to the required level.
68    ///
69    /// # Returns
70    ///
71    /// The minimum verbosity level needed to display this message
72    fn required_verbosity(&self) -> VerbosityLevel;
73
74    /// Get the output channel for this message
75    ///
76    /// Determines whether the message goes to stdout or stderr following
77    /// Unix conventions.
78    ///
79    /// # Returns
80    ///
81    /// The channel (Stdout or Stderr) where this message should be written
82    fn channel(&self) -> Channel;
83
84    /// Get the type name of this message
85    ///
86    /// Returns a human-readable type identifier for this message type.
87    /// This is primarily used by formatter overrides (e.g., JSON formatter)
88    /// to include type information in the output.
89    ///
90    /// # Returns
91    ///
92    /// A static string representing the message type name
93    fn type_name(&self) -> &'static str;
94}
95
96/// Optional trait for post-processing message output
97///
98/// This allows transforming the standard message format without
99/// modifying individual message types. Use sparingly - prefer
100/// extending the message trait or using themes for most cases.
101///
102/// # When to Use
103///
104/// - **Machine-readable formats**: JSON, XML, structured logs
105/// - **Additional decoration**: ANSI colors, markup codes
106/// - **Output wrapping**: Adding metadata, timestamps, process info
107///
108/// # When NOT to Use
109///
110/// - **Symbol changes**: Use `Theme` instead
111/// - **New message types**: Implement `OutputMessage` trait instead
112/// - **Channel routing changes**: Define in message type's `channel()` method
113///
114/// # Examples
115///
116/// ```rust,ignore
117/// use torrust_tracker_deployer_lib::presentation::cli::views::{FormatterOverride, OutputMessage};
118///
119/// struct JsonFormatter;
120///
121/// impl FormatterOverride for JsonFormatter {
122///     fn transform(&self, formatted: &str, message: &dyn OutputMessage) -> String {
123///         // Transform to JSON representation
124///         format!(r#"{{"content": "{}"}}"#, formatted.trim())
125///     }
126/// }
127/// ```
128pub trait FormatterOverride: Send + Sync {
129    /// Transform formatted message output
130    ///
131    /// This method receives the already-formatted message (with theme applied)
132    /// and the original message object for context. It should return the
133    /// transformed output.
134    ///
135    /// # Arguments
136    ///
137    /// * `formatted` - The message already formatted with theme
138    /// * `message` - The original message object (for metadata/context)
139    ///
140    /// # Returns
141    ///
142    /// The transformed message string
143    fn transform(&self, formatted: &str, message: &dyn OutputMessage) -> String;
144}
145
146/// Trait for output destinations
147///
148/// An output sink receives formatted messages and writes them to a destination.
149/// Sinks handle the mechanics of where output goes, not how it's formatted.
150///
151/// # Design Philosophy
152///
153/// Sinks receive already-formatted messages (with theme applied) and route them
154/// to appropriate destinations. They don't handle formatting or verbosity filtering -
155/// those concerns are handled by message types and filters respectively.
156///
157/// # Examples
158///
159/// ```rust,ignore
160/// use torrust_tracker_deployer_lib::presentation::cli::views::{OutputSink, OutputMessage};
161/// use std::fs::File;
162///
163/// struct FileSink {
164///     file: File,
165/// }
166///
167/// impl OutputSink for FileSink {
168///     fn write_message(&mut self, message: &dyn OutputMessage, formatted: &str) {
169///         use std::io::Write;
170///         writeln!(self.file, "{}", formatted).ok();
171///     }
172/// }
173/// ```
174pub trait OutputSink: Send + Sync {
175    /// Write a formatted message to this sink
176    ///
177    /// # Arguments
178    ///
179    /// * `message` - The message object (for metadata like channel)
180    /// * `formatted` - The already-formatted message text
181    fn write_message(&mut self, message: &dyn OutputMessage, formatted: &str);
182}