gardesk/garwarp / 6166dd7

Browse files

map portal errors

Authored by mfwolffe <wolffemf@dukes.jmu.edu>
SHA
6166dd7dbb784ca49a53d61a60dc957a6e4d1e91
Parents
789b4f0
Tree
3319a07

5 changed files

StatusFile+-
M garwarp/src/daemon.rs 35 3
A garwarp/src/error.rs 140 0
M garwarp/src/main.rs 1 0
M garwarp/src/request.rs 2 0
M garwarp/src/window.rs 2 0
garwarp/src/daemon.rsmodified
@@ -8,6 +8,7 @@ use garwarp_ipc::{ControlRequest, ControlResponse, HealthStatus, StatusResponse}
88
 
99
 use crate::config::Config;
1010
 use crate::dbus::{self, SessionNameGuard};
11
+use crate::error::{PortalError, map_portal_error};
1112
 use crate::lock::SingleInstanceGuard;
1213
 use crate::logging;
1314
 use crate::request::RequestRegistry;
@@ -87,9 +88,12 @@ fn handle_connection(stream: UnixStream, state: &mut DaemonState) -> io::Result<
8788
             state.running = false;
8889
             ControlResponse::AckStopping
8990
         }
90
-        None => ControlResponse::Error {
91
-            reason: "invalid_request".to_string(),
92
-        },
91
+        None => {
92
+            let mapping = map_portal_error(&PortalError::InvalidRequestPayload);
93
+            ControlResponse::Error {
94
+                reason: mapping.reason.to_string(),
95
+            }
96
+        }
9397
     };
9498
 
9599
     let stream = reader.into_inner();
@@ -191,4 +195,32 @@ mod tests {
191195
         assert_eq!(state.health, HealthStatus::Stopping);
192196
         assert!(!state.running);
193197
     }
198
+
199
+    #[test]
200
+    fn invalid_request_uses_stable_error_reason() {
201
+        let (mut client, server) = UnixStream::pair().expect("pair should be created");
202
+        client
203
+            .write_all(b"unknown\n")
204
+            .expect("invalid request should be written");
205
+
206
+        let mut state = DaemonState {
207
+            health: HealthStatus::Healthy,
208
+            requests: RequestRegistry::new(Duration::from_secs(5)),
209
+            running: true,
210
+        };
211
+        handle_connection(server, &mut state).expect("request should be handled");
212
+
213
+        let mut response_line = String::new();
214
+        let mut reader = BufReader::new(client);
215
+        reader
216
+            .read_line(&mut response_line)
217
+            .expect("response should be readable");
218
+        let response = ControlResponse::parse_line(&response_line).expect("response should parse");
219
+        assert_eq!(
220
+            response,
221
+            ControlResponse::Error {
222
+                reason: "invalid_request".to_string(),
223
+            }
224
+        );
225
+    }
194226
 }
