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