wasm_runner_sdk/
response.rs1use crate::abi;
4use serde::Serialize;
5
6#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
8pub struct StatusCode(u16);
9
10impl StatusCode {
11 pub const OK: StatusCode = StatusCode(200);
13 pub const CREATED: StatusCode = StatusCode(201);
14 pub const ACCEPTED: StatusCode = StatusCode(202);
15 pub const NO_CONTENT: StatusCode = StatusCode(204);
16
17 pub const MOVED_PERMANENTLY: StatusCode = StatusCode(301);
19 pub const FOUND: StatusCode = StatusCode(302);
20 pub const SEE_OTHER: StatusCode = StatusCode(303);
21 pub const NOT_MODIFIED: StatusCode = StatusCode(304);
22 pub const TEMPORARY_REDIRECT: StatusCode = StatusCode(307);
23 pub const PERMANENT_REDIRECT: StatusCode = StatusCode(308);
24
25 pub const BAD_REQUEST: StatusCode = StatusCode(400);
27 pub const UNAUTHORIZED: StatusCode = StatusCode(401);
28 pub const FORBIDDEN: StatusCode = StatusCode(403);
29 pub const NOT_FOUND: StatusCode = StatusCode(404);
30 pub const METHOD_NOT_ALLOWED: StatusCode = StatusCode(405);
31 pub const CONFLICT: StatusCode = StatusCode(409);
32 pub const GONE: StatusCode = StatusCode(410);
33 pub const UNPROCESSABLE_ENTITY: StatusCode = StatusCode(422);
34 pub const TOO_MANY_REQUESTS: StatusCode = StatusCode(429);
35
36 pub const INTERNAL_SERVER_ERROR: StatusCode = StatusCode(500);
38 pub const NOT_IMPLEMENTED: StatusCode = StatusCode(501);
39 pub const BAD_GATEWAY: StatusCode = StatusCode(502);
40 pub const SERVICE_UNAVAILABLE: StatusCode = StatusCode(503);
41 pub const GATEWAY_TIMEOUT: StatusCode = StatusCode(504);
42
43 pub const fn from_u16(code: u16) -> Self {
45 StatusCode(code)
46 }
47
48 pub const fn as_u16(&self) -> u16 {
50 self.0
51 }
52
53 pub const fn is_success(&self) -> bool {
55 self.0 >= 200 && self.0 < 300
56 }
57
58 pub const fn is_redirection(&self) -> bool {
60 self.0 >= 300 && self.0 < 400
61 }
62
63 pub const fn is_client_error(&self) -> bool {
65 self.0 >= 400 && self.0 < 500
66 }
67
68 pub const fn is_server_error(&self) -> bool {
70 self.0 >= 500 && self.0 < 600
71 }
72}
73
74impl From<u16> for StatusCode {
75 fn from(code: u16) -> Self {
76 StatusCode(code)
77 }
78}
79
80impl std::fmt::Display for StatusCode {
81 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
82 write!(f, "{}", self.0)
83 }
84}
85
86#[derive(Debug, Clone)]
88pub struct Response {
89 status: StatusCode,
90 headers: Vec<(String, String)>,
91 body: Vec<u8>,
92}
93
94impl Response {
95 pub fn new(status: StatusCode) -> Self {
97 Self {
98 status,
99 headers: Vec::new(),
100 body: Vec::new(),
101 }
102 }
103
104 pub fn ok() -> Self {
106 Self::new(StatusCode::OK)
107 }
108
109 pub fn status(status: impl Into<StatusCode>) -> Self {
111 Self::new(status.into())
112 }
113
114 pub fn with_status(mut self, status: impl Into<StatusCode>) -> Self {
116 self.status = status.into();
117 self
118 }
119
120 pub fn with_header(mut self, name: impl Into<String>, value: impl Into<String>) -> Self {
122 self.headers.push((name.into(), value.into()));
123 self
124 }
125
126 pub fn with_content_type(self, content_type: impl Into<String>) -> Self {
128 self.with_header("content-type", content_type)
129 }
130
131 pub fn with_body(mut self, body: impl Into<Vec<u8>>) -> Self {
133 self.body = body.into();
134 self
135 }
136
137 pub fn with_text(self, text: impl Into<String>) -> Self {
139 let text = text.into();
140 self.with_content_type("text/plain; charset=utf-8")
141 .with_body(text.into_bytes())
142 }
143
144 pub fn with_html(self, html: impl Into<String>) -> Self {
146 let html = html.into();
147 self.with_content_type("text/html; charset=utf-8")
148 .with_body(html.into_bytes())
149 }
150
151 pub fn with_json<T: Serialize>(self, value: &T) -> Result<Self, serde_json::Error> {
153 let json = serde_json::to_vec(value)?;
154 Ok(self
155 .with_content_type("application/json")
156 .with_body(json))
157 }
158
159 pub fn with_cookie(
161 mut self,
162 name: impl Into<String>,
163 value: impl Into<String>,
164 options: Option<&str>,
165 ) -> Self {
166 let name = name.into();
167 let value = value.into();
168 let cookie_value = match options {
169 Some(opts) => format!("{}={}; {}", name, value, opts),
170 None => format!("{}={}", name, value),
171 };
172 self.headers.push(("set-cookie".to_string(), cookie_value));
173 self
174 }
175
176 pub fn status_code(&self) -> StatusCode {
178 self.status
179 }
180
181 pub fn headers(&self) -> &[(String, String)] {
183 &self.headers
184 }
185
186 pub fn body(&self) -> &[u8] {
188 &self.body
189 }
190
191 pub fn send(self) {
193 unsafe {
194 abi::response_create(self.status.0 as i32);
195
196 for (name, value) in &self.headers {
197 abi::response_set_header(
198 name.as_ptr(),
199 name.len() as i32,
200 value.as_ptr(),
201 value.len() as i32,
202 );
203 }
204
205 if !self.body.is_empty() {
206 abi::response_write_body(self.body.as_ptr(), self.body.len() as i32);
207 }
208
209 abi::response_send();
210 }
211 }
212}
213
214pub trait IntoResponse {
219 fn into_response(self) -> Response;
221}
222
223impl IntoResponse for Response {
225 fn into_response(self) -> Response {
226 self
227 }
228}
229
230impl IntoResponse for String {
232 fn into_response(self) -> Response {
233 Response::ok().with_text(self)
234 }
235}
236
237impl IntoResponse for &str {
238 fn into_response(self) -> Response {
239 Response::ok().with_text(self)
240 }
241}
242
243impl IntoResponse for Vec<u8> {
245 fn into_response(self) -> Response {
246 Response::ok()
247 .with_content_type("application/octet-stream")
248 .with_body(self)
249 }
250}
251
252impl IntoResponse for &[u8] {
253 fn into_response(self) -> Response {
254 Response::ok()
255 .with_content_type("application/octet-stream")
256 .with_body(self.to_vec())
257 }
258}
259
260impl IntoResponse for StatusCode {
262 fn into_response(self) -> Response {
263 Response::new(self)
264 }
265}
266
267impl<T: IntoResponse> IntoResponse for (StatusCode, T) {
269 fn into_response(self) -> Response {
270 let (status, body) = self;
271 body.into_response().with_status(status)
272 }
273}
274
275impl<T: IntoResponse> IntoResponse for (StatusCode, Vec<(String, String)>, T) {
277 fn into_response(self) -> Response {
278 let (status, headers, body) = self;
279 let mut response = body.into_response().with_status(status);
280 for (name, value) in headers {
281 response = response.with_header(name, value);
282 }
283 response
284 }
285}
286
287impl<T: IntoResponse, E: IntoResponse> IntoResponse for Result<T, E> {
289 fn into_response(self) -> Response {
290 match self {
291 Ok(v) => v.into_response(),
292 Err(e) => e.into_response(),
293 }
294 }
295}
296
297impl<T: IntoResponse> IntoResponse for Option<T> {
299 fn into_response(self) -> Response {
300 match self {
301 Some(v) => v.into_response(),
302 None => Response::new(StatusCode::NOT_FOUND).with_text("Not Found"),
303 }
304 }
305}
306
307impl IntoResponse for () {
309 fn into_response(self) -> Response {
310 Response::new(StatusCode::OK)
311 }
312}
313
314#[derive(Debug, Clone)]
318pub struct Json<T>(pub T);
319
320impl<T: Serialize> IntoResponse for Json<T> {
321 fn into_response(self) -> Response {
322 match serde_json::to_vec(&self.0) {
323 Ok(body) => Response::ok()
324 .with_content_type("application/json")
325 .with_body(body),
326 Err(e) => Response::new(StatusCode::INTERNAL_SERVER_ERROR)
327 .with_text(format!("JSON serialization error: {}", e)),
328 }
329 }
330}
331
332#[derive(Debug, Clone)]
334pub struct Html<T>(pub T);
335
336impl<T: Into<String>> IntoResponse for Html<T> {
337 fn into_response(self) -> Response {
338 Response::ok().with_html(self.0)
339 }
340}
341
342#[derive(Debug, Clone)]
344pub struct Redirect {
345 status: StatusCode,
346 location: String,
347}
348
349impl Redirect {
350 pub fn to(location: impl Into<String>) -> Self {
352 Self {
353 status: StatusCode::FOUND,
354 location: location.into(),
355 }
356 }
357
358 pub fn permanent(location: impl Into<String>) -> Self {
360 Self {
361 status: StatusCode::MOVED_PERMANENTLY,
362 location: location.into(),
363 }
364 }
365
366 pub fn see_other(location: impl Into<String>) -> Self {
368 Self {
369 status: StatusCode::SEE_OTHER,
370 location: location.into(),
371 }
372 }
373
374 pub fn temporary(location: impl Into<String>) -> Self {
376 Self {
377 status: StatusCode::TEMPORARY_REDIRECT,
378 location: location.into(),
379 }
380 }
381}
382
383impl IntoResponse for Redirect {
384 fn into_response(self) -> Response {
385 Response::new(self.status).with_header("location", self.location)
386 }
387}