reinhardt_pages/server_fn/
server_fn_trait.rs1use serde::{Deserialize, Serialize};
6
7pub trait ServerFn {
12 type Input: Serialize + for<'de> Deserialize<'de>;
14
15 type Output: Serialize + for<'de> Deserialize<'de>;
17
18 fn endpoint() -> &'static str;
20
21 fn codec() -> &'static str {
23 "json"
24 }
25}
26
27#[derive(Debug, Clone, Serialize, Deserialize)]
32pub enum ServerFnError {
33 Network(String),
35
36 Serialization(String),
38
39 Deserialization(String),
41
42 Server {
44 status: u16,
46 message: String,
48 },
49
50 Application(String),
52}
53
54impl ServerFnError {
55 pub fn network(msg: impl Into<String>) -> Self {
57 Self::Network(msg.into())
58 }
59
60 pub fn serialization(msg: impl Into<String>) -> Self {
62 Self::Serialization(msg.into())
63 }
64
65 pub fn deserialization(msg: impl Into<String>) -> Self {
67 Self::Deserialization(msg.into())
68 }
69
70 pub fn server(status: u16, message: impl Into<String>) -> Self {
72 Self::Server {
73 status,
74 message: message.into(),
75 }
76 }
77
78 pub fn application(msg: impl Into<String>) -> Self {
80 Self::Application(msg.into())
81 }
82
83 pub fn message(&self) -> &str {
99 match self {
100 Self::Network(msg)
101 | Self::Serialization(msg)
102 | Self::Deserialization(msg)
103 | Self::Application(msg) => msg,
104 Self::Server { message, .. } => message,
105 }
106 }
107}
108
109impl std::fmt::Display for ServerFnError {
110 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
111 match self {
112 Self::Network(msg) => write!(f, "Network error: {}", msg),
113 Self::Serialization(msg) => write!(f, "Serialization error: {}", msg),
114 Self::Deserialization(msg) => write!(f, "Deserialization error: {}", msg),
115 Self::Server { status, message } => {
116 write!(f, "Server error ({}): {}", status, message)
117 }
118 Self::Application(msg) => write!(f, "Application error: {}", msg),
119 }
120 }
121}
122
123impl std::error::Error for ServerFnError {}
124
125pub fn parse_server_error_message(raw: &str) -> String {
154 if let Ok(e) = serde_json::from_str::<ServerFnError>(raw) {
156 return unwrap_nested_or_raw(e.message());
157 }
158 for prefix in [
160 "Network error: ",
161 "Serialization error: ",
162 "Deserialization error: ",
163 "Application error: ",
164 ] {
165 if let Some(msg) = raw.strip_prefix(prefix) {
166 return unwrap_nested_or_raw(msg);
167 }
168 }
169 if let Some(rest) = raw.strip_prefix("Server error (")
171 && let Some(idx) = rest.find("): ")
172 {
173 return unwrap_nested_or_raw(&rest[idx + 3..]);
174 }
175 raw.to_string()
177}
178
179fn unwrap_nested_or_raw(msg: &str) -> String {
182 serde_json::from_str::<ServerFnError>(msg)
183 .map(|e| e.message().to_string())
184 .unwrap_or_else(|_| msg.to_string())
185}
186
187#[cfg(test)]
188mod tests {
189 use rstest::rstest;
190
191 use super::*;
192
193 #[rstest]
194 fn test_server_fn_error_creation() {
195 let network_err = ServerFnError::network("Connection timeout");
197 let server_err = ServerFnError::server(404, "Not found");
198
199 assert!(matches!(network_err, ServerFnError::Network(_)));
201 assert!(matches!(
202 server_err,
203 ServerFnError::Server { status: 404, .. }
204 ));
205 }
206
207 #[rstest]
208 fn test_server_fn_error_display() {
209 let network_err = ServerFnError::network("Connection timeout");
211 let server_err = ServerFnError::server(500, "Internal error");
212
213 assert_eq!(network_err.to_string(), "Network error: Connection timeout");
215 assert_eq!(server_err.to_string(), "Server error (500): Internal error");
216 }
217
218 #[rstest]
219 #[case::network(ServerFnError::network("timeout"), "timeout")]
220 #[case::serialization(ServerFnError::serialization("bad input"), "bad input")]
221 #[case::deserialization(ServerFnError::deserialization("bad json"), "bad json")]
222 #[case::server(ServerFnError::server(403, "Forbidden"), "Forbidden")]
223 #[case::application(ServerFnError::application("Invalid choice_id"), "Invalid choice_id")]
224 fn test_message_returns_inner_text(#[case] err: ServerFnError, #[case] expected: &str) {
225 let msg = err.message();
227
228 assert_eq!(msg, expected);
230 }
231
232 #[rstest]
233 fn test_message_returns_empty_string_when_inner_is_empty() {
234 let err = ServerFnError::application("");
236
237 assert_eq!(err.message(), "");
239 }
240
241 #[rstest]
242 fn test_message_differs_from_display() {
243 let err = ServerFnError::application("Invalid choice_id");
245
246 let message = err.message();
248 let display = err.to_string();
249
250 assert_ne!(message, display);
252 assert_eq!(message, "Invalid choice_id");
253 assert_eq!(display, "Application error: Invalid choice_id");
254 }
255
256 #[rstest]
257 #[case::application(r#"{"Application":"Invalid choice_id"}"#, "Invalid choice_id")]
258 #[case::server(r#"{"Server":{"status":403,"message":"Forbidden"}}"#, "Forbidden")]
259 #[case::network(r#"{"Network":"Connection timeout"}"#, "Connection timeout")]
260 fn test_parse_server_error_message_from_json(#[case] json: &str, #[case] expected: &str) {
261 let msg = parse_server_error_message(json);
263
264 assert_eq!(msg, expected);
266 }
267
268 #[rstest]
269 #[case::application("Application error: Invalid choice_id", "Invalid choice_id")]
270 #[case::network("Network error: Connection timeout", "Connection timeout")]
271 #[case::serialization("Serialization error: bad input", "bad input")]
272 #[case::deserialization("Deserialization error: bad json", "bad json")]
273 #[case::server("Server error (403): Forbidden", "Forbidden")]
274 #[case::server_500("Server error (500): Internal error", "Internal error")]
275 fn test_parse_server_error_message_from_display(#[case] display: &str, #[case] expected: &str) {
276 let msg = parse_server_error_message(display);
278
279 assert_eq!(msg, expected);
281 }
282
283 #[rstest]
284 #[case::server_wrapping_application(
285 r#"Server error (500): {"Application":"Invalid choice_id"}"#,
286 "Invalid choice_id"
287 )]
288 #[case::server_wrapping_network(
289 r#"Server error (500): {"Network":"Connection lost"}"#,
290 "Connection lost"
291 )]
292 #[case::json_server_wrapping_application(
293 r#"{"Server":{"status":500,"message":"{\"Application\":\"Invalid choice_id\"}"}}"#,
294 "Invalid choice_id"
295 )]
296 fn test_parse_server_error_message_unwraps_nested_json(
297 #[case] input: &str,
298 #[case] expected: &str,
299 ) {
300 let msg = parse_server_error_message(input);
302
303 assert_eq!(msg, expected);
305 }
306
307 #[rstest]
308 fn test_parse_server_error_message_falls_back_for_invalid_json() {
309 let raw = "plain error text";
311
312 let msg = parse_server_error_message(raw);
314
315 assert_eq!(msg, "plain error text");
317 }
318
319 #[rstest]
320 fn test_parse_server_error_message_falls_back_for_empty_string() {
321 let msg = parse_server_error_message("");
323
324 assert_eq!(msg, "");
326 }
327
328 #[rstest]
329 fn test_parse_server_error_message_falls_back_for_non_server_fn_error_json() {
330 let raw = r#"{"foo":"bar"}"#;
332
333 let msg = parse_server_error_message(raw);
335
336 assert_eq!(msg, raw);
338 }
339}