Skip to content

Commit 82b7409

Browse files
committed
refactor: extract shared types into packages/deployer-types/ workspace package
1 parent a0f9d3c commit 82b7409

33 files changed

Lines changed: 3042 additions & 3200 deletions

Cargo.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ members = [
33
"packages/linting",
44
"packages/dependency-installer",
55
"packages/sdk",
6+
"packages/deployer-types",
67
]
78
resolver = "2"
89

@@ -60,13 +61,13 @@ tera = "1.0"
6061
testcontainers = { version = "0.26", features = [ "blocking" ] }
6162
thiserror = "2.0"
6263
torrust-dependency-installer = { path = "packages/dependency-installer" }
64+
torrust-deployer-types = { path = "packages/deployer-types" }
6365
torrust-linting = { path = "packages/linting" }
6466
tracing = { version = "0.1", features = [ "attributes" ] }
6567
tracing-appender = "0.2"
6668
tracing-subscriber = { version = "0.3", features = [ "env-filter", "json", "fmt" ] }
6769
url = { version = "2.0", features = [ "serde" ] }
6870
uuid = { version = "1.0", features = [ "v4", "serde" ] }
69-
email_address = "0.2.9"
7071

7172
[dev-dependencies]
7273
regex = "1.0"

docs/codebase-architecture.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -219,6 +219,7 @@ Independently versioned Cargo workspace packages in `packages/`:
219219

220220
-`packages/linting/` - Unified linting framework (runs markdownlint, yamllint, taplo, cspell, clippy, rustfmt, shellcheck)
221221
-`packages/dependency-installer/` - Dependency detection and installation for development setup (OpenTofu, Ansible, LXD, cargo-machete)
222+
-`packages/deployer-types/` - Shared value objects and traits (`torrust-deployer-types`) — cross-cutting foundational types (e.g., `EnvironmentName`, `DomainName`, `Username`, `Clock`, `ErrorKind`) shared by the root crate and SDK
222223
-`packages/sdk/` - Programmatic SDK (`torrust-tracker-deployer-sdk`) — independently consumable Rust crate for deploying Torrust Tracker instances without the CLI
223224

224225
### Presentation Layer

