systemprompt_api/routes/managed/contract/
mod.rs1mod bounds;
6use axum::Json;
7use axum::extract::Request;
8use axum::http::{StatusCode, header};
9use axum::middleware::Next;
10use axum::response::{IntoResponse, Response};
11use serde::Serialize;
12
13#[derive(Debug, Clone, Serialize)]
15pub struct Problem {
16 #[serde(rename = "type")]
17 pub kind: String,
18 pub title: String,
19 pub status: u16,
20 pub detail: String,
21}
22
23pub(crate) fn problem(status: StatusCode, detail: impl Into<String>) -> Response {
24 (
25 status,
26 [
27 (header::CONTENT_TYPE, "application/problem+json"),
28 (header::CACHE_CONTROL, "no-store"),
29 ],
30 Json(Problem {
31 kind: "about:blank".to_owned(),
32 title: status
33 .canonical_reason()
34 .unwrap_or("Request failed")
35 .to_owned(),
36 status: status.as_u16(),
37 detail: detail.into(),
38 }),
39 )
40 .into_response()
41}
42pub async fn normalize(request: Request, next: Next) -> Response {
43 let request = match bounds::validate(request).await {
44 Ok(request) => request,
45 Err(response) => return response,
46 };
47 let mut response = next.run(request).await;
48 if (response.status().is_client_error() || response.status().is_server_error())
49 && response
50 .headers()
51 .get(header::CONTENT_TYPE)
52 .and_then(|value| value.to_str().ok())
53 .is_none_or(|value| !value.starts_with("application/problem+json"))
54 {
55 let status = response.status();
56 let mut normalized = problem(
57 status,
58 match status {
59 StatusCode::BAD_REQUEST | StatusCode::UNPROCESSABLE_ENTITY => {
60 "Invalid request body, query or path parameters"
61 },
62 StatusCode::UNAUTHORIZED => "Valid authentication is required",
63 StatusCode::FORBIDDEN => "This identity or browser origin is not authorized",
64 StatusCode::NOT_FOUND => "The requested resource is unavailable",
65 StatusCode::TOO_MANY_REQUESTS => {
66 "Request limit reached; retry after the indicated delay"
67 },
68 _ => "The operation could not be completed",
69 },
70 );
71 for name in [header::WWW_AUTHENTICATE, header::RETRY_AFTER] {
72 if let Some(value) = response.headers().get(&name) {
73 normalized.headers_mut().insert(name, value.clone());
74 }
75 }
76 return normalized;
77 }
78 response.headers_mut().insert(
79 header::CACHE_CONTROL,
80 http::HeaderValue::from_static("no-store"),
81 );
82 response
83}