opendev_runtime/
error_handler.rs1use serde::{Deserialize, Serialize};
10
11#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
13#[serde(rename_all = "lowercase")]
14pub enum ErrorAction {
15 Retry,
17 Skip,
19 Cancel,
21 Edit,
23}
24
25impl ErrorAction {
26 pub fn from_char(c: char) -> Option<Self> {
28 match c {
29 'r' => Some(Self::Retry),
30 's' => Some(Self::Skip),
31 'c' => Some(Self::Cancel),
32 'e' => Some(Self::Edit),
33 _ => None,
34 }
35 }
36
37 pub fn as_char(&self) -> char {
39 match self {
40 Self::Retry => 'r',
41 Self::Skip => 's',
42 Self::Cancel => 'c',
43 Self::Edit => 'e',
44 }
45 }
46}
47
48#[derive(Debug, Clone)]
50pub struct ErrorResult {
51 pub action: ErrorAction,
52 pub should_retry: bool,
53 pub should_cancel: bool,
54 pub edited_params: Option<serde_json::Value>,
55}
56
57impl ErrorResult {
58 pub fn retry() -> Self {
60 Self {
61 action: ErrorAction::Retry,
62 should_retry: true,
63 should_cancel: false,
64 edited_params: None,
65 }
66 }
67
68 pub fn skip() -> Self {
70 Self {
71 action: ErrorAction::Skip,
72 should_retry: false,
73 should_cancel: false,
74 edited_params: None,
75 }
76 }
77
78 pub fn cancel() -> Self {
80 Self {
81 action: ErrorAction::Cancel,
82 should_retry: false,
83 should_cancel: true,
84 edited_params: None,
85 }
86 }
87
88 pub fn edit(params: serde_json::Value) -> Self {
90 Self {
91 action: ErrorAction::Edit,
92 should_retry: true,
93 should_cancel: false,
94 edited_params: Some(params),
95 }
96 }
97}
98
99#[derive(Debug, Clone)]
101pub struct OperationError {
102 pub message: String,
104 pub operation_type: String,
106 pub target: String,
108 pub allow_retry: bool,
110 pub allow_edit: bool,
112}
113
114pub fn available_actions(error: &OperationError) -> Vec<(ErrorAction, &'static str)> {
116 let mut actions = Vec::new();
117 if error.allow_retry {
118 actions.push((ErrorAction::Retry, "Retry"));
119 }
120 if error.allow_edit {
121 actions.push((ErrorAction::Edit, "Edit parameters and retry"));
122 }
123 actions.push((ErrorAction::Skip, "Skip this operation"));
124 actions.push((ErrorAction::Cancel, "Cancel all remaining operations"));
125 actions
126}
127
128pub fn resolve_choice(choice: char, error: &OperationError) -> Option<ErrorResult> {
132 match choice {
133 'r' if error.allow_retry => Some(ErrorResult::retry()),
134 's' => Some(ErrorResult::skip()),
135 'c' => Some(ErrorResult::cancel()),
136 'e' if error.allow_edit => {
137 None
139 }
140 _ => None,
141 }
142}
143
144pub fn is_transient_error(message: &str) -> bool {
146 let lower = message.to_lowercase();
147 let transient_patterns = [
148 "timeout",
149 "connection reset",
150 "connection refused",
151 "temporarily unavailable",
152 "service unavailable",
153 "bad gateway",
154 "gateway timeout",
155 "rate limit",
156 "too many requests",
157 "overloaded",
158 ];
159 transient_patterns.iter().any(|p| lower.contains(p))
160}
161
162#[cfg(test)]
163#[path = "error_handler_tests.rs"]
164mod tests;