docs/refactors/active-refactorings.md

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
| Document | Status | Issue | Target | Created |
44
| ----------------------------------------------------------------------------------------------------- | ----------- | ---------------------------------------------------------------------- | ----------------------------------------- | ---------- |
55
| [E2E Test Isolation - Complete Log Directory Support](plans/e2e-test-isolation-log-dir.md) | 📋 Planning | [#365](https://github.com/torrust/torrust-tracker-deployer/issues/365) | Add log_dir to all E2E tests | 2026-02-18 |
6-
| [Extract Shared Types Package](plans/extract-shared-types-package.md) | 📋 Planning | TBD | Shared value objects for SDK consumers | 2026-02-24 |
76
| [SDK DDD Layer Boundary Fixes](plans/sdk-ddd-layer-boundary-fixes.md) | 📋 Planning | TBD | Remove DDD violations from SDK surface | 2026-02-24 |
87
| [Separate View Data from Views](plans/separate-view-data-from-views.md) | 📋 Planning | TBD | Organize presentation layer command views | 2026-02-16 |
98
| [Simplify Controller Command Handler Creation](plans/simplify-controller-command-handler-creation.md) | 📋 Planning | TBD | Remove unnecessary progress steps | 2025-12-17 |

docs/refactors/completed-refactorings.md

Lines changed: 30 additions & 29 deletions
Large diffs are not rendered by default.

docs/refactors/plans/extract-shared-types-package.md

Lines changed: 0 additions & 336 deletions
This file was deleted.

packages/README.md

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,27 @@ This directory contains reusable Rust workspace packages that support the Torrus
4646

4747
**Documentation**: See [packages/linting/README.md](./linting/README.md)
4848

49+
### [`deployer-types/`](./deployer-types/)
50+
51+
**Purpose**: Shared value objects and traits for the Torrust Tracker Deployer ecosystem
52+
53+
**Key Features**:
54+
55+
- Validated value objects: `DomainName`, `Email`, `Username`, `EnvironmentName`, `ServiceEndpoint`
56+
- Secret wrappers: `ApiToken` / `PlainApiToken`, `Password` / `PlainPassword` (via `secrecy`)
57+
- Time abstraction: `Clock` trait + `SystemClock` implementation for testability
58+
- Error infrastructure: `ErrorKind` enum + `Traceable` trait
59+
- Minimal dependencies (no tokio, no infrastructure)
60+
- Both the root crate and the SDK crate depend on this package
61+
62+
**Use Cases**:
63+
64+
- Canonical source of cross-cutting value objects shared across workspace packages
65+
- Enables SDK consumers to import foundational types without depending on the full root crate
66+
- Supports independent versioning and future publishing
67+
68+
**Documentation**: See [packages/deployer-types/README.md](./deployer-types/README.md)
69+
4970
### [`sdk/`](./sdk/)
5071

5172
**Purpose**: Programmatic Rust SDK for deploying and managing Torrust Tracker instances

packages/deployer-types/Cargo.toml

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
[package]
2+
name = "torrust-deployer-types"
3+
version = "0.1.0"
4+
edition = "2021"
5+
description = "Shared value objects and traits for the Torrust Tracker Deployer"
6+
license = "MIT"
7+
8+
[dependencies]
9+
chrono = { version = "0.4", features = [ "serde" ] }
10+
email_address = "0.2.9"
11+
schemars = "1.1"
12+
secrecy = { version = "0.10", features = [ "serde" ] }
13+
serde = { version = "1.0", features = [ "derive" ] }
14+
thiserror = "2.0"
15+
tracing = "0.1"
16+
url = { version = "2.0", features = [ "serde" ] }
17+
18+
[dev-dependencies]
19+
serde_json = "1.0"

packages/deployer-types/README.md

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
# Torrust Deployer Types
2+
3+
Shared value objects and traits for the Torrust Tracker Deployer ecosystem.
4+
5+
## Overview
6+
7+
This package provides foundational types shared between:
8+
9+
- `torrust-tracker-deployer` (root crate)
10+
- `torrust-tracker-deployer-sdk` (SDK package)
11+
12+
These are validated value objects and cross-cutting traits with no business logic
13+
and minimal external dependencies.
14+
15+
## Types
16+
17+
| Type | Description |
18+
| ---------------------------- | ------------------------------------------- |
19+
| `Clock` / `SystemClock` | Time abstraction for testability |
20+
| `DomainName` | Validated DNS-like domain name |
21+
| `Email` | Validated email address |
22+
| `EnvironmentName` | Validated environment identifier |
23+
| `Username` | Validated username string |
24+
| `ServiceEndpoint` | Validated URL + port combination |
25+
| `ApiToken` / `PlainApiToken` | Secret API token wrapper |
26+
| `Password` / `PlainPassword` | Secret password wrapper |
27+
| `ErrorKind` | High-level error categorization enum |
28+
| `Traceable` | Trait for structured error trace generation |
29+
30+
## Usage
31+
32+
```toml
33+
[dependencies]
34+
torrust-deployer-types = { path = "packages/deployer-types" }
35+
```
36+
37+
```rust
38+
use torrust_deployer_types::{EnvironmentName, DomainName, Clock};
39+
```
40+
41+
## Architecture
42+
43+
```text
44+
torrust-deployer-types ← this package (no internal deps)
45+
↑ ↑
46+
torrust-tracker-deployer torrust-tracker-deployer-sdk
47+
```
48+
49+
Both the root crate and the SDK package depend on this package for shared types.
50+
51+
## License
52+
53+
MIT
Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
//! Clock abstraction for testable time management
2+
//!
3+
//! This module provides a clock abstraction that allows controlling time
4+
//! in tests. Time is treated as an infrastructure concern, similar to
5+
//! database or filesystem access.
6+
//!
7+
//! # Design Philosophy
8+
//!
9+
//! Direct use of `Utc::now()` throughout the codebase makes tests
10+
//! non-deterministic and harder to maintain. By abstracting time behind
11+
//! a trait, we can:
12+
//!
13+
//! - Control time in tests (set specific timestamps)
14+
//! - Make tests deterministic and reproducible
15+
//! - Test time-dependent behavior (timeouts, retries, etc.)
16+
//! - Mock time progression without actual delays
17+
//!
18+
//! # Usage
19+
//!
20+
//! ## In Production Code
21+
//!
22+
//! ```rust
23+
//! use torrust_deployer_types::Clock;
24+
//!
25+
//! fn record_event(clock: &dyn Clock) {
26+
//! let timestamp = clock.now();
27+
//! println!("Event occurred at: {}", timestamp);
28+
//! }
29+
//! ```
30+
//!
31+
//! ## In Tests
32+
//!
33+
//! ```rust,no_run
34+
//! // Note: MockClock is only available in the root crate's testing module
35+
//! # #[cfg(test)]
36+
//! # {
37+
//! use torrust_tracker_deployer_lib::testing::MockClock;
38+
//! use chrono::{DateTime, TimeZone, Utc};
39+
//!
40+
//! let fixed_time = Utc.with_ymd_and_hms(2025, 10, 7, 12, 0, 0).unwrap();
41+
//! let clock = MockClock::new(fixed_time);
42+
//!
43+
//! // Time is now fixed at the specified timestamp
44+
//! assert_eq!(clock.now(), fixed_time);
45+
//!
46+
//! // Advance time by 5 seconds
47+
//! clock.advance_secs(5);
48+
//! assert_eq!(
49+
//! clock.now(),
50+
//! Utc.with_ymd_and_hms(2025, 10, 7, 12, 0, 5).unwrap()
51+
//! );
52+
//! # }
53+
//! ```
54+
55+
use chrono::{DateTime, Utc};
56+
57+
/// Clock trait for obtaining the current time
58+
///
59+
/// This trait abstracts time acquisition, making it mockable in tests.
60+
/// All time-dependent code should use this trait instead of calling
61+
/// `Utc::now()` directly.
62+
pub trait Clock: Send + Sync {
63+
/// Returns the current time in UTC
64+
fn now(&self) -> DateTime<Utc>;
65+
}
66+
67+
/// System clock implementation using real system time
68+
///
69+
/// This is the production implementation that uses `Utc::now()`
70+
/// to get the actual current time.
71+
///
72+
/// # Example
73+
///
74+
/// ```rust
75+
/// use torrust_deployer_types::{Clock, SystemClock};
76+
///
77+
/// let clock = SystemClock;
78+
/// let now = clock.now();
79+
/// println!("Current time: {}", now);
80+
/// ```
81+
#[derive(Debug, Clone, Copy, Default)]
82+
pub struct SystemClock;
83+
84+
impl Clock for SystemClock {
85+
fn now(&self) -> DateTime<Utc> {
86+
Utc::now()
87+
}
88+
}
89+
90+
#[cfg(test)]
91+
mod tests {
92+
use super::*;
93+
94+
#[test]
95+
fn it_should_return_current_system_time() {
96+
let clock = SystemClock;
97+
let before = Utc::now();
98+
let now = clock.now();
99+
let after = Utc::now();
100+
101+
// Verify the returned time is between before and after
102+
assert!(now >= before);
103+
assert!(now <= after);
104+
}
105+
106+
#[test]
107+
fn it_should_return_different_times_on_subsequent_calls() {
108+
let clock = SystemClock;
109+
let first = clock.now();
110+
111+
// Small delay to ensure different timestamp
112+
std::thread::sleep(std::time::Duration::from_millis(10));
113+
114+
let second = clock.now();
115+
assert!(second > first);
116+
}
117+
}

0 commit comments

Comments
 (0)