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}