Skip to main content

continuity_runtime/
recovery.rs

1use crate::error::{ContinuityValidationError, ContinuityValidationResult};
2use crate::validation::{require_non_empty, require_non_empty_slice, require_schema_version};
3use crate::vocab::RecoveryReplayStateV1;
4use crate::vocab::ResilienceExerciseKindV1;
5use schemars::JsonSchema;
6use serde::{Deserialize, Serialize};
7
8#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
9pub struct RecoveryPlanV1 {
10    pub schema_version: String,
11    pub recovery_plan_id: String,
12    pub incident_case_id: String,
13    pub recovery_steps: Vec<String>,
14    pub success_criteria: Vec<String>,
15    pub owner_ref: String,
16    pub approved_at: String,
17}
18
19impl RecoveryPlanV1 {
20    pub const SCHEMA_VERSION: &'static str = "RecoveryPlanV1";
21
22    pub fn new(
23        recovery_plan_id: impl Into<String>,
24        incident_case_id: impl Into<String>,
25        recovery_steps: Vec<String>,
26        success_criteria: Vec<String>,
27        owner_ref: impl Into<String>,
28        approved_at: impl Into<String>,
29    ) -> Result<Self, ContinuityValidationError> {
30        let value = Self {
31            schema_version: Self::SCHEMA_VERSION.to_string(),
32            recovery_plan_id: recovery_plan_id.into(),
33            incident_case_id: incident_case_id.into(),
34            recovery_steps,
35            success_criteria,
36            owner_ref: owner_ref.into(),
37            approved_at: approved_at.into(),
38        };
39        value.validate()?;
40        Ok(value)
41    }
42
43    pub fn validate(&self) -> ContinuityValidationResult {
44        require_schema_version(&self.schema_version, Self::SCHEMA_VERSION)?;
45        require_non_empty(&self.recovery_plan_id, "recovery_plan_id")?;
46        require_non_empty(&self.incident_case_id, "incident_case_id")?;
47        require_non_empty_slice(&self.recovery_steps, "recovery_steps")?;
48        require_non_empty_slice(&self.success_criteria, "success_criteria")?;
49        require_non_empty(&self.owner_ref, "owner_ref")?;
50        require_non_empty(&self.approved_at, "approved_at")?;
51        Ok(())
52    }
53}
54
55#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
56pub struct RecoveryReplaySliceV1 {
57    pub schema_version: String,
58    pub recovery_replay_slice_id: String,
59    pub incident_case_id: String,
60    pub artifact_refs: Vec<String>,
61    pub time_window: String,
62    pub replay_state: RecoveryReplayStateV1,
63    pub gaps: Vec<String>,
64}
65
66impl RecoveryReplaySliceV1 {
67    pub const SCHEMA_VERSION: &'static str = "RecoveryReplaySliceV1";
68
69    pub fn new(
70        recovery_replay_slice_id: impl Into<String>,
71        incident_case_id: impl Into<String>,
72        artifact_refs: Vec<String>,
73        time_window: impl Into<String>,
74        replay_state: RecoveryReplayStateV1,
75        gaps: Vec<String>,
76    ) -> Result<Self, ContinuityValidationError> {
77        let value = Self {
78            schema_version: Self::SCHEMA_VERSION.to_string(),
79            recovery_replay_slice_id: recovery_replay_slice_id.into(),
80            incident_case_id: incident_case_id.into(),
81            artifact_refs,
82            time_window: time_window.into(),
83            replay_state,
84            gaps,
85        };
86        value.validate()?;
87        Ok(value)
88    }
89
90    pub fn validate(&self) -> ContinuityValidationResult {
91        require_schema_version(&self.schema_version, Self::SCHEMA_VERSION)?;
92        require_non_empty(&self.recovery_replay_slice_id, "recovery_replay_slice_id")?;
93        require_non_empty(&self.incident_case_id, "incident_case_id")?;
94        require_non_empty_slice(&self.artifact_refs, "artifact_refs")?;
95        require_non_empty(&self.time_window, "time_window")?;
96        if matches!(self.replay_state, RecoveryReplayStateV1::Partial) && self.gaps.is_empty() {
97            return Err(ContinuityValidationError::InvalidState("gaps"));
98        }
99        Ok(())
100    }
101}
102
103#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
104pub struct PostmortemBundleV1 {
105    pub schema_version: String,
106    pub postmortem_bundle_id: String,
107    pub incident_case_id: String,
108    pub root_cause_summary: String,
109    pub corrective_actions: Vec<String>,
110    pub owner_ref: String,
111    pub published_at: String,
112}
113
114impl PostmortemBundleV1 {
115    pub const SCHEMA_VERSION: &'static str = "PostmortemBundleV1";
116
117    pub fn new(
118        postmortem_bundle_id: impl Into<String>,
119        incident_case_id: impl Into<String>,
120        root_cause_summary: impl Into<String>,
121        corrective_actions: Vec<String>,
122        owner_ref: impl Into<String>,
123        published_at: impl Into<String>,
124    ) -> Result<Self, ContinuityValidationError> {
125        let value = Self {
126            schema_version: Self::SCHEMA_VERSION.to_string(),
127            postmortem_bundle_id: postmortem_bundle_id.into(),
128            incident_case_id: incident_case_id.into(),
129            root_cause_summary: root_cause_summary.into(),
130            corrective_actions,
131            owner_ref: owner_ref.into(),
132            published_at: published_at.into(),
133        };
134        value.validate()?;
135        Ok(value)
136    }
137
138    pub fn validate(&self) -> ContinuityValidationResult {
139        require_schema_version(&self.schema_version, Self::SCHEMA_VERSION)?;
140        require_non_empty(&self.postmortem_bundle_id, "postmortem_bundle_id")?;
141        require_non_empty(&self.incident_case_id, "incident_case_id")?;
142        require_non_empty(&self.root_cause_summary, "root_cause_summary")?;
143        require_non_empty_slice(&self.corrective_actions, "corrective_actions")?;
144        require_non_empty(&self.owner_ref, "owner_ref")?;
145        require_non_empty(&self.published_at, "published_at")?;
146        Ok(())
147    }
148}
149
150#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
151pub struct ResilienceExerciseV1 {
152    pub schema_version: String,
153    pub resilience_exercise_id: String,
154    pub service_level_profile_id: String,
155    pub exercise_kind: ResilienceExerciseKindV1,
156    pub scenario: String,
157    pub completed_at: String,
158    pub follow_up_actions: Vec<String>,
159}
160
161impl ResilienceExerciseV1 {
162    pub const SCHEMA_VERSION: &'static str = "ResilienceExerciseV1";
163
164    pub fn new(
165        resilience_exercise_id: impl Into<String>,
166        service_level_profile_id: impl Into<String>,
167        exercise_kind: ResilienceExerciseKindV1,
168        scenario: impl Into<String>,
169        completed_at: impl Into<String>,
170        follow_up_actions: Vec<String>,
171    ) -> Result<Self, ContinuityValidationError> {
172        let value = Self {
173            schema_version: Self::SCHEMA_VERSION.to_string(),
174            resilience_exercise_id: resilience_exercise_id.into(),
175            service_level_profile_id: service_level_profile_id.into(),
176            exercise_kind,
177            scenario: scenario.into(),
178            completed_at: completed_at.into(),
179            follow_up_actions,
180        };
181        value.validate()?;
182        Ok(value)
183    }
184
185    pub fn validate(&self) -> ContinuityValidationResult {
186        require_schema_version(&self.schema_version, Self::SCHEMA_VERSION)?;
187        require_non_empty(&self.resilience_exercise_id, "resilience_exercise_id")?;
188        require_non_empty(&self.service_level_profile_id, "service_level_profile_id")?;
189        require_non_empty(&self.scenario, "scenario")?;
190        require_non_empty(&self.completed_at, "completed_at")?;
191        require_non_empty_slice(&self.follow_up_actions, "follow_up_actions")?;
192        Ok(())
193    }
194}