Skip to main content

torrust_tracker_deployer_lib/domain/environment/state/
common.rs

1//! Common failure context structure
2//!
3//! Provides the `BaseFailureContext` type that contains fields shared across
4//! all command failure contexts (provision, configure, etc.).
5//!
6//! This reduces duplication and provides a consistent structure for:
7//! - Timing information (execution start, failure time, duration)
8//! - Error summary
9//! - Trace identification and file location
10
11use std::path::PathBuf;
12use std::time::Duration;
13
14use chrono::{DateTime, Utc};
15use serde::{Deserialize, Serialize};
16
17use crate::domain::environment::TraceId;
18
19/// Base failure context shared across all command failures
20///
21/// Contains common fields that all failure contexts need:
22/// - Error summary for display
23/// - Timing information (start, fail, duration)
24/// - Trace identification and file path
25///
26/// This is embedded in command-specific failure contexts like
27/// `ProvisionFailureContext` and `ConfigureFailureContext`.
28#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
29pub struct BaseFailureContext {
30    /// Human-readable error summary
31    pub error_summary: String,
32
33    /// When the failure occurred
34    pub failed_at: DateTime<Utc>,
35
36    /// When execution started
37    pub execution_started_at: DateTime<Utc>,
38
39    /// How long execution ran before failing
40    pub execution_duration: Duration,
41
42    /// Unique trace identifier
43    pub trace_id: TraceId,
44
45    /// Path to the detailed trace file (if generated)
46    pub trace_file_path: Option<PathBuf>,
47}
48
49#[cfg(test)]
50mod tests {
51    use super::*;
52
53    #[test]
54    fn it_should_create_base_failure_context() {
55        let now = Utc::now();
56        let trace_id = TraceId::new();
57
58        let context = BaseFailureContext {
59            error_summary: "Test error".to_string(),
60            failed_at: now,
61            execution_started_at: now,
62            execution_duration: Duration::from_secs(10),
63            trace_id: trace_id.clone(),
64            trace_file_path: None,
65        };
66
67        assert_eq!(context.error_summary, "Test error");
68        assert_eq!(context.trace_id, trace_id);
69        assert_eq!(context.trace_file_path, None);
70    }
71
72    #[test]
73    fn it_should_serialize_base_failure_context_to_json() {
74        let now = Utc::now();
75        let trace_id = TraceId::new();
76
77        let context = BaseFailureContext {
78            error_summary: "Test error".to_string(),
79            failed_at: now,
80            execution_started_at: now,
81            execution_duration: Duration::from_secs(10),
82            trace_id,
83            trace_file_path: Some(PathBuf::from("/tmp/trace.log")),
84        };
85
86        let json = serde_json::to_string(&context).unwrap();
87        assert!(json.contains("Test error"));
88        assert!(json.contains("/tmp/trace.log"));
89    }
90
91    #[test]
92    fn it_should_deserialize_base_failure_context_from_json() {
93        let trace_id = TraceId::new();
94        let json = format!(
95            r#"{{
96                "error_summary": "Deserialized error",
97                "failed_at": "2025-10-07T12:00:00Z",
98                "execution_started_at": "2025-10-07T11:59:00Z",
99                "execution_duration": {{"secs": 60, "nanos": 0}},
100                "trace_id": "{trace_id}",
101                "trace_file_path": null
102            }}"#
103        );
104
105        let context: BaseFailureContext = serde_json::from_str(&json).unwrap();
106        assert_eq!(context.error_summary, "Deserialized error");
107        assert_eq!(context.execution_duration, Duration::from_mins(1));
108    }
109
110    #[test]
111    fn it_should_clone_base_failure_context() {
112        let now = Utc::now();
113        let context = BaseFailureContext {
114            error_summary: "Original error".to_string(),
115            failed_at: now,
116            execution_started_at: now,
117            execution_duration: Duration::from_secs(5),
118            trace_id: TraceId::new(),
119            trace_file_path: None,
120        };
121
122        let cloned = context.clone();
123        assert_eq!(context.error_summary, cloned.error_summary);
124        assert_eq!(context.trace_id, cloned.trace_id);
125    }
126}