1use bytes::Bytes;
2use http::header::CONTENT_TYPE;
3use http::{Response, StatusCode};
4use http_body_util::Full;
5use ranvier_core::Outcome;
6
7pub type HttpResponse = Response<Full<Bytes>>;
8
9pub trait IntoResponse {
10 fn into_response(self) -> HttpResponse;
11}
12
13pub fn json_error_response(status: StatusCode, message: impl Into<String>) -> HttpResponse {
14 let payload = serde_json::json!({ "error": message.into() });
15 Response::builder()
16 .status(status)
17 .header(CONTENT_TYPE, "application/json")
18 .body(Full::new(Bytes::from(payload.to_string())))
19 .expect("response builder should be infallible")
20}
21
22impl IntoResponse for HttpResponse {
23 fn into_response(self) -> HttpResponse {
24 self
25 }
26}
27
28impl IntoResponse for String {
29 fn into_response(self) -> HttpResponse {
30 Response::builder()
31 .status(StatusCode::OK)
32 .header(CONTENT_TYPE, "text/plain; charset=utf-8")
33 .body(Full::new(Bytes::from(self)))
34 .expect("response builder should be infallible")
35 }
36}
37
38impl IntoResponse for &'static str {
39 fn into_response(self) -> HttpResponse {
40 Response::builder()
41 .status(StatusCode::OK)
42 .header(CONTENT_TYPE, "text/plain; charset=utf-8")
43 .body(Full::new(Bytes::from(self)))
44 .expect("response builder should be infallible")
45 }
46}
47
48impl IntoResponse for Bytes {
49 fn into_response(self) -> HttpResponse {
50 Response::builder()
51 .status(StatusCode::OK)
52 .header(CONTENT_TYPE, "application/octet-stream")
53 .body(Full::new(self))
54 .expect("response builder should be infallible")
55 }
56}
57
58impl IntoResponse for serde_json::Value {
59 fn into_response(self) -> HttpResponse {
60 Response::builder()
61 .status(StatusCode::OK)
62 .header(CONTENT_TYPE, "application/json")
63 .body(Full::new(Bytes::from(self.to_string())))
64 .expect("response builder should be infallible")
65 }
66}
67
68impl IntoResponse for () {
69 fn into_response(self) -> HttpResponse {
70 Response::builder()
71 .status(StatusCode::NO_CONTENT)
72 .body(Full::new(Bytes::new()))
73 .expect("response builder should be infallible")
74 }
75}
76
77impl IntoResponse for (StatusCode, String) {
78 fn into_response(self) -> HttpResponse {
79 Response::builder()
80 .status(self.0)
81 .header(CONTENT_TYPE, "text/plain; charset=utf-8")
82 .body(Full::new(Bytes::from(self.1)))
83 .expect("response builder should be infallible")
84 }
85}
86
87impl IntoResponse for (StatusCode, &'static str) {
88 fn into_response(self) -> HttpResponse {
89 Response::builder()
90 .status(self.0)
91 .header(CONTENT_TYPE, "text/plain; charset=utf-8")
92 .body(Full::new(Bytes::from(self.1)))
93 .expect("response builder should be infallible")
94 }
95}
96
97impl IntoResponse for (StatusCode, Bytes) {
98 fn into_response(self) -> HttpResponse {
99 Response::builder()
100 .status(self.0)
101 .header(CONTENT_TYPE, "application/octet-stream")
102 .body(Full::new(self.1))
103 .expect("response builder should be infallible")
104 }
105}
106
107pub fn outcome_to_response<Out, E>(outcome: Outcome<Out, E>) -> HttpResponse
108where
109 Out: IntoResponse,
110 E: std::fmt::Debug,
111{
112 outcome_to_response_with_error(outcome, |error| {
113 (
114 StatusCode::INTERNAL_SERVER_ERROR,
115 format!("Error: {:?}", error),
116 )
117 .into_response()
118 })
119}
120
121pub fn outcome_to_response_with_error<Out, E, F>(
122 outcome: Outcome<Out, E>,
123 on_fault: F,
124) -> HttpResponse
125where
126 Out: IntoResponse,
127 F: FnOnce(&E) -> HttpResponse,
128{
129 match outcome {
130 Outcome::Next(output) => output.into_response(),
131 Outcome::Fault(error) => on_fault(&error),
132 _ => "OK".into_response(),
133 }
134}
135
136#[cfg(test)]
137mod tests {
138 use super::*;
139 use ranvier_core::Outcome;
140
141 #[test]
142 fn string_into_response_sets_200_and_text_body() {
143 let response = "hello".to_string().into_response();
144 assert_eq!(response.status(), StatusCode::OK);
145 }
146
147 #[test]
148 fn tuple_into_response_preserves_status_code() {
149 let response = (StatusCode::CREATED, "created").into_response();
150 assert_eq!(response.status(), StatusCode::CREATED);
151 }
152
153 #[test]
154 fn outcome_fault_maps_to_internal_server_error() {
155 let response = outcome_to_response::<String, &str>(Outcome::Fault("boom"));
156 assert_eq!(response.status(), StatusCode::INTERNAL_SERVER_ERROR);
157 }
158
159 #[test]
160 fn json_error_response_sets_json_content_type() {
161 let response = json_error_response(StatusCode::UNAUTHORIZED, "forbidden");
162 assert_eq!(response.status(), StatusCode::UNAUTHORIZED);
163 assert_eq!(
164 response
165 .headers()
166 .get(CONTENT_TYPE)
167 .and_then(|value| value.to_str().ok()),
168 Some("application/json")
169 );
170 }
171}