garwarp/src/error.rsadded
@@ -0,0 +1,140 @@
1
+#![allow(dead_code)]
2
+
3
+use crate::request::RequestError;
4
+
5
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
6
+#[repr(u32)]
7
+pub enum PortalResponseCode {
8
+    Success = 0,
9
+    Cancelled = 1,
10
+    Failed = 2,
11
+}
12
+
13
+#[derive(Debug, Clone, PartialEq, Eq)]
14
+pub enum PortalError {
15
+    CancelledByUser,
16
+    InvalidRequestPayload,
17
+    InvalidParentWindow,
18
+    OwnershipMismatch,
19
+    RequestNotFound,
20
+    RequestAlreadyExists,
21
+    RequestTimeout,
22
+    InvalidTransition,
23
+    InternalFailure,
24
+}
25
+
26
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
27
+pub struct ErrorMapping {
28
+    pub code: PortalResponseCode,
29
+    pub reason: &'static str,
30
+}
31
+
32
+#[must_use]
33
+pub fn map_portal_error(error: &PortalError) -> ErrorMapping {
34
+    match error {
35
+        PortalError::CancelledByUser => ErrorMapping {
36
+            code: PortalResponseCode::Cancelled,
37
+            reason: "cancelled",
38
+        },
39
+        PortalError::InvalidRequestPayload => ErrorMapping {
40
+            code: PortalResponseCode::Failed,
41
+            reason: "invalid_request",
42
+        },
43
+        PortalError::InvalidParentWindow => ErrorMapping {
44
+            code: PortalResponseCode::Failed,
45
+            reason: "invalid_parent_window",
46
+        },
47
+        PortalError::OwnershipMismatch => ErrorMapping {
48
+            code: PortalResponseCode::Failed,
49
+            reason: "ownership_mismatch",
50
+        },
51
+        PortalError::RequestNotFound => ErrorMapping {
52
+            code: PortalResponseCode::Failed,
53
+            reason: "request_not_found",
54
+        },
55
+        PortalError::RequestAlreadyExists => ErrorMapping {
56
+            code: PortalResponseCode::Failed,
57
+            reason: "request_conflict",
58
+        },
59
+        PortalError::RequestTimeout => ErrorMapping {
60
+            code: PortalResponseCode::Failed,
61
+            reason: "request_timeout",
62
+        },
63
+        PortalError::InvalidTransition => ErrorMapping {
64
+            code: PortalResponseCode::Failed,
65
+            reason: "invalid_transition",
66
+        },
67
+        PortalError::InternalFailure => ErrorMapping {
68
+            code: PortalResponseCode::Failed,
69
+            reason: "internal_error",
70
+        },
71
+    }
72
+}
73
+
74
+#[must_use]
75
+pub fn map_request_error(error: &RequestError) -> ErrorMapping {
76
+    match error {
77
+        RequestError::AlreadyExists(_) => map_portal_error(&PortalError::RequestAlreadyExists),
78
+        RequestError::NotFound(_) => map_portal_error(&PortalError::RequestNotFound),
79
+        RequestError::OwnerMismatch(_) => map_portal_error(&PortalError::OwnershipMismatch),
80
+        RequestError::InvalidTransition { .. } => map_portal_error(&PortalError::InvalidTransition),
81
+    }
82
+}
83
+
84
+#[cfg(test)]
85
+mod tests {
86
+    use super::{PortalError, PortalResponseCode, map_portal_error, map_request_error};
87
+    use crate::request::{RequestError, RequestState};
88
+
89
+    #[test]
90
+    fn cancelled_maps_to_cancelled_code() {
91
+        let mapping = map_portal_error(&PortalError::CancelledByUser);
92
+        assert_eq!(mapping.code, PortalResponseCode::Cancelled);
93
+        assert_eq!(mapping.reason, "cancelled");
94
+        assert_eq!(mapping.code as u32, 1);
95
+    }
96
+
97
+    #[test]
98
+    fn request_owner_mismatch_maps_to_failed_code() {
99
+        let mapping = map_request_error(&RequestError::OwnerMismatch("req-1".to_string()));
100
+        assert_eq!(mapping.code, PortalResponseCode::Failed);
101
+        assert_eq!(mapping.reason, "ownership_mismatch");
102
+        assert_eq!(mapping.code as u32, 2);
103
+    }
104
+
105
+    #[test]
106
+    fn invalid_transition_maps_to_stable_reason() {
107
+        let mapping = map_request_error(&RequestError::InvalidTransition {
108
+            id: "req-1".to_string(),
109
+            from: RequestState::Pending,
110
+            to: RequestState::Fulfilled,
111
+        });
112
+        assert_eq!(mapping.code, PortalResponseCode::Failed);
113
+        assert_eq!(mapping.reason, "invalid_transition");
114
+    }
115
+
116
+    #[test]
117
+    fn all_portal_error_variants_are_mapped() {
118
+        let errors = [
119
+            PortalError::CancelledByUser,
120
+            PortalError::InvalidRequestPayload,
121
+            PortalError::InvalidParentWindow,
122
+            PortalError::OwnershipMismatch,
123
+            PortalError::RequestNotFound,
124
+            PortalError::RequestAlreadyExists,
125
+            PortalError::RequestTimeout,
126
+            PortalError::InvalidTransition,
127
+            PortalError::InternalFailure,
128
+        ];
129
+
130
+        for error in errors {
131
+            let mapping = map_portal_error(&error);
132
+            assert!(!mapping.reason.is_empty());
133
+        }
134
+    }
135
+
136
+    #[test]
137
+    fn success_code_constant_stays_zero() {
138
+        assert_eq!(PortalResponseCode::Success as u32, 0);
139
+    }
140
+}
garwarp/src/main.rsmodified
@@ -1,6 +1,7 @@
11
 mod config;
22
 mod daemon;
33
 mod dbus;
4
+mod error;
45
 mod lock;
56
 mod logging;
67
 mod request;
garwarp/src/request.rsmodified
@@ -1,3 +1,5 @@
1
+#![allow(dead_code)]
2
+
13
 use std::collections::HashMap;
24
 use std::fmt;
35
 use std::time::{Duration, Instant};
garwarp/src/window.rsmodified
@@ -1,3 +1,5 @@
1
+#![allow(dead_code)]
2
+
13
 use std::fmt;
24
 
35
 #[derive(Debug, Clone, Copy, PartialEq, Eq)]