rustack_apigatewayv2_http/
response.rs1use bytes::Bytes;
7use rustack_apigatewayv2_model::error::ApiGatewayV2Error;
8use serde::Serialize;
9
10pub const CONTENT_TYPE: &str = "application/json";
12
13pub fn error_to_response(
23 error: &ApiGatewayV2Error,
24) -> Result<http::Response<Bytes>, ApiGatewayV2Error> {
25 let body_obj = serde_json::json!({
26 "message": error.message,
27 });
28 let body_bytes = serde_json::to_vec(&body_obj).map_err(|e| {
29 ApiGatewayV2Error::internal_error(format!("Failed to serialize error response: {e}"))
30 })?;
31
32 http::Response::builder()
33 .status(error.status_code)
34 .header("content-type", CONTENT_TYPE)
35 .body(Bytes::from(body_bytes))
36 .map_err(|e| {
37 ApiGatewayV2Error::internal_error(format!("Failed to build error response: {e}"))
38 })
39}
40
41pub fn json_response(
47 status: u16,
48 body: &impl Serialize,
49) -> Result<http::Response<Bytes>, ApiGatewayV2Error> {
50 let body_bytes = serde_json::to_vec(body).map_err(|e| {
51 ApiGatewayV2Error::internal_error(format!("Failed to serialize response: {e}"))
52 })?;
53 http::Response::builder()
54 .status(status)
55 .header("content-type", CONTENT_TYPE)
56 .body(Bytes::from(body_bytes))
57 .map_err(|e| ApiGatewayV2Error::internal_error(format!("Failed to build response: {e}")))
58}
59
60pub fn empty_response(status: u16) -> Result<http::Response<Bytes>, ApiGatewayV2Error> {
66 http::Response::builder()
67 .status(status)
68 .body(Bytes::new())
69 .map_err(|e| {
70 ApiGatewayV2Error::internal_error(format!("Failed to build empty response: {e}"))
71 })
72}
73
74#[cfg(test)]
75mod tests {
76 use rustack_apigatewayv2_model::error::ApiGatewayV2ErrorCode;
77
78 use super::*;
79
80 #[test]
81 fn test_should_format_error_with_lowercase_message() {
82 let err = ApiGatewayV2Error::with_message(
83 ApiGatewayV2ErrorCode::NotFoundException,
84 "API not found: abc123",
85 );
86 let resp = error_to_response(&err).expect("should build");
87 assert_eq!(resp.status(), http::StatusCode::NOT_FOUND);
88 assert_eq!(
89 resp.headers()
90 .get("content-type")
91 .expect("has content-type"),
92 CONTENT_TYPE,
93 );
94
95 let parsed: serde_json::Value =
96 serde_json::from_slice(resp.body()).expect("valid JSON body");
97 assert_eq!(parsed["message"], "API not found: abc123");
98 assert!(parsed.get("Type").is_none());
100 }
101
102 #[test]
103 fn test_should_format_bad_request_error() {
104 let err = ApiGatewayV2Error::with_message(
105 ApiGatewayV2ErrorCode::BadRequestException,
106 "Invalid input",
107 );
108 let resp = error_to_response(&err).expect("should build");
109 assert_eq!(resp.status(), http::StatusCode::BAD_REQUEST);
110
111 let parsed: serde_json::Value =
112 serde_json::from_slice(resp.body()).expect("valid JSON body");
113 assert_eq!(parsed["message"], "Invalid input");
114 }
115
116 #[test]
117 fn test_should_build_json_success_response() {
118 let body = serde_json::json!({"apiId": "abc123", "name": "my-api"});
119 let resp = json_response(201, &body).expect("should build");
120 assert_eq!(resp.status().as_u16(), 201);
121 assert_eq!(
122 resp.headers()
123 .get("content-type")
124 .expect("has content-type"),
125 CONTENT_TYPE,
126 );
127 assert!(!resp.body().is_empty());
128 }
129
130 #[test]
131 fn test_should_build_empty_response() {
132 let resp = empty_response(204).expect("should build");
133 assert_eq!(resp.status().as_u16(), 204);
134 assert!(resp.body().is_empty());
135 }
136
137 #[test]
138 fn test_should_use_application_json_content_type() {
139 assert_eq!(CONTENT_TYPE, "application/json");
140 }
141
142 #[test]
143 fn test_should_map_all_error_codes_to_correct_status() {
144 let cases = [
145 (ApiGatewayV2ErrorCode::BadRequestException, 400),
146 (ApiGatewayV2ErrorCode::AccessDeniedException, 403),
147 (ApiGatewayV2ErrorCode::NotFoundException, 404),
148 (ApiGatewayV2ErrorCode::ConflictException, 409),
149 (ApiGatewayV2ErrorCode::TooManyRequestsException, 500),
150 (ApiGatewayV2ErrorCode::UnknownOperation, 500),
151 ];
152 for (code, expected_status) in cases {
153 let err = ApiGatewayV2Error::with_message(code, "test");
154 let resp = error_to_response(&err).expect("should build");
155 assert_eq!(
156 resp.status().as_u16(),
157 expected_status,
158 "wrong status for {code:?}",
159 );
160 }
161 }
162
163 #[test]
164 fn test_should_not_include_type_field_in_error() {
165 let err =
166 ApiGatewayV2Error::with_message(ApiGatewayV2ErrorCode::AccessDeniedException, "denied");
167 let resp = error_to_response(&err).expect("should build");
168 let parsed: serde_json::Value = serde_json::from_slice(resp.body()).expect("valid JSON");
169 assert!(parsed.get("Type").is_none());
170 assert!(parsed.get("__type").is_none());
171 assert_eq!(parsed["message"], "denied");
172 }
173}