Skip to main content

soma_http_server/
rejection.rs

1//! Generic rejection envelopes for routing-level failures: unmatched
2//! routes and disallowed methods. Rendered through `soma_http_api`'s
3//! structured error body so any HTTP surface that mounts these renders the
4//! same shape for the same failure class. Body-extraction rejections
5//! (malformed JSON, payload too large) are
6//! `soma_http_api::response::json_rejection_response` — an API-shape
7//! concern, not a transport concern, so they live there instead.
8//!
9//! [`not_found_handler`] is wired into `apps/soma`'s router `.fallback()`.
10//! [`method_not_allowed`] is available for a product router to wire up (via
11//! Axum's per-route method fallback) but is not currently mounted anywhere
12//! — Axum's `.fallback()` only intercepts unmatched *paths*, not
13//! matched-path/disallowed-method requests, so a consumer must opt in
14//! separately for 405s to use this envelope.
15
16use axum::{http::StatusCode, response::Response};
17use soma_http_api::problem::ErrorBody;
18
19/// Render a `404 Not Found` body: `{"error": "not_found"}`.
20pub fn not_found() -> Response {
21    ErrorBody::new("not_found").into_response_with_status(StatusCode::NOT_FOUND)
22}
23
24/// Axum `.fallback()` handler wrapping [`not_found`]. Mount with
25/// `router.fallback(not_found_handler)`.
26pub async fn not_found_handler() -> Response {
27    not_found()
28}
29
30/// Render a `405 Method Not Allowed` body:
31/// `{"error": "method_not_allowed"}`.
32pub fn method_not_allowed() -> Response {
33    ErrorBody::new("method_not_allowed").into_response_with_status(StatusCode::METHOD_NOT_ALLOWED)
34}
35
36#[cfg(test)]
37mod tests {
38    use super::*;
39
40    #[test]
41    fn not_found_is_404_with_stable_error_code() {
42        let response = not_found();
43        assert_eq!(response.status(), StatusCode::NOT_FOUND);
44    }
45
46    #[test]
47    fn method_not_allowed_is_405_with_stable_error_code() {
48        let response = method_not_allowed();
49        assert_eq!(response.status(), StatusCode::METHOD_NOT_ALLOWED);
50    }
51
52    #[tokio::test]
53    async fn not_found_handler_matches_not_found_body() {
54        use axum::body::to_bytes;
55
56        let direct = to_bytes(not_found().into_body(), usize::MAX).await.unwrap();
57        let via_handler = to_bytes(not_found_handler().await.into_body(), usize::MAX)
58            .await
59            .unwrap();
60        assert_eq!(direct, via_handler);
61    }
62}