1use crate::{SysErrorCode, http_status_for_reason};
4use serde::Serialize;
5use std::fmt::Display;
6
7#[derive(Debug, Serialize)]
8pub struct ErrorResponse<'a> {
9 pub status: u16,
10 pub sys_code: u16,
11 pub tag: &'a str,
12 pub message: String,
13}
14
15pub fn build_error_response<'a, R: SysErrorCode + Display>(r: &'a R) -> ErrorResponse<'a> {
16 ErrorResponse {
17 status: http_status_for_reason(r),
18 sys_code: r.sys_code(),
19 tag: r.sys_tag(),
20 message: r.to_string(),
21 }
22}
23
24pub fn error_response_json<R: SysErrorCode + Display>(r: &R) -> (u16, String) {
25 let er = build_error_response(r);
26 (
27 er.status,
28 serde_json::to_string(&er).unwrap_or_else(|_| {
29 format!(
30 "{{\"status\":{},\"sys_code\":{},\"tag\":\"{}\",\"message\":\"{}\"}}",
31 er.status,
32 er.sys_code,
33 er.tag,
34 er.message.replace('"', "'")
35 )
36 }),
37 )
38}
39
40pub fn error_response_text<R: SysErrorCode + Display>(r: &R) -> (u16, String) {
41 let er = build_error_response(r);
42 (
43 er.status,
44 format!("[{}] {}: {}", er.tag, er.sys_code, er.message),
45 )
46}
47
48#[cfg(test)]
49mod tests {
50 use super::*;
51 use std::fmt;
52
53 struct TestError {
54 code: u16,
55 tag: &'static str,
56 msg: String,
57 }
58
59 impl SysErrorCode for TestError {
60 fn sys_code(&self) -> u16 {
61 self.code
62 }
63 fn sys_tag(&self) -> &'static str {
64 self.tag
65 }
66 }
67
68 impl fmt::Display for TestError {
69 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
70 write!(f, "{}", self.msg)
71 }
72 }
73
74 #[test]
75 fn test_build_error_response() {
76 let err = TestError {
77 code: 42201,
78 tag: "test.tag",
79 msg: "test message".into(),
80 };
81 let resp = build_error_response(&err);
82 assert_eq!(resp.status, 422);
83 assert_eq!(resp.sys_code, 42201);
84 assert_eq!(resp.tag, "test.tag");
85 assert_eq!(resp.message, "test message");
86 }
87
88 #[test]
89 fn test_error_response_json() {
90 let err = TestError {
91 code: 40401,
92 tag: "conf.core",
93 msg: "not found".into(),
94 };
95 let (status, json) = error_response_json(&err);
96 assert_eq!(status, 404);
97 assert!(json.contains("\"status\":404"));
98 assert!(json.contains("\"sys_code\":40401"));
99 assert!(json.contains("\"tag\":\"conf.core\""));
100 assert!(json.contains("\"message\":\"not found\""));
101 }
102
103 #[test]
104 fn test_error_response_json_with_quotes() {
105 let err = TestError {
106 code: 50001,
107 tag: "test",
108 msg: "error with \"quotes\"".into(),
109 };
110 let (status, json) = error_response_json(&err);
111 assert_eq!(status, 500);
112 assert!(json.contains("error with"));
114 }
115
116 #[test]
117 fn test_error_response_text() {
118 let err = TestError {
119 code: 42211,
120 tag: "parse.oml",
121 msg: "syntax error".into(),
122 };
123 let (status, text) = error_response_text(&err);
124 assert_eq!(status, 422);
125 assert_eq!(text, "[parse.oml] 42211: syntax error");
126 }
127
128 #[test]
129 fn test_error_response_text_format() {
130 let err = TestError {
131 code: 50201,
132 tag: "source",
133 msg: "supplier failed".into(),
134 };
135 let (status, text) = error_response_text(&err);
136 assert_eq!(status, 502);
137 assert!(text.starts_with("[source]"));
138 assert!(text.contains("50201"));
139 assert!(text.contains("supplier failed"));
140 }
141
142 #[test]
143 fn test_error_response_serialize() {
144 let resp = ErrorResponse {
145 status: 404,
146 sys_code: 40401,
147 tag: "test",
148 message: "not found".into(),
149 };
150 let json = serde_json::to_string(&resp).unwrap();
151 assert!(json.contains("\"status\":404"));
152 assert!(json.contains("\"sys_code\":40401"));
153 }
154}