1use serde::{Deserialize, Serialize};
6
7#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
11#[non_exhaustive]
12pub struct ErrorResponse {
13 pub code: ErrorCode,
14 pub message: String,
15 #[serde(default, skip_serializing_if = "Vec::is_empty")]
20 pub detail: Vec<String>,
21}
22
23impl ErrorResponse {
24 pub fn new(code: ErrorCode, message: impl Into<String>) -> Self {
25 Self {
26 code,
27 message: message.into(),
28 detail: Vec::new(),
29 }
30 }
31
32 pub fn with_detail(mut self, detail: Vec<String>) -> Self {
33 self.detail = detail;
34 self
35 }
36}
37
38#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
42#[serde(rename_all = "snake_case")]
43#[non_exhaustive]
44pub enum ErrorCode {
45 BadRequest,
47 Unauthorized,
51 PayloadTooLarge,
55 UnsupportedTarget,
58 RenderFailed,
61 Internal,
63 Overloaded,
67 #[serde(other)]
70 Unknown,
71}
72
73#[cfg(test)]
74mod tests {
75 use super::*;
76
77 #[test]
78 fn error_response_round_trips_through_json() {
79 let err = ErrorResponse::new(ErrorCode::RenderFailed, "typst: unclosed delimiter");
80
81 let json = serde_json::to_string(&err).unwrap();
82 assert_eq!(
83 json,
84 r#"{"code":"render_failed","message":"typst: unclosed delimiter"}"#
85 );
86 assert_eq!(serde_json::from_str::<ErrorResponse>(&json).unwrap(), err);
87 }
88
89 #[test]
90 fn error_codes_serialize_snake_case() {
91 for (code, expected) in [
92 (ErrorCode::BadRequest, "\"bad_request\""),
93 (ErrorCode::Unauthorized, "\"unauthorized\""),
94 (ErrorCode::PayloadTooLarge, "\"payload_too_large\""),
95 (ErrorCode::UnsupportedTarget, "\"unsupported_target\""),
96 (ErrorCode::RenderFailed, "\"render_failed\""),
97 (ErrorCode::Internal, "\"internal\""),
98 (ErrorCode::Overloaded, "\"overloaded\""),
99 ] {
100 let json = serde_json::to_string(&code).unwrap();
101 assert_eq!(json, expected);
102 assert_eq!(serde_json::from_str::<ErrorCode>(&json).unwrap(), code);
103 }
104 }
105
106 #[test]
107 fn an_unknown_code_decodes_instead_of_failing_the_body() {
108 let err: ErrorResponse =
109 serde_json::from_str(r#"{"code":"from_the_future","message":"m"}"#).unwrap();
110 assert_eq!(err.code, ErrorCode::Unknown);
111 }
112
113 #[test]
114 fn detail_is_omitted_when_empty_and_carried_when_present() {
115 let with = ErrorResponse::new(ErrorCode::RenderFailed, "m")
116 .with_detail(vec!["main.typ:1:2: error: x".into()]);
117
118 let json = serde_json::to_string(&with).unwrap();
119 assert_eq!(
120 json,
121 r#"{"code":"render_failed","message":"m","detail":["main.typ:1:2: error: x"]}"#
122 );
123 assert_eq!(serde_json::from_str::<ErrorResponse>(&json).unwrap(), with);
124 let old: ErrorResponse =
125 serde_json::from_str(r#"{"code":"render_failed","message":"m"}"#).unwrap();
126 assert!(old.detail.is_empty());
127 }
128}