atagen

jelq

public

an http based roguelike rpg about jelqing

Star0
Watch0
27256acf27800b63fd950b95ecc55b2a2486c1c5
1
ssh://brewery@pls.lobotomise.me:33/atagen/jelq.git

Blame: src/problem.rs

a9b016e4b248atagen2026-10-10 08:12 UTC1
use axum::{
a9b016e4b248atagen2026-10-10 08:12 UTC2
    Json,
a9b016e4b248atagen2026-10-10 08:12 UTC3
    http::{HeaderMap, HeaderName, HeaderValue, StatusCode, header},
a9b016e4b248atagen2026-10-10 08:12 UTC4
    response::{IntoResponse, Response},
a9b016e4b248atagen2026-10-10 08:12 UTC5
};
a9b016e4b248atagen2026-10-10 08:12 UTC6
use serde_json::{Map, Value, json};
a9b016e4b248atagen2026-10-10 08:12 UTC7
a9b016e4b248atagen2026-10-10 08:12 UTC8
pub const PROBLEM_BASE: &str = "https://jelq.dev/problems/";
a9b016e4b248atagen2026-10-10 08:12 UTC9
a9b016e4b248atagen2026-10-10 08:12 UTC10
/// An RFC 9457 problem document.
a9b016e4b248atagen2026-10-10 08:12 UTC11
pub struct Problem(Box<Inner>);
a9b016e4b248atagen2026-10-10 08:12 UTC12
a9b016e4b248atagen2026-10-10 08:12 UTC13
struct Inner {
a9b016e4b248atagen2026-10-10 08:12 UTC14
    status: StatusCode,
a9b016e4b248atagen2026-10-10 08:12 UTC15
    slug: &'static str,
a9b016e4b248atagen2026-10-10 08:12 UTC16
    detail: String,
a9b016e4b248atagen2026-10-10 08:12 UTC17
    extensions: Map<String, Value>,
a9b016e4b248atagen2026-10-10 08:12 UTC18
    headers: HeaderMap,
a9b016e4b248atagen2026-10-10 08:12 UTC19
}
a9b016e4b248atagen2026-10-10 08:12 UTC20
a9b016e4b248atagen2026-10-10 08:12 UTC21
impl Problem {
a9b016e4b248atagen2026-10-10 08:12 UTC22
    pub fn new(status: StatusCode, slug: &'static str, detail: impl Into<String>) -> Self {
a9b016e4b248atagen2026-10-10 08:12 UTC23
        Self(Box::new(Inner {
a9b016e4b248atagen2026-10-10 08:12 UTC24
            status,
a9b016e4b248atagen2026-10-10 08:12 UTC25
            slug,
a9b016e4b248atagen2026-10-10 08:12 UTC26
            detail: detail.into(),
a9b016e4b248atagen2026-10-10 08:12 UTC27
            extensions: Map::new(),
a9b016e4b248atagen2026-10-10 08:12 UTC28
            headers: HeaderMap::new(),
a9b016e4b248atagen2026-10-10 08:12 UTC29
        }))
a9b016e4b248atagen2026-10-10 08:12 UTC30
    }
a9b016e4b248atagen2026-10-10 08:12 UTC31
27256acf2780atagen2026-10-14 13:42 UTC32
    pub fn slug(&self) -> &'static str {
27256acf2780atagen2026-10-14 13:42 UTC33
        self.0.slug
27256acf2780atagen2026-10-14 13:42 UTC34
    }
27256acf2780atagen2026-10-14 13:42 UTC35
dfa38cf4cd4batagen2026-10-10 09:17 UTC36
    pub fn status(&self) -> StatusCode {
dfa38cf4cd4batagen2026-10-10 09:17 UTC37
        self.0.status
dfa38cf4cd4batagen2026-10-10 09:17 UTC38
    }
dfa38cf4cd4batagen2026-10-10 09:17 UTC39
a9b016e4b248atagen2026-10-10 08:12 UTC40
    pub fn with(mut self, key: &str, value: impl Into<Value>) -> Self {
a9b016e4b248atagen2026-10-10 08:12 UTC41
        self.0.extensions.insert(key.into(), value.into());
a9b016e4b248atagen2026-10-10 08:12 UTC42
        self
a9b016e4b248atagen2026-10-10 08:12 UTC43
    }
