Skip to content

Commit b07f889

Browse files
committed
docs: add issue specification for proposal 6 - type-safe channel routing
- Create task issue #135 for Proposal 6 - Add detailed specification document - Update epic issue #102 with new subissue - Add 'newtypes' to project dictionary
1 parent 08bbc8d commit b07f889

File tree

3 files changed

+312
-10
lines changed

3 files changed

+312
-10
lines changed

docs/issues/102-epic-user-output-architecture-improvements.md

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,11 @@ See comprehensive refactoring plan: [docs/refactors/plans/user-output-architectu
1515

1616
## Goals
1717

18-
- [ ] **Separate Concerns**: Extract verbosity filtering, theme configuration, and formatting logic
19-
- [ ] **Simplify Testing**: Improve test infrastructure and reduce duplication
20-
- [ ] **Enable Extensibility**: Support different output styles (emoji, plain text, JSON) and destinations
21-
- [ ] **Improve Maintainability**: Reduce code duplication and establish clear abstractions
22-
- [ ] **Maintain Quality**: All refactorings must pass pre-commit checks and maintain test coverage
18+
- [x] **Separate Concerns**: Extract verbosity filtering, theme configuration, and formatting logic
19+
- [x] **Simplify Testing**: Improve test infrastructure and reduce duplication
20+
- [x] **Enable Extensibility**: Support different output styles (emoji, plain text, JSON) and destinations
21+
- [x] **Improve Maintainability**: Reduce code duplication and establish clear abstractions
22+
- [x] **Maintain Quality**: All refactorings must pass pre-commit checks and maintain test coverage
2323

2424
## Proposals Summary
2525

@@ -57,17 +57,18 @@ See comprehensive refactoring plan: [docs/refactors/plans/user-output-architectu
5757
### Phase 0: Quick Wins
5858

5959
- [x] [#103](https://github.com/torrust/torrust-tracker-deployer/issues/103) - Proposal 0: Extract Verbosity Filtering Logic
60-
- [ ] [#123](https://github.com/torrust/torrust-tracker-deployer/issues/123) - Proposal 1: Simplify Test Infrastructure
61-
- [ ] [#124](https://github.com/torrust/torrust-tracker-deployer/issues/124) - Proposal 2: Add Theme/Configuration Support
60+
- [x] [#123](https://github.com/torrust/torrust-tracker-deployer/issues/123) - Proposal 1: Simplify Test Infrastructure
61+
- [x] [#124](https://github.com/torrust/torrust-tracker-deployer/issues/124) - Proposal 2: Add Theme/Configuration Support
6262

6363
### Phase 1: Strategic Improvements
6464

65-
- [ ] [#127](https://github.com/torrust/torrust-tracker-deployer/issues/127) - Proposal 3: Use Message Trait for Extensibility
66-
- [ ] [#128](https://github.com/torrust/torrust-tracker-deployer/issues/128) - Proposal 5: Parameterized Test Cases
65+
- [x] [#127](https://github.com/torrust/torrust-tracker-deployer/issues/127) - Proposal 3: Use Message Trait for Extensibility
66+
- [x] [#128](https://github.com/torrust/torrust-tracker-deployer/issues/128) - Proposal 5: Parameterized Test Cases
6767

6868
### Phase 2: Polish & Extensions
6969

70-
- [ ] [#133](https://github.com/torrust/torrust-tracker-deployer/issues/133) - Proposal 4: Add Formatter Override Support
70+
- [x] [#133](https://github.com/torrust/torrust-tracker-deployer/issues/133) - Proposal 4: Add Formatter Override Support
71+
- [ ] [#135](https://github.com/torrust/torrust-tracker-deployer/issues/135) - Proposal 6: Type-Safe Channel Routing
7172

7273
### Other Related Work
7374

Lines changed: 300 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,300 @@
1+
# Type-Safe Channel Routing for User Output
2+
3+
**Issue**: [#135](https://github.com/torrust/torrust-tracker-deployer/issues/135)
4+
**Parent Epic**: [#102](https://github.com/torrust/torrust-tracker-deployer/issues/102) - User Output Architecture Improvements
5+
**Related**: [Refactoring Plan - Proposal 6](../refactors/plans/user-output-architecture-improvements.md#proposal-6-type-safe-channel-routing)
6+
7+
## Overview
8+
9+
Add compile-time type safety for channel routing (stdout vs stderr) in the `UserOutput` module by introducing newtype wrappers for writers. Currently, channel routing is done with runtime pattern matching which, while functional, doesn't provide compile-time guarantees that messages go to the correct channel.
10+
11+
This refactoring introduces `StdoutWriter` and `StderrWriter` newtype wrappers that make channel routing explicit in the type system, preventing accidental channel confusion at compile time.
12+
13+
## Goals
14+
15+
- [ ] Add type-safe newtype wrappers for stdout and stderr writers
16+
- [ ] Replace runtime channel matching with compile-time type safety
17+
- [ ] Make channel routing explicit and self-documenting in code
18+
- [ ] Maintain existing functionality and API compatibility
19+
- [ ] Improve IDE support and code navigation for channel-specific operations
20+
21+
## 🏗️ Architecture Requirements
22+
23+
**DDD Layer**: Presentation
24+
**Module Path**: `src/presentation/user_output.rs`
25+
**Pattern**: Type-safe wrappers using newtype pattern
26+
27+
### Module Structure Requirements
28+
29+
- [ ] Follow module organization conventions (see [docs/contributing/module-organization.md](../contributing/module-organization.md))
30+
- [ ] Keep writer wrappers private as implementation details
31+
- [ ] Maintain public API compatibility with existing code
32+
33+
### Architectural Constraints
34+
35+
- [ ] Zero-cost abstraction using newtype pattern
36+
- [ ] No runtime overhead compared to current implementation
37+
- [ ] Preserve existing error handling behavior
38+
- [ ] Error handling follows project conventions (see [docs/contributing/error-handling.md](../contributing/error-handling.md))
39+
40+
### Anti-Patterns to Avoid
41+
42+
- ❌ Exposing writer wrappers in public API unnecessarily
43+
- ❌ Adding runtime checks when compile-time safety is available
44+
- ❌ Breaking existing test infrastructure
45+
46+
## Specifications
47+
48+
### Newtype Wrappers for Stdout and Stderr
49+
50+
Create type-safe wrappers using the newtype pattern:
51+
52+
```rust
53+
/// Stdout writer wrapper for type safety
54+
///
55+
/// This newtype wrapper ensures that stdout-specific operations
56+
/// can only be performed on stdout writers, preventing accidental
57+
/// channel confusion at compile time.
58+
struct StdoutWriter(Box<dyn Write + Send + Sync>);
59+
60+
impl StdoutWriter {
61+
/// Create a new stdout writer wrapper
62+
fn new(writer: Box<dyn Write + Send + Sync>) -> Self {
63+
Self(writer)
64+
}
65+
66+
/// Write a line to stdout
67+
///
68+
/// Writes the given message followed by a newline to the stdout channel.
69+
/// Errors are silently ignored as output operations are best-effort.
70+
fn write_line(&mut self, message: &str) {
71+
writeln!(self.0, "{message}").ok();
72+
}
73+
}
74+
75+
/// Stderr writer wrapper for type safety
76+
///
77+
/// This newtype wrapper ensures that stderr-specific operations
78+
/// can only be performed on stderr writers, preventing accidental
79+
/// channel confusion at compile time.
80+
struct StderrWriter(Box<dyn Write + Send + Sync>);
81+
82+
impl StderrWriter {
83+
/// Create a new stderr writer wrapper
84+
fn new(writer: Box<dyn Write + Send + Sync>) -> Self {
85+
Self(writer)
86+
}
87+
88+
/// Write a line to stderr
89+
///
90+
/// Writes the given message followed by a newline to the stderr channel.
91+
/// Errors are silently ignored as output operations are best-effort.
92+
fn write_line(&mut self, message: &str) {
93+
writeln!(self.0, "{message}").ok();
94+
}
95+
}
96+
```
97+
98+
### Updated UserOutput Structure
99+
100+
Replace raw `Box<dyn Write>` fields with typed wrappers:
101+
102+
```rust
103+
pub struct UserOutput {
104+
theme: Theme,
105+
verbosity_filter: VerbosityFilter,
106+
stdout: StdoutWriter,
107+
stderr: StderrWriter,
108+
formatter_override: Option<Box<dyn FormatterOverride>>,
109+
}
110+
```
111+
112+
### Type-Safe Writer Access
113+
114+
Add private helper methods that leverage type safety:
115+
116+
```rust
117+
impl UserOutput {
118+
/// Write a message to stdout using the typed writer
119+
fn write_to_stdout(&mut self, formatted: &str) {
120+
self.stdout.write_line(formatted);
121+
}
122+
123+
/// Write a message to stderr using the typed writer
124+
fn write_to_stderr(&mut self, formatted: &str) {
125+
self.stderr.write_line(formatted);
126+
}
127+
}
128+
```
129+
130+
### Updated Message Writing Logic
131+
132+
Replace runtime channel matching with compile-time dispatch:
133+
134+
```rust
135+
impl UserOutput {
136+
/// Write a message to the appropriate channel
137+
pub fn write(&mut self, message: &dyn OutputMessage) {
138+
if !self.verbosity_filter.should_show(message.required_verbosity()) {
139+
return;
140+
}
141+
142+
let formatted = if let Some(ref formatter) = self.formatter_override {
143+
formatter.transform(&message.format(&self.theme), message)
144+
} else {
145+
message.format(&self.theme)
146+
};
147+
148+
// Type-safe dispatch based on channel
149+
match message.channel() {
150+
Channel::Stdout => self.write_to_stdout(&formatted),
151+
Channel::Stderr => self.write_to_stderr(&formatted),
152+
}
153+
}
154+
}
155+
```
156+
157+
### Constructor Updates
158+
159+
Update all constructors to use typed wrappers:
160+
161+
```rust
162+
impl UserOutput {
163+
pub fn with_theme_and_writers(
164+
verbosity: VerbosityLevel,
165+
theme: Theme,
166+
stdout_writer: Box<dyn Write + Send + Sync>,
167+
stderr_writer: Box<dyn Write + Send + Sync>,
168+
) -> Self {
169+
Self {
170+
theme,
171+
verbosity_filter: VerbosityFilter::new(verbosity),
172+
stdout: StdoutWriter::new(stdout_writer),
173+
stderr: StderrWriter::new(stderr_writer),
174+
formatter_override: None,
175+
}
176+
}
177+
178+
pub fn with_theme_writers_and_formatter(
179+
verbosity: VerbosityLevel,
180+
theme: Theme,
181+
stdout_writer: Box<dyn Write + Send + Sync>,
182+
stderr_writer: Box<dyn Write + Send + Sync>,
183+
formatter_override: Option<Box<dyn FormatterOverride>>,
184+
) -> Self {
185+
Self {
186+
theme,
187+
verbosity_filter: VerbosityFilter::new(verbosity),
188+
stdout: StdoutWriter::new(stdout_writer),
189+
stderr: StderrWriter::new(stderr_writer),
190+
formatter_override,
191+
}
192+
}
193+
}
194+
```
195+
196+
## Implementation Plan
197+
198+
### Phase 1: Create Newtype Wrappers (30 minutes)
199+
200+
- [ ] Task 1.1: Create `StdoutWriter` newtype struct with documentation
201+
- [ ] Task 1.2: Create `StderrWriter` newtype struct with documentation
202+
- [ ] Task 1.3: Implement `new()` constructor for both wrappers
203+
- [ ] Task 1.4: Implement `write_line()` method for both wrappers
204+
- [ ] Task 1.5: Add unit tests for wrapper creation and writing
205+
206+
### Phase 2: Update UserOutput Structure (45 minutes)
207+
208+
- [ ] Task 2.1: Update `UserOutput` struct fields to use typed wrappers
209+
- [ ] Task 2.2: Add private helper methods `write_to_stdout()` and `write_to_stderr()`
210+
- [ ] Task 2.3: Update all constructors to wrap writers in typed newtype
211+
- [ ] Task 2.4: Update `write()` method to use typed writer helpers
212+
- [ ] Task 2.5: Verify all existing public methods compile and work correctly
213+
214+
### Phase 3: Update Tests (30 minutes)
215+
216+
- [ ] Task 3.1: Update test infrastructure to work with newtype wrappers
217+
- [ ] Task 3.2: Add tests for type-safe channel routing
218+
- [ ] Task 3.3: Verify all existing tests pass without modification
219+
- [ ] Task 3.4: Add test cases demonstrating compile-time safety benefits
220+
221+
### Phase 4: Documentation and Quality (30 minutes)
222+
223+
- [ ] Task 4.1: Update module documentation to mention type-safe routing
224+
- [ ] Task 4.2: Add code examples showing type safety benefits
225+
- [ ] Task 4.3: Run pre-commit checks and fix any issues
226+
- [ ] Task 4.4: Verify documentation builds correctly
227+
228+
## Acceptance Criteria
229+
230+
> **Note for Contributors**: These criteria define what the PR reviewer will check. Use this as your pre-review checklist before submitting the PR to minimize back-and-forth iterations.
231+
232+
**Quality Checks**:
233+
234+
- [ ] Pre-commit checks pass: `./scripts/pre-commit.sh`
235+
- [ ] All tests pass: `cargo test`
236+
- [ ] Documentation builds: `cargo doc --no-deps`
237+
238+
**Task-Specific Criteria**:
239+
240+
- [ ] `StdoutWriter` and `StderrWriter` newtype wrappers are implemented
241+
- [ ] Both wrappers have `new()` and `write_line()` methods
242+
- [ ] `UserOutput` struct uses typed wrappers instead of raw `Box<dyn Write>`
243+
- [ ] All constructors wrap raw writers in typed newtypes
244+
- [ ] Private helper methods `write_to_stdout()` and `write_to_stderr()` exist
245+
- [ ] The `write()` method uses typed helpers instead of direct writer access
246+
- [ ] All existing tests pass without modification
247+
- [ ] New tests demonstrate compile-time safety benefits
248+
- [ ] Module documentation is updated to reflect type-safe routing
249+
- [ ] Code examples show the benefits of the newtype pattern
250+
- [ ] No performance regression (zero-cost abstraction)
251+
- [ ] IDE autocomplete shows channel-specific methods
252+
253+
## Benefits
254+
255+
**Compile-Time Safety**: Type system prevents accidental channel confusion
256+
**Self-Documenting Code**: Method names explicitly show which channel is used
257+
**Better IDE Support**: Autocomplete and navigation work better with explicit types
258+
**Prevents Channel Swaps**: Impossible to accidentally write to wrong channel
259+
**Zero-Cost Abstraction**: No runtime overhead with newtype pattern
260+
**Improved Maintainability**: Type errors caught at compile time instead of runtime
261+
262+
## Related Documentation
263+
264+
- [Module Organization](../contributing/module-organization.md) - Code organization conventions
265+
- [Error Handling Guide](../contributing/error-handling.md) - Error handling patterns
266+
- [User Output Architecture Improvements Plan](../refactors/plans/user-output-architecture-improvements.md) - Complete refactoring plan
267+
- [Development Principles](../development-principles.md) - Core development principles
268+
269+
## Notes
270+
271+
### Why Newtype Pattern?
272+
273+
The newtype pattern is a zero-cost abstraction in Rust - the wrapper types have the same memory layout and performance characteristics as the wrapped type, but provide compile-time type safety.
274+
275+
### Design Decision: Private Wrappers
276+
277+
The newtype wrappers (`StdoutWriter` and `StderrWriter`) are intentionally kept private as implementation details. The public API continues to accept `Box<dyn Write + Send + Sync>` for maximum flexibility, and the wrapping happens internally.
278+
279+
This allows:
280+
281+
- Existing test code to continue working without modification
282+
- Users to inject any writer implementation without knowing about the wrappers
283+
- Internal refactoring without breaking the public API
284+
285+
### Compatibility
286+
287+
This refactoring maintains full backward compatibility:
288+
289+
- All public methods have the same signatures
290+
- All existing tests continue to pass
291+
- No changes required in calling code
292+
- The type safety improvements are internal implementation details
293+
294+
### Future Extensions
295+
296+
This type-safe foundation makes future improvements easier:
297+
298+
- Adding buffering control becomes trivial with typed wrappers
299+
- Per-channel configuration (colors, formatting) is straightforward
300+
- Mock writers for testing are easier to implement and use

project-words.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,7 @@ nameof
9999
nanos
100100
newgrp
101101
newtype
102+
newtypes
102103
nistp
103104
nocapture
104105
noconfirm

0 commit comments

Comments
 (0)