1use crate::error::{ContinuityValidationError, ContinuityValidationResult};
2use crate::validation::{require_non_empty, require_non_empty_slice, require_schema_version};
3use crate::vocab::IncidentSeverityV1;
4use schemars::JsonSchema;
5use serde::{Deserialize, Serialize};
6
7#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
8pub struct IncidentClassRuleV1 {
9 pub incident_class: String,
10 pub default_severity: IncidentSeverityV1,
11}
12
13impl IncidentClassRuleV1 {
14 pub fn validate(&self) -> ContinuityValidationResult {
15 require_non_empty(&self.incident_class, "incident_class")
16 }
17}
18
19#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
20pub struct IncidentRouteRuleV1 {
21 pub incident_class: String,
22 pub route: String,
23}
24
25impl IncidentRouteRuleV1 {
26 pub fn validate(&self) -> ContinuityValidationResult {
27 require_non_empty(&self.incident_class, "incident_class")?;
28 require_non_empty(&self.route, "route")
29 }
30}
31
32#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
33pub struct SeverityRuleV1 {
34 pub condition: String,
35 pub severity: IncidentSeverityV1,
36}
37
38impl SeverityRuleV1 {
39 pub fn validate(&self) -> ContinuityValidationResult {
40 require_non_empty(&self.condition, "condition")
41 }
42}
43
44#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
45pub struct SeverityResponseClockV1 {
46 pub severity: IncidentSeverityV1,
47 pub minutes: i64,
48}
49
50impl SeverityResponseClockV1 {
51 pub fn validate(&self) -> ContinuityValidationResult {
52 if self.minutes <= 0 {
53 return Err(ContinuityValidationError::InvalidState("minutes"));
54 }
55 Ok(())
56 }
57}
58
59#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
60pub struct SeverityPostmortemClockV1 {
61 pub severity: IncidentSeverityV1,
62 pub hours: i64,
63}
64
65impl SeverityPostmortemClockV1 {
66 pub fn validate(&self) -> ContinuityValidationResult {
67 if self.hours <= 0 {
68 return Err(ContinuityValidationError::InvalidState("hours"));
69 }
70 Ok(())
71 }
72}
73
74#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
75pub struct IncidentTaxonomyV1 {
76 pub schema_version: String,
77 pub incident_taxonomy_id: String,
78 #[serde(default, skip_serializing_if = "Vec::is_empty")]
79 pub incident_classes: Vec<IncidentClassRuleV1>,
80 #[serde(default, skip_serializing_if = "Vec::is_empty")]
81 pub default_routes: Vec<IncidentRouteRuleV1>,
82 #[serde(default, skip_serializing_if = "Vec::is_empty")]
83 pub required_artifact_families: Vec<String>,
84}
85
86impl IncidentTaxonomyV1 {
87 pub const SCHEMA_VERSION: &'static str = "IncidentTaxonomyV1";
88
89 pub fn new(
90 incident_taxonomy_id: impl Into<String>,
91 incident_classes: Vec<IncidentClassRuleV1>,
92 default_routes: Vec<IncidentRouteRuleV1>,
93 required_artifact_families: Vec<String>,
94 ) -> Result<Self, ContinuityValidationError> {
95 let value = Self {
96 schema_version: Self::SCHEMA_VERSION.to_string(),
97 incident_taxonomy_id: incident_taxonomy_id.into(),
98 incident_classes,
99 default_routes,
100 required_artifact_families,
101 };
102 value.validate()?;
103 Ok(value)
104 }
105
106 pub fn validate(&self) -> ContinuityValidationResult {
107 require_schema_version(&self.schema_version, Self::SCHEMA_VERSION)?;
108 require_non_empty(&self.incident_taxonomy_id, "incident_taxonomy_id")?;
109 require_non_empty_slice(&self.incident_classes, "incident_classes")?;
110 require_non_empty_slice(&self.default_routes, "default_routes")?;
111 require_non_empty_slice(
112 &self.required_artifact_families,
113 "required_artifact_families",
114 )?;
115 for rule in &self.incident_classes {
116 rule.validate()?;
117 }
118 for route in &self.default_routes {
119 route.validate()?;
120 }
121 Ok(())
122 }
123}
124
125#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
126pub struct SeverityMatrixV1 {
127 pub schema_version: String,
128 pub severity_matrix_id: String,
129 #[serde(default, skip_serializing_if = "Vec::is_empty")]
130 pub severity_rules: Vec<SeverityRuleV1>,
131 pub customer_impact_rubric: String,
132 pub internal_impact_rubric: String,
133 pub override_rule: String,
134}
135
136impl SeverityMatrixV1 {
137 pub const SCHEMA_VERSION: &'static str = "SeverityMatrixV1";
138
139 pub fn new(
140 severity_matrix_id: impl Into<String>,
141 severity_rules: Vec<SeverityRuleV1>,
142 customer_impact_rubric: impl Into<String>,
143 internal_impact_rubric: impl Into<String>,
144 override_rule: impl Into<String>,
145 ) -> Result<Self, ContinuityValidationError> {
146 let value = Self {
147 schema_version: Self::SCHEMA_VERSION.to_string(),
148 severity_matrix_id: severity_matrix_id.into(),
149 severity_rules,
150 customer_impact_rubric: customer_impact_rubric.into(),
151 internal_impact_rubric: internal_impact_rubric.into(),
152 override_rule: override_rule.into(),
153 };
154 value.validate()?;
155 Ok(value)
156 }
157
158 pub fn validate(&self) -> ContinuityValidationResult {
159 require_schema_version(&self.schema_version, Self::SCHEMA_VERSION)?;
160 require_non_empty(&self.severity_matrix_id, "severity_matrix_id")?;
161 require_non_empty_slice(&self.severity_rules, "severity_rules")?;
162 require_non_empty(&self.customer_impact_rubric, "customer_impact_rubric")?;
163 require_non_empty(&self.internal_impact_rubric, "internal_impact_rubric")?;
164 require_non_empty(&self.override_rule, "override_rule")?;
165 for rule in &self.severity_rules {
166 rule.validate()?;
167 }
168 Ok(())
169 }
170}
171
172#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
173pub struct PagerRouteProfileV1 {
174 pub schema_version: String,
175 pub pager_route_profile_id: String,
176 #[serde(default, skip_serializing_if = "Vec::is_empty")]
177 pub rotation_refs: Vec<String>,
178 #[serde(default, skip_serializing_if = "Vec::is_empty")]
179 pub handoff_rules: Vec<String>,
180 pub ack_timeout_minutes: i64,
181 pub max_levels: i64,
182}
183
184impl PagerRouteProfileV1 {
185 pub const SCHEMA_VERSION: &'static str = "PagerRouteProfileV1";
186
187 pub fn new(
188 pager_route_profile_id: impl Into<String>,
189 rotation_refs: Vec<String>,
190 handoff_rules: Vec<String>,
191 ack_timeout_minutes: i64,
192 max_levels: i64,
193 ) -> Result<Self, ContinuityValidationError> {
194 let value = Self {
195 schema_version: Self::SCHEMA_VERSION.to_string(),
196 pager_route_profile_id: pager_route_profile_id.into(),
197 rotation_refs,
198 handoff_rules,
199 ack_timeout_minutes,
200 max_levels,
201 };
202 value.validate()?;
203 Ok(value)
204 }
205
206 pub fn validate(&self) -> ContinuityValidationResult {
207 require_schema_version(&self.schema_version, Self::SCHEMA_VERSION)?;
208 require_non_empty(&self.pager_route_profile_id, "pager_route_profile_id")?;
209 require_non_empty_slice(&self.rotation_refs, "rotation_refs")?;
210 require_non_empty_slice(&self.handoff_rules, "handoff_rules")?;
211 if self.ack_timeout_minutes <= 0 {
212 return Err(ContinuityValidationError::InvalidState(
213 "ack_timeout_minutes",
214 ));
215 }
216 if self.max_levels <= 0 {
217 return Err(ContinuityValidationError::InvalidState("max_levels"));
218 }
219 Ok(())
220 }
221}
222
223#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
224pub struct EscalationClockPolicyV1 {
225 pub schema_version: String,
226 pub escalation_clock_policy_id: String,
227 pub severity_matrix_id: String,
228 #[serde(default, skip_serializing_if = "Vec::is_empty")]
229 pub response_clock_minutes: Vec<SeverityResponseClockV1>,
230 #[serde(default, skip_serializing_if = "Vec::is_empty")]
231 pub postmortem_clock_hours: Vec<SeverityPostmortemClockV1>,
232 #[serde(default, skip_serializing_if = "Vec::is_empty")]
233 pub pause_rules: Vec<String>,
234 pub exception_path: String,
235}
236
237impl EscalationClockPolicyV1 {
238 pub const SCHEMA_VERSION: &'static str = "EscalationClockPolicyV1";
239
240 pub fn new(
241 escalation_clock_policy_id: impl Into<String>,
242 severity_matrix_id: impl Into<String>,
243 response_clock_minutes: Vec<SeverityResponseClockV1>,
244 postmortem_clock_hours: Vec<SeverityPostmortemClockV1>,
245 pause_rules: Vec<String>,
246 exception_path: impl Into<String>,
247 ) -> Result<Self, ContinuityValidationError> {
248 let value = Self {
249 schema_version: Self::SCHEMA_VERSION.to_string(),
250 escalation_clock_policy_id: escalation_clock_policy_id.into(),
251 severity_matrix_id: severity_matrix_id.into(),
252 response_clock_minutes,
253 postmortem_clock_hours,
254 pause_rules,
255 exception_path: exception_path.into(),
256 };
257 value.validate()?;
258 Ok(value)
259 }
260
261 pub fn validate(&self) -> ContinuityValidationResult {
262 require_schema_version(&self.schema_version, Self::SCHEMA_VERSION)?;
263 require_non_empty(
264 &self.escalation_clock_policy_id,
265 "escalation_clock_policy_id",
266 )?;
267 require_non_empty(&self.severity_matrix_id, "severity_matrix_id")?;
268 require_non_empty_slice(&self.response_clock_minutes, "response_clock_minutes")?;
269 require_non_empty_slice(&self.postmortem_clock_hours, "postmortem_clock_hours")?;
270 require_non_empty(&self.exception_path, "exception_path")?;
271 for clock in &self.response_clock_minutes {
272 clock.validate()?;
273 }
274 for clock in &self.postmortem_clock_hours {
275 clock.validate()?;
276 }
277 Ok(())
278 }
279}