a9b016e4b248atagen2026-10-10 08:12 UTC44
98432b6057aaatagen2026-10-14 09:03 UTC45
    pub fn document(&self) -> Map<String, Value> {
98432b6057aaatagen2026-10-14 09:03 UTC46
        let problem = &self.0;
98432b6057aaatagen2026-10-14 09:03 UTC47
        let mut body = Map::new();
98432b6057aaatagen2026-10-14 09:03 UTC48
        body.insert(
98432b6057aaatagen2026-10-14 09:03 UTC49
            "type".into(),
98432b6057aaatagen2026-10-14 09:03 UTC50
            json!(format!("{PROBLEM_BASE}{}", problem.slug)),
98432b6057aaatagen2026-10-14 09:03 UTC51
        );
98432b6057aaatagen2026-10-14 09:03 UTC52
        body.insert("title".into(), json!(reason(problem.status)));
98432b6057aaatagen2026-10-14 09:03 UTC53
        body.insert("status".into(), json!(problem.status.as_u16()));
98432b6057aaatagen2026-10-14 09:03 UTC54
        body.insert("detail".into(), json!(problem.detail));
98432b6057aaatagen2026-10-14 09:03 UTC55
        body.extend(problem.extensions.clone());
98432b6057aaatagen2026-10-14 09:03 UTC56
        body
98432b6057aaatagen2026-10-14 09:03 UTC57
    }
98432b6057aaatagen2026-10-14 09:03 UTC58
a9b016e4b248atagen2026-10-10 08:12 UTC59
    pub fn header(mut self, name: HeaderName, value: impl AsRef<str>) -> Self {
782069860236atagen2026-10-10 12:44 UTC60
        if let Ok(value) = HeaderValue::from_str(value.as_ref()) {
782069860236atagen2026-10-10 12:44 UTC61
            self.0.headers.append(name, value);
782069860236atagen2026-10-10 12:44 UTC62
        }
a9b016e4b248atagen2026-10-10 08:12 UTC63
        self
a9b016e4b248atagen2026-10-10 08:12 UTC64
    }
a9b016e4b248atagen2026-10-10 08:12 UTC65
}
a9b016e4b248atagen2026-10-10 08:12 UTC66
a9b016e4b248atagen2026-10-10 08:12 UTC67
// The http crate still uses the RFC 7231 names for these.
99524cf9f17catagen2026-10-10 10:53 UTC68
pub fn reason(status: StatusCode) -> &'static str {
a9b016e4b248atagen2026-10-10 08:12 UTC69
    match status.as_u16() {
a9b016e4b248atagen2026-10-10 08:12 UTC70
        413 => "Content Too Large",
a9b016e4b248atagen2026-10-10 08:12 UTC71
        422 => "Unprocessable Content",
a9b016e4b248atagen2026-10-10 08:12 UTC72
        _ => status.canonical_reason().unwrap_or("Unknown"),
a9b016e4b248atagen2026-10-10 08:12 UTC73
    }
a9b016e4b248atagen2026-10-10 08:12 UTC74
}
a9b016e4b248atagen2026-10-10 08:12 UTC75
a9b016e4b248atagen2026-10-10 08:12 UTC76
impl IntoResponse for Problem {
a9b016e4b248atagen2026-10-10 08:12 UTC77
    fn into_response(self) -> Response {
98432b6057aaatagen2026-10-14 09:03 UTC78
        let body = self.document();
a9b016e4b248atagen2026-10-10 08:12 UTC79
        let problem = *self.0;
a9b016e4b248atagen2026-10-10 08:12 UTC80
        let mut response = (problem.status, Json(body)).into_response();
a9b016e4b248atagen2026-10-10 08:12 UTC81
        let headers = response.headers_mut();
a9b016e4b248atagen2026-10-10 08:12 UTC82
        headers.insert(
a9b016e4b248atagen2026-10-10 08:12 UTC83
            header::CONTENT_TYPE,
a9b016e4b248atagen2026-10-10 08:12 UTC84
            HeaderValue::from_static("application/problem+json"),
a9b016e4b248atagen2026-10-10 08:12 UTC85
        );
a9b016e4b248atagen2026-10-10 08:12 UTC86
        for (name, value) in &problem.headers {
a9b016e4b248atagen2026-10-10 08:12 UTC87
            headers.append(name, value.clone());
a9b016e4b248atagen2026-10-10 08:12 UTC88
        }
a9b016e4b248atagen2026-10-10 08:12 UTC89
        response
a9b016e4b248atagen2026-10-10 08:12 UTC90
    }
a9b016e4b248atagen2026-10-10 08:12 UTC91
}