durable_streams_server/protocol/
problem.rs1use axum::{
2 body::Body,
3 extract::OriginalUri,
4 http::{
5 HeaderMap, HeaderValue, StatusCode,
6 header::{CONTENT_TYPE, IntoHeaderName},
7 },
8 response::{IntoResponse, Response},
9};
10use serde::{Deserialize, Serialize};
11
12#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
17pub struct ProblemDetails {
18 #[serde(rename = "type")]
20 pub problem_type: String,
21 pub title: String,
23 pub status: u16,
25 pub code: String,
27 #[serde(skip_serializing_if = "Option::is_none")]
29 pub detail: Option<String>,
30 #[serde(skip_serializing_if = "Option::is_none")]
32 pub instance: Option<String>,
33}
34
35impl ProblemDetails {
36 #[must_use]
38 pub fn new(
39 problem_type: &'static str,
40 title: &'static str,
41 status: StatusCode,
42 code: &'static str,
43 ) -> Self {
44 Self {
45 problem_type: problem_type.to_string(),
46 title: title.to_string(),
47 status: status.as_u16(),
48 code: code.to_string(),
49 detail: None,
50 instance: None,
51 }
52 }
53
54 #[must_use]
56 pub fn with_detail(mut self, detail: impl Into<String>) -> Self {
57 self.detail = Some(detail.into());
58 self
59 }
60
61 #[must_use]
63 pub fn with_instance(mut self, instance: impl Into<String>) -> Self {
64 self.instance = Some(instance.into());
65 self
66 }
67}
68
69#[derive(Debug, Clone)]
71pub struct ProblemTelemetry {
72 pub problem_type: String,
73 pub code: String,
74 pub title: String,
75 pub detail: Option<String>,
76 pub error_class: Option<String>,
77 pub storage_backend: Option<String>,
78 pub storage_operation: Option<String>,
79 pub internal_detail: Option<String>,
80 pub retry_after_secs: Option<u32>,
81}
82
83impl From<&ProblemDetails> for ProblemTelemetry {
84 fn from(problem: &ProblemDetails) -> Self {
85 Self {
86 problem_type: problem.problem_type.clone(),
87 code: problem.code.clone(),
88 title: problem.title.clone(),
89 detail: problem.detail.clone(),
90 error_class: None,
91 storage_backend: None,
92 storage_operation: None,
93 internal_detail: None,
94 retry_after_secs: None,
95 }
96 }
97}
98
99#[derive(Debug, Clone)]
101pub struct ProblemResponse {
102 problem: ProblemDetails,
103 headers: HeaderMap,
104 telemetry: Option<ProblemTelemetry>,
105}
106
107pub type Result<T> = std::result::Result<T, ProblemResponse>;
109
110impl ProblemResponse {
111 #[must_use]
113 pub fn new(problem: ProblemDetails) -> Self {
114 Self {
115 problem,
116 headers: HeaderMap::new(),
117 telemetry: None,
118 }
119 }
120
121 #[must_use]
123 pub fn with_detail(mut self, detail: impl Into<String>) -> Self {
124 self.problem.detail = Some(detail.into());
125 self
126 }
127
128 #[must_use]
130 pub fn with_instance(mut self, instance: impl Into<String>) -> Self {
131 self.problem.instance = Some(instance.into());
132 self
133 }
134
135 #[must_use]
137 pub fn with_header<K>(mut self, key: K, value: HeaderValue) -> Self
138 where
139 K: IntoHeaderName,
140 {
141 self.headers.insert(key, value);
142 self
143 }
144
145 #[must_use]
147 pub fn with_telemetry(mut self, telemetry: ProblemTelemetry) -> Self {
148 self.telemetry = Some(telemetry);
149 self
150 }
151}
152
153#[must_use]
158pub fn request_instance(OriginalUri(uri): &OriginalUri) -> String {
159 uri.path_and_query()
160 .map(|value| value.as_str().to_string())
161 .unwrap_or_else(|| uri.path().to_string())
162}
163
164impl IntoResponse for ProblemResponse {
165 fn into_response(self) -> Response {
166 let status =
167 StatusCode::from_u16(self.problem.status).unwrap_or(StatusCode::INTERNAL_SERVER_ERROR);
168 let telemetry = self
169 .telemetry
170 .unwrap_or_else(|| ProblemTelemetry::from(&self.problem));
171 let body = match serde_json::to_vec(&self.problem) {
172 Ok(body) => body,
173 Err(_) => br#"{"type":"/errors/internal","title":"Internal Server Error","status":500,"code":"INTERNAL_ERROR"}"#.to_vec(),
174 };
175
176 let mut response = Response::new(Body::from(body));
177 *response.status_mut() = status;
178
179 let headers = response.headers_mut();
180 headers.insert(
181 CONTENT_TYPE,
182 HeaderValue::from_static("application/problem+json"),
183 );
184 headers.extend(self.headers);
185
186 response.extensions_mut().insert(telemetry);
187 response
188 }
189}