torrust_tracker_deployer_lib/application/
errors.rs1use std::fmt;
21
22use thiserror::Error;
23
24#[derive(Debug, Error)]
43pub enum PersistenceError {
44 #[error("Environment not found")]
46 NotFound,
47
48 #[error("Conflict: another process is accessing this environment")]
50 Conflict,
51
52 #[error("Internal error: {0}")]
54 Internal(#[source] anyhow::Error),
55}
56
57impl From<crate::domain::environment::repository::RepositoryError> for PersistenceError {
58 fn from(e: crate::domain::environment::repository::RepositoryError) -> Self {
59 use crate::domain::environment::repository::RepositoryError;
60 match e {
61 RepositoryError::NotFound => Self::NotFound,
62 RepositoryError::Conflict => Self::Conflict,
63 RepositoryError::Internal(inner) => Self::Internal(inner),
64 }
65 }
66}
67
68#[derive(Debug, Error)]
85#[error("Expected state '{expected}', but found '{actual}'")]
86pub struct InvalidStateError {
87 pub expected: String,
89 pub actual: String,
91}
92
93impl From<crate::domain::environment::state::StateTypeError> for InvalidStateError {
94 fn from(e: crate::domain::environment::state::StateTypeError) -> Self {
95 use crate::domain::environment::state::StateTypeError;
96 match e {
97 StateTypeError::UnexpectedState { expected, actual } => Self {
98 expected: expected.to_string(),
99 actual,
100 },
101 }
102 }
103}
104
105#[derive(Debug, Clone, Copy, PartialEq, Eq)]
121pub enum ReleaseWorkflowStep {
122 CreateTrackerStorage,
124 InitTrackerDatabase,
126 RenderTrackerTemplates,
128 DeployTrackerConfigToRemote,
130 CreatePrometheusStorage,
132 RenderPrometheusTemplates,
134 DeployPrometheusConfigToRemote,
136 CreateGrafanaStorage,
138 RenderGrafanaTemplates,
140 DeployGrafanaProvisioning,
142 CreateMysqlStorage,
144 RenderBackupTemplates,
146 CreateBackupStorage,
148 DeployBackupConfigToRemote,
150 InstallBackupCrontab,
152 RenderCaddyTemplates,
154 DeployCaddyConfigToRemote,
156 RenderDockerComposeTemplates,
158 DeployComposeFilesToRemote,
160}
161
162impl fmt::Display for ReleaseWorkflowStep {
163 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
164 let name = match self {
165 Self::CreateTrackerStorage => "Create Tracker Storage",
166 Self::InitTrackerDatabase => "Initialize Tracker Database",
167 Self::RenderTrackerTemplates => "Render Tracker Templates",
168 Self::DeployTrackerConfigToRemote => "Deploy Tracker Config to Remote",
169 Self::CreatePrometheusStorage => "Create Prometheus Storage",
170 Self::RenderPrometheusTemplates => "Render Prometheus Templates",
171 Self::DeployPrometheusConfigToRemote => "Deploy Prometheus Config to Remote",
172 Self::CreateGrafanaStorage => "Create Grafana Storage",
173 Self::RenderGrafanaTemplates => "Render Grafana Templates",
174 Self::DeployGrafanaProvisioning => "Deploy Grafana Provisioning",
175 Self::CreateMysqlStorage => "Create MySQL Storage",
176 Self::RenderBackupTemplates => "Render Backup Templates",
177 Self::CreateBackupStorage => "Create Backup Storage",
178 Self::DeployBackupConfigToRemote => "Deploy Backup Config to Remote",
179 Self::InstallBackupCrontab => "Install Backup Crontab",
180 Self::RenderCaddyTemplates => "Render Caddy Templates",
181 Self::DeployCaddyConfigToRemote => "Deploy Caddy Config to Remote",
182 Self::RenderDockerComposeTemplates => "Render Docker Compose Templates",
183 Self::DeployComposeFilesToRemote => "Deploy Compose Files to Remote",
184 };
185 write!(f, "{name}")
186 }
187}
188
189impl From<crate::domain::environment::state::ReleaseStep> for ReleaseWorkflowStep {
190 fn from(s: crate::domain::environment::state::ReleaseStep) -> Self {
191 use crate::domain::environment::state::ReleaseStep;
192 match s {
193 ReleaseStep::CreateTrackerStorage => Self::CreateTrackerStorage,
194 ReleaseStep::InitTrackerDatabase => Self::InitTrackerDatabase,
195 ReleaseStep::RenderTrackerTemplates => Self::RenderTrackerTemplates,
196 ReleaseStep::DeployTrackerConfigToRemote => Self::DeployTrackerConfigToRemote,
197 ReleaseStep::CreatePrometheusStorage => Self::CreatePrometheusStorage,
198 ReleaseStep::RenderPrometheusTemplates => Self::RenderPrometheusTemplates,
199 ReleaseStep::DeployPrometheusConfigToRemote => Self::DeployPrometheusConfigToRemote,
200 ReleaseStep::CreateGrafanaStorage => Self::CreateGrafanaStorage,
201 ReleaseStep::RenderGrafanaTemplates => Self::RenderGrafanaTemplates,
202 ReleaseStep::DeployGrafanaProvisioning => Self::DeployGrafanaProvisioning,
203 ReleaseStep::CreateMysqlStorage => Self::CreateMysqlStorage,
204 ReleaseStep::RenderBackupTemplates => Self::RenderBackupTemplates,
205 ReleaseStep::CreateBackupStorage => Self::CreateBackupStorage,
206 ReleaseStep::DeployBackupConfigToRemote => Self::DeployBackupConfigToRemote,
207 ReleaseStep::InstallBackupCrontab => Self::InstallBackupCrontab,
208 ReleaseStep::RenderCaddyTemplates => Self::RenderCaddyTemplates,
209 ReleaseStep::DeployCaddyConfigToRemote => Self::DeployCaddyConfigToRemote,
210 ReleaseStep::RenderDockerComposeTemplates => Self::RenderDockerComposeTemplates,
211 ReleaseStep::DeployComposeFilesToRemote => Self::DeployComposeFilesToRemote,
212 }
213 }
214}
215
216#[cfg(test)]
217mod tests {
218 use super::*;
219
220 #[test]
221 fn it_should_display_not_found_persistence_error() {
222 let err = PersistenceError::NotFound;
223 assert_eq!(err.to_string(), "Environment not found");
224 }
225
226 #[test]
227 fn it_should_display_conflict_persistence_error() {
228 let err = PersistenceError::Conflict;
229 assert_eq!(
230 err.to_string(),
231 "Conflict: another process is accessing this environment"
232 );
233 }
234
235 #[test]
236 fn it_should_display_invalid_state_error() {
237 let err = InvalidStateError {
238 expected: "provisioned".to_string(),
239 actual: "created".to_string(),
240 };
241 assert_eq!(
242 err.to_string(),
243 "Expected state 'provisioned', but found 'created'"
244 );
245 }
246
247 #[test]
248 fn it_should_convert_from_repository_error_not_found() {
249 use crate::domain::environment::repository::RepositoryError;
250 let domain_err = RepositoryError::NotFound;
251 let app_err = PersistenceError::from(domain_err);
252 assert!(matches!(app_err, PersistenceError::NotFound));
253 }
254
255 #[test]
256 fn it_should_convert_from_repository_error_conflict() {
257 use crate::domain::environment::repository::RepositoryError;
258 let domain_err = RepositoryError::Conflict;
259 let app_err = PersistenceError::from(domain_err);
260 assert!(matches!(app_err, PersistenceError::Conflict));
261 }
262
263 #[test]
264 fn it_should_convert_from_state_type_error() {
265 use crate::domain::environment::state::StateTypeError;
266 let domain_err = StateTypeError::UnexpectedState {
267 expected: "provisioned",
268 actual: "created".to_string(),
269 };
270 let app_err = InvalidStateError::from(domain_err);
271 assert_eq!(app_err.expected, "provisioned");
272 assert_eq!(app_err.actual, "created");
273 }
274
275 #[test]
276 fn it_should_convert_release_step_to_workflow_step() {
277 use crate::domain::environment::state::ReleaseStep;
278 let step = ReleaseStep::InitTrackerDatabase;
279 let ws: ReleaseWorkflowStep = step.into();
280 assert_eq!(ws, ReleaseWorkflowStep::InitTrackerDatabase);
281 assert_eq!(ws.to_string(), "Initialize Tracker Database");
282 }
283}