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}