siderite_core/
response.rs1use crate::body::Body;
8use crate::error::ApiError;
9use http::{HeaderValue, StatusCode, header};
10use siderite_openapi::{Operation, Schema, SchemaRegistry};
11use siderite_validation::{Dump, DumpOptions};
12
13pub type Response = http::Response<Body>;
15
16pub const SUCCESS: &str = "200";
18
19pub trait IntoResponse {
21 fn into_response(self) -> Response;
23
24 fn describe(_op: &mut Operation, _registry: &mut SchemaRegistry) {}
26}
27
28#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
30pub struct Json<T>(pub T);
31
32#[derive(Debug, Clone, PartialEq, Eq)]
34pub struct Html<T>(pub T);
35
36#[derive(Debug, Clone, PartialEq, Eq)]
38pub struct PlainText<T>(pub T);
39
40#[derive(Debug, Clone, Copy, PartialEq, Eq)]
42pub struct NoContent;
43
44#[derive(Debug, Clone, Copy)]
52pub struct WithStatus<R>(pub StatusCode, pub R);
53
54pub(crate) fn with_content_type(content_type: &'static str, body: impl Into<Body>) -> Response {
56 let mut response = Response::new(body.into());
57 response
58 .headers_mut()
59 .insert(header::CONTENT_TYPE, HeaderValue::from_static(content_type));
60 response
61}
62
63pub(crate) fn status_only(status: StatusCode) -> Response {
65 let mut response = Response::new(Body::empty());
66 *response.status_mut() = status;
67 response
68}
69
70impl IntoResponse for Response {
71 fn into_response(self) -> Response {
72 self
73 }
74}
75
76fn dump_response<T: Dump + ?Sized>(value: &T, opts: &DumpOptions) -> Response {
78 let value = siderite_validation::dump::DumpSerialize(value, opts);
79 match serde_json::to_vec(&value) {
80 Ok(bytes) => with_content_type("application/json", bytes),
81 Err(err) => ApiError::internal(err).into_response(),
82 }
83}
84
85#[derive(Debug, Clone)]
88pub struct JsonDump<T>(pub T, pub DumpOptions);
89
90impl<T: Dump + Schema + 'static> IntoResponse for JsonDump<T> {
91 fn into_response(self) -> Response {
92 dump_response(&self.0, &self.1)
93 }
94
95 fn describe(op: &mut Operation, registry: &mut SchemaRegistry) {
96 <Json<T> as IntoResponse>::describe(op, registry);
97 }
98}
99
100impl<T: Dump + Schema + 'static> IntoResponse for Json<T> {
101 fn into_response(self) -> Response {
103 dump_response(&self.0, &DumpOptions::default())
104 }
105
106 fn describe(op: &mut Operation, registry: &mut SchemaRegistry) {
107 let schema = registry.subschema::<T>();
108 op.add_response(
109 SUCCESS,
110 "Successful Response",
111 Some(("application/json", schema)),
112 );
113 }
114}
115
116macro_rules! text_like {
117 ($($ty:ident => $mime:literal),*) => {$(
118 impl<T: Into<String>> IntoResponse for $ty<T> {
119 fn into_response(self) -> Response {
120 with_content_type($mime, self.0.into())
121 }
122
123 fn describe(op: &mut Operation, registry: &mut SchemaRegistry) {
124 let schema = registry.subschema::<String>();
125 op.add_response(SUCCESS, "Successful Response", Some(($mime, schema)));
126 }
127 }
128 )*};
129}
130text_like!(Html => "text/html; charset=utf-8", PlainText => "text/plain; charset=utf-8");
131
132impl IntoResponse for &'static str {
133 fn into_response(self) -> Response {
134 PlainText(self).into_response()
135 }
136
137 fn describe(op: &mut Operation, registry: &mut SchemaRegistry) {
138 <PlainText<String> as IntoResponse>::describe(op, registry);
139 }
140}
141
142impl IntoResponse for String {
143 fn into_response(self) -> Response {
144 PlainText(self).into_response()
145 }
146
147 fn describe(op: &mut Operation, registry: &mut SchemaRegistry) {
148 <PlainText<String> as IntoResponse>::describe(op, registry);
149 }
150}
151
152impl IntoResponse for () {
153 fn into_response(self) -> Response {
154 status_only(StatusCode::OK)
155 }
156
157 fn describe(op: &mut Operation, _: &mut SchemaRegistry) {
158 op.add_response(SUCCESS, "Successful Response", None);
159 }
160}
161
162impl IntoResponse for StatusCode {
163 fn into_response(self) -> Response {
164 status_only(self)
165 }
166}
167
168impl IntoResponse for NoContent {
169 fn into_response(self) -> Response {
170 status_only(StatusCode::NO_CONTENT)
171 }
172
173 fn describe(op: &mut Operation, _: &mut SchemaRegistry) {
174 op.add_response("204", "No Content", None);
175 }
176}
177
178impl<R: IntoResponse> IntoResponse for WithStatus<R> {
179 fn into_response(self) -> Response {
180 let mut response = self.1.into_response();
181 *response.status_mut() = self.0;
182 response
183 }
184
185 fn describe(op: &mut Operation, registry: &mut SchemaRegistry) {
186 R::describe(op, registry);
187 }
188}
189
190impl<R: IntoResponse> IntoResponse for (StatusCode, R) {
194 fn into_response(self) -> Response {
195 WithStatus(self.0, self.1).into_response()
196 }
197
198 fn describe(op: &mut Operation, registry: &mut SchemaRegistry) {
199 R::describe(op, registry);
200 }
201}
202
203impl<T: IntoResponse, E: IntoResponse> IntoResponse for Result<T, E> {
204 fn into_response(self) -> Response {
205 match self {
206 Ok(v) => v.into_response(),
207 Err(e) => e.into_response(),
208 }
209 }
210
211 fn describe(op: &mut Operation, registry: &mut SchemaRegistry) {
212 T::describe(op, registry);
213 E::describe(op, registry);
214 }
215}