1use crate::body::Body;
11use crate::error::ApiError;
12use http::request::Parts;
13use serde::de::DeserializeOwned;
14use serde_json::{Value, json};
15use siderite_openapi::{
16 Operation, Parameter, ParameterLocation, Schema, SchemaObject, SchemaRegistry,
17};
18use siderite_validation::{
19 Validate, ValidationContext, ValidationError, parse_value, text_pairs_to_value,
20};
21use std::future::Future;
22
23pub use crate::response::Json;
24
25pub type Request = http::Request<Body>;
27
28pub trait FromRequestParts: Sized + Send {
30 const BACKGROUND_TASKS: bool = false;
33
34 fn from_request_parts(parts: &mut Parts)
36 -> impl Future<Output = Result<Self, ApiError>> + Send;
37
38 fn describe(_op: &mut Operation, _registry: &mut SchemaRegistry) {}
40}
41
42pub trait FromRequest: Sized + Send {
44 const BACKGROUND_TASKS: bool = false;
46
47 fn from_request(req: Request) -> impl Future<Output = Result<Self, ApiError>> + Send;
49
50 fn describe(_op: &mut Operation, _registry: &mut SchemaRegistry) {}
52}
53
54impl<T: FromRequestParts> FromRequest for T {
55 const BACKGROUND_TASKS: bool = T::BACKGROUND_TASKS;
56
57 fn from_request(req: Request) -> impl Future<Output = Result<Self, ApiError>> + Send {
58 let (mut parts, _body) = req.into_parts();
59 async move { T::from_request_parts(&mut parts).await }
60 }
61
62 fn describe(op: &mut Operation, registry: &mut SchemaRegistry) {
63 <T as FromRequestParts>::describe(op, registry);
64 }
65}
66
67impl<T: FromRequestParts> FromRequestParts for Option<T> {
72 const BACKGROUND_TASKS: bool = T::BACKGROUND_TASKS;
73
74 async fn from_request_parts(parts: &mut Parts) -> Result<Self, ApiError> {
75 match T::from_request_parts(parts).await {
76 Ok(value) => Ok(Some(value)),
77 Err(error) if error.is_absent() => Ok(None),
78 Err(error) => Err(error),
79 }
80 }
81
82 fn describe(op: &mut Operation, registry: &mut SchemaRegistry) {
86 let before = op.parameters.len();
87 let had_body = op.request_body.is_some();
88 let security_before = std::mem::take(&mut op.security);
89 <T as FromRequestParts>::describe(op, registry);
90 let added = std::mem::replace(&mut op.security, security_before);
91 if !added.is_empty() {
92 let base = if op.security.is_empty() {
93 vec![std::collections::BTreeMap::new()]
94 } else {
95 op.security.clone()
96 };
97 let mut alternatives = base.clone();
98 for alternative in &base {
99 for requirement in &added {
100 let mut with = alternative.clone();
101 with.extend(requirement.clone());
102 if !alternatives.contains(&with) {
103 alternatives.push(with);
104 }
105 }
106 }
107 op.security = alternatives;
108 }
109 for p in &mut op.parameters[before..] {
110 if p.location != ParameterLocation::Path {
112 p.required = false;
113 }
114 }
115 if !had_body && let Some(body) = &mut op.request_body {
116 body.required = false;
117 }
118 }
119}
120
121pub(crate) fn invalid(location: &str, code: &str, message: impl Into<String>) -> ApiError {
123 ApiError::unprocessable(json!([{
124 "location": [location],
125 "code": code,
126 "message": message.into(),
127 }]))
128}
129
130pub(crate) fn validation_problem(error: &ValidationError) -> ApiError {
132 ApiError::unprocessable(serde_json::to_value(&error.errors).unwrap_or(Value::Null))
133}
134
135pub(crate) fn validated<T>(input: Value, ctx: ValidationContext) -> Result<T, ApiError>
137where
138 T: DeserializeOwned + Validate,
139{
140 parse_value(input, ctx).map_err(|e| validation_problem(&e))
141}
142
143pub(crate) fn urlencoded_to_value(text: &str) -> Result<Value, serde_urlencoded::de::Error> {
145 serde_urlencoded::from_str::<Vec<(String, String)>>(text).map(text_pairs_to_value)
146}
147
148#[derive(Debug, Clone, Copy, PartialEq, Eq)]
152pub struct Path<T>(pub T);
153
154impl<T> FromRequestParts for Path<T>
155where
156 T: DeserializeOwned + Send + Schema + Validate + 'static,
157{
158 async fn from_request_parts(parts: &mut Parts) -> Result<Self, ApiError> {
159 use axum::extract::FromRequestParts as _;
160 use axum::extract::rejection::PathRejection;
161 match axum::extract::Path::<T>::from_request_parts(parts, &()).await {
162 Ok(axum::extract::Path(value)) => {
163 let mut ctx = ValidationContext::for_text().at_root("path");
164 value.validate(&mut ctx);
165 if ctx.has_errors() {
166 Err(validation_problem(&ctx.take_errors()))
167 } else {
168 Ok(Path(value))
169 }
170 }
171 Err(PathRejection::FailedToDeserializePathParams(err)) => {
172 Err(invalid("path", "path_invalid", err.body_text()))
173 }
174 Err(other) => Err(ApiError::internal(other)),
175 }
176 }
177
178 fn describe(op: &mut Operation, registry: &mut SchemaRegistry) {
179 let schema = T::schema(registry);
180 if let Some(props) = object_properties(&schema) {
181 for (name, prop, _) in props {
182 op.add_parameter(Parameter::new(name, ParameterLocation::Path, true, prop));
183 }
184 } else if let Some(Value::Array(items)) = schema.get("prefixItems") {
185 for item in items {
186 let item = serde_json::from_value(item.clone()).unwrap_or_default();
187 op.add_parameter(Parameter::new("", ParameterLocation::Path, true, item));
188 }
189 } else {
190 op.add_parameter(Parameter::new("", ParameterLocation::Path, true, schema));
191 }
192 }
193}
194
195#[derive(Debug, Clone, Copy, PartialEq, Eq)]
197pub struct Query<T>(pub T);
198
199impl<T> FromRequestParts for Query<T>
200where
201 T: DeserializeOwned + Send + Schema + Validate + 'static,
202{
203 async fn from_request_parts(parts: &mut Parts) -> Result<Self, ApiError> {
204 let query = parts.uri.query().unwrap_or_default();
205 let input = urlencoded_to_value(query)
206 .map_err(|e| invalid("query", "query_invalid", e.to_string()))?;
207 validated(input, ValidationContext::for_text().at_root("query"))
208 .map(Query)
209 .map_err(|e| if query.is_empty() { e.absent() } else { e })
210 }
211
212 fn describe(op: &mut Operation, registry: &mut SchemaRegistry) {
213 let schema = T::schema(registry);
214 for (name, prop, required) in object_properties(&schema).unwrap_or_default() {
215 op.add_parameter(Parameter::new(
216 name,
217 ParameterLocation::Query,
218 required,
219 prop,
220 ));
221 }
222 }
223}
224
225pub(crate) fn object_properties(
227 schema: &SchemaObject,
228) -> Option<Vec<(String, SchemaObject, bool)>> {
229 let Value::Object(props) = schema.get("properties")? else {
230 return None;
231 };
232 let required: Vec<&str> = schema
233 .get("required")
234 .and_then(Value::as_array)
235 .map(|r| r.iter().filter_map(Value::as_str).collect())
236 .unwrap_or_default();
237 Some(
238 props
239 .iter()
240 .map(|(name, prop)| {
241 let prop = serde_json::from_value(prop.clone()).unwrap_or_default();
242 (name.clone(), prop, required.contains(&name.as_str()))
243 })
244 .collect(),
245 )
246}
247
248impl<T> FromRequest for Json<T>
249where
250 T: DeserializeOwned + Send + Schema + Validate + 'static,
251{
252 async fn from_request(req: Request) -> Result<Self, ApiError> {
253 use axum::extract::FromRequest as _;
254 use axum::extract::rejection::JsonRejection;
255 let input = match axum::Json::<Value>::from_request(req.map(Body::into_inner), &()).await {
258 Ok(axum::Json(value)) => value,
259 Err(rejection) => {
260 let message = rejection.body_text();
261 return Err(match rejection {
262 JsonRejection::JsonDataError(_) | JsonRejection::JsonSyntaxError(_) => {
263 invalid("body", "json_invalid", message)
264 }
265 JsonRejection::MissingJsonContentType(_) => {
266 ApiError::new(http::StatusCode::UNSUPPORTED_MEDIA_TYPE, message)
267 }
268 other => ApiError::new(other.status(), message),
269 });
270 }
271 };
272 validated(input, ValidationContext::new().at_root("body")).map(Json)
273 }
274
275 fn describe(op: &mut Operation, registry: &mut SchemaRegistry) {
276 let schema = registry.subschema::<T>();
277 op.set_request_body("application/json", schema, true);
278 }
279}
280
281#[derive(Debug)]
283pub struct RawRequest(pub Request);
284
285impl FromRequest for RawRequest {
286 async fn from_request(req: Request) -> Result<Self, ApiError> {
287 Ok(RawRequest(req))
288 }
289}
290
291impl FromRequestParts for http::Method {
293 async fn from_request_parts(parts: &mut Parts) -> Result<Self, ApiError> {
294 Ok(parts.method.clone())
295 }
296}
297
298impl FromRequestParts for http::Uri {
300 async fn from_request_parts(parts: &mut Parts) -> Result<Self, ApiError> {
301 Ok(parts.uri.clone())
302 }
303}
304
305impl FromRequestParts for http::HeaderMap {
307 async fn from_request_parts(parts: &mut Parts) -> Result<Self, ApiError> {
308 Ok(parts.headers.clone())
309 }
310}