Skip to main content

continuity_runtime/
profile_p7_incident_routing.rs

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}