an http based roguelike rpg about jelqing
Blame: src/problem.rs
a9b016e4b248 | atagen | 2026-10-10 08:12 UTC | 1 | use axum::{ |
a9b016e4b248 | atagen | 2026-10-10 08:12 UTC | 2 | Json, |
a9b016e4b248 | atagen | 2026-10-10 08:12 UTC | 3 | http::{HeaderMap, HeaderName, HeaderValue, StatusCode, header}, |
a9b016e4b248 | atagen | 2026-10-10 08:12 UTC | 4 | response::{IntoResponse, Response}, |
a9b016e4b248 | atagen | 2026-10-10 08:12 UTC | 5 | }; |
a9b016e4b248 | atagen | 2026-10-10 08:12 UTC | 6 | use serde_json::{Map, Value, json}; |
a9b016e4b248 | atagen | 2026-10-10 08:12 UTC | 7 | |
a9b016e4b248 | atagen | 2026-10-10 08:12 UTC | 8 | pub const PROBLEM_BASE: &str = "https://jelq.dev/problems/"; |
a9b016e4b248 | atagen | 2026-10-10 08:12 UTC | 9 | |
a9b016e4b248 | atagen | 2026-10-10 08:12 UTC | 10 | /// An RFC 9457 problem document. |
a9b016e4b248 | atagen | 2026-10-10 08:12 UTC | 11 | pub struct Problem(Box<Inner>); |
a9b016e4b248 | atagen | 2026-10-10 08:12 UTC | 12 | |
a9b016e4b248 | atagen | 2026-10-10 08:12 UTC | 13 | struct Inner { |
a9b016e4b248 | atagen | 2026-10-10 08:12 UTC | 14 | status: StatusCode, |
a9b016e4b248 | atagen | 2026-10-10 08:12 UTC | 15 | slug: &'static str, |
a9b016e4b248 | atagen | 2026-10-10 08:12 UTC | 16 | detail: String, |
a9b016e4b248 | atagen | 2026-10-10 08:12 UTC | 17 | extensions: Map<String, Value>, |
a9b016e4b248 | atagen | 2026-10-10 08:12 UTC | 18 | headers: HeaderMap, |
a9b016e4b248 | atagen | 2026-10-10 08:12 UTC | 19 | } |
a9b016e4b248 | atagen | 2026-10-10 08:12 UTC | 20 | |
a9b016e4b248 | atagen | 2026-10-10 08:12 UTC | 21 | impl Problem { |
a9b016e4b248 | atagen | 2026-10-10 08:12 UTC | 22 | pub fn new(status: StatusCode, slug: &'static str, detail: impl Into<String>) -> Self { |
a9b016e4b248 | atagen | 2026-10-10 08:12 UTC | 23 | Self(Box::new(Inner { |
a9b016e4b248 | atagen | 2026-10-10 08:12 UTC | 24 | status, |
a9b016e4b248 | atagen | 2026-10-10 08:12 UTC | 25 | slug, |
a9b016e4b248 | atagen | 2026-10-10 08:12 UTC | 26 | detail: detail.into(), |
a9b016e4b248 | atagen | 2026-10-10 08:12 UTC | 27 | extensions: Map::new(), |
a9b016e4b248 | atagen | 2026-10-10 08:12 UTC | 28 | headers: HeaderMap::new(), |
a9b016e4b248 | atagen | 2026-10-10 08:12 UTC | 29 | })) |
a9b016e4b248 | atagen | 2026-10-10 08:12 UTC | 30 | } |
a9b016e4b248 | atagen | 2026-10-10 08:12 UTC | 31 | |
27256acf2780 | atagen | 2026-10-14 13:42 UTC | 32 | pub fn slug(&self) -> &'static str { |
27256acf2780 | atagen | 2026-10-14 13:42 UTC | 33 | self.0.slug |
27256acf2780 | atagen | 2026-10-14 13:42 UTC | 34 | } |
27256acf2780 | atagen | 2026-10-14 13:42 UTC | 35 | |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 36 | pub fn status(&self) -> StatusCode { |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 37 | self.0.status |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 38 | } |
dfa38cf4cd4b | atagen | 2026-10-10 09:17 UTC | 39 | |
a9b016e4b248 | atagen | 2026-10-10 08:12 UTC | 40 | pub fn with(mut self, key: &str, value: impl Into<Value>) -> Self { |
a9b016e4b248 | atagen | 2026-10-10 08:12 UTC | 41 | self.0.extensions.insert(key.into(), value.into()); |
a9b016e4b248 | atagen | 2026-10-10 08:12 UTC | 42 | self |
a9b016e4b248 | atagen | 2026-10-10 08:12 UTC | 43 | } |
a9b016e4b248 | atagen | 2026-10-10 08:12 UTC | 44 | |
98432b6057aa | atagen | 2026-10-14 09:03 UTC | 45 | pub fn document(&self) -> Map<String, Value> { |
98432b6057aa | atagen | 2026-10-14 09:03 UTC | 46 | let problem = &self.0; |
98432b6057aa | atagen | 2026-10-14 09:03 UTC | 47 | let mut body = Map::new(); |
98432b6057aa | atagen | 2026-10-14 09:03 UTC | 48 | body.insert( |
98432b6057aa | atagen | 2026-10-14 09:03 UTC | 49 | "type".into(), |
98432b6057aa | atagen | 2026-10-14 09:03 UTC | 50 | json!(format!("{PROBLEM_BASE}{}", problem.slug)), |
98432b6057aa | atagen | 2026-10-14 09:03 UTC | 51 | ); |
98432b6057aa | atagen | 2026-10-14 09:03 UTC | 52 | body.insert("title".into(), json!(reason(problem.status))); |
98432b6057aa | atagen | 2026-10-14 09:03 UTC | 53 | body.insert("status".into(), json!(problem.status.as_u16())); |
98432b6057aa | atagen | 2026-10-14 09:03 UTC | 54 | body.insert("detail".into(), json!(problem.detail)); |
98432b6057aa | atagen | 2026-10-14 09:03 UTC | 55 | body.extend(problem.extensions.clone()); |
98432b6057aa | atagen | 2026-10-14 09:03 UTC | 56 | body |
98432b6057aa | atagen | 2026-10-14 09:03 UTC | 57 | } |
98432b6057aa | atagen | 2026-10-14 09:03 UTC | 58 | |
a9b016e4b248 | atagen | 2026-10-10 08:12 UTC | 59 | pub fn header(mut self, name: HeaderName, value: impl AsRef<str>) -> Self { |
782069860236 | atagen | 2026-10-10 12:44 UTC | 60 | if let Ok(value) = HeaderValue::from_str(value.as_ref()) { |
782069860236 | atagen | 2026-10-10 12:44 UTC | 61 | self.0.headers.append(name, value); |
782069860236 | atagen | 2026-10-10 12:44 UTC | 62 | } |
a9b016e4b248 | atagen | 2026-10-10 08:12 UTC | 63 | self |
a9b016e4b248 | atagen | 2026-10-10 08:12 UTC | 64 | } |
a9b016e4b248 | atagen | 2026-10-10 08:12 UTC | 65 | } |
a9b016e4b248 | atagen | 2026-10-10 08:12 UTC | 66 | |
a9b016e4b248 | atagen | 2026-10-10 08:12 UTC | 67 | // The http crate still uses the RFC 7231 names for these. |
99524cf9f17c | atagen | 2026-10-10 10:53 UTC | 68 | pub fn reason(status: StatusCode) -> &'static str { |
a9b016e4b248 | atagen | 2026-10-10 08:12 UTC | 69 | match status.as_u16() { |
a9b016e4b248 | atagen | 2026-10-10 08:12 UTC | 70 | 413 => "Content Too Large", |
a9b016e4b248 | atagen | 2026-10-10 08:12 UTC | 71 | 422 => "Unprocessable Content", |
a9b016e4b248 | atagen | 2026-10-10 08:12 UTC | 72 | _ => status.canonical_reason().unwrap_or("Unknown"), |
a9b016e4b248 | atagen | 2026-10-10 08:12 UTC | 73 | } |
a9b016e4b248 | atagen | 2026-10-10 08:12 UTC | 74 | } |
a9b016e4b248 | atagen | 2026-10-10 08:12 UTC | 75 | |
a9b016e4b248 | atagen | 2026-10-10 08:12 UTC | 76 | impl IntoResponse for Problem { |
a9b016e4b248 | atagen | 2026-10-10 08:12 UTC | 77 | fn into_response(self) -> Response { |
98432b6057aa | atagen | 2026-10-14 09:03 UTC | 78 | let body = self.document(); |
a9b016e4b248 | atagen | 2026-10-10 08:12 UTC | 79 | let problem = *self.0; |
a9b016e4b248 | atagen | 2026-10-10 08:12 UTC | 80 | let mut response = (problem.status, Json(body)).into_response(); |
a9b016e4b248 | atagen | 2026-10-10 08:12 UTC | 81 | let headers = response.headers_mut(); |
a9b016e4b248 | atagen | 2026-10-10 08:12 UTC | 82 | headers.insert( |
a9b016e4b248 | atagen | 2026-10-10 08:12 UTC | 83 | header::CONTENT_TYPE, |
a9b016e4b248 | atagen | 2026-10-10 08:12 UTC | 84 | HeaderValue::from_static("application/problem+json"), |
a9b016e4b248 | atagen | 2026-10-10 08:12 UTC | 85 | ); |
a9b016e4b248 | atagen | 2026-10-10 08:12 UTC | 86 | for (name, value) in &problem.headers { |
a9b016e4b248 | atagen | 2026-10-10 08:12 UTC | 87 | headers.append(name, value.clone()); |
a9b016e4b248 | atagen | 2026-10-10 08:12 UTC | 88 | } |
a9b016e4b248 | atagen | 2026-10-10 08:12 UTC | 89 | response |
a9b016e4b248 | atagen | 2026-10-10 08:12 UTC | 90 | } |
a9b016e4b248 | atagen | 2026-10-10 08:12 UTC | 91 | } |