1use std::borrow::Cow;
2
3#[cfg(feature = "rocket_extras")]
4use std::cmp::Ordering;
5
6use proc_macro2::TokenStream;
7use quote::{quote, quote_spanned, ToTokens};
8use syn::parse_quote;
9use syn::spanned::Spanned;
10use syn::{punctuated::Punctuated, token::Comma, ItemFn};
11
12use crate::component::{ComponentSchema, ComponentSchemaProps, TypeTree};
13use crate::path::{PathOperation, PathTypeTree};
14
15#[cfg(feature = "auto_into_responses")]
16pub mod auto_types;
17
18#[cfg(feature = "actix_extras")]
19pub mod actix;
20
21#[cfg(feature = "axum_extras")]
22pub mod axum;
23
24#[cfg(feature = "rocket_extras")]
25pub mod rocket;
26
27#[cfg_attr(
29 not(any(
30 feature = "actix_extras",
31 feature = "rocket_extras",
32 feature = "axum_extras"
33 )),
34 allow(dead_code)
35)]
36#[cfg_attr(feature = "debug", derive(Debug))]
37pub struct ValueArgument<'a> {
38 pub name: Option<Cow<'a, str>>,
39 #[cfg(any(
40 feature = "actix_extras",
41 feature = "rocket_extras",
42 feature = "axum_extras"
43 ))]
44 pub argument_in: ArgumentIn,
45 pub type_tree: Option<TypeTree<'a>>,
46}
47
48#[cfg(feature = "actix_extras")]
49impl<'v> From<(MacroArg, TypeTree<'v>)> for ValueArgument<'v> {
50 fn from((macro_arg, primitive_arg): (MacroArg, TypeTree<'v>)) -> Self {
51 Self {
52 name: match macro_arg {
53 MacroArg::Path(path) => Some(Cow::Owned(path.name)),
54 },
55 type_tree: Some(primitive_arg),
56 argument_in: ArgumentIn::Path,
57 }
58 }
59}
60
61#[cfg_attr(
62 not(any(
63 feature = "actix_extras",
64 feature = "rocket_extras",
65 feature = "axum_extras"
66 )),
67 allow(dead_code)
68)]
69#[cfg_attr(feature = "debug", derive(Debug))]
72pub struct IntoParamsType<'a> {
73 pub parameter_in_provider: TokenStream,
74 pub type_path: Option<Cow<'a, syn::Path>>,
75}
76
77impl<'i> From<(Option<Cow<'i, syn::Path>>, TokenStream)> for IntoParamsType<'i> {
78 fn from((type_path, parameter_in_provider): (Option<Cow<'i, syn::Path>>, TokenStream)) -> Self {
79 IntoParamsType {
80 parameter_in_provider,
81 type_path,
82 }
83 }
84}
85
86#[cfg(any(
87 feature = "actix_extras",
88 feature = "rocket_extras",
89 feature = "axum_extras"
90))]
91#[cfg_attr(feature = "debug", derive(Debug))]
92#[derive(PartialEq, Eq)]
93pub enum ArgumentIn {
94 Path,
95 #[cfg(feature = "rocket_extras")]
96 Query,
97}
98
99#[cfg_attr(feature = "debug", derive(Debug))]
100pub struct RequestBody<'r> {
101 ty: TypeTree<'r>,
102}
103
104impl<'t> From<TypeTree<'t>> for RequestBody<'t> {
105 fn from(value: TypeTree<'t>) -> RequestBody<'t> {
106 Self { ty: value }
107 }
108}
109
110impl ToTokens for RequestBody<'_> {
111 fn to_tokens(&self, tokens: &mut TokenStream) {
112 let mut actual_body = get_actual_body_type(&self.ty).unwrap().clone();
113
114 if let Some(option) = find_option_type_tree(&self.ty) {
115 let path = option.path.clone();
116 actual_body = TypeTree {
117 children: Some(vec![actual_body]),
118 generic_type: Some(crate::component::GenericType::Option),
119 value_type: crate::component::ValueType::Object,
120 span: Some(path.span()),
121 path,
122 }
123 };
124
125 let required = if actual_body.is_option() {
126 quote!(utoipa::openapi::Required::False)
127 } else {
128 quote!(utoipa::openapi::Required::True)
129 };
130
131 let mut create_body_tokens = |content_type: &str, actual_body: &TypeTree| {
132 let schema = ComponentSchema::new(ComponentSchemaProps {
133 type_tree: actual_body,
134 features: None,
135 description: None,
136 deprecated: None,
137 object_name: "",
138 });
139
140 tokens.extend(quote_spanned! {actual_body.span.unwrap()=>
141 utoipa::openapi::request_body::RequestBodyBuilder::new()
142 .content(#content_type,
143 utoipa::openapi::content::Content::new(#schema)
144 )
145 .required(Some(#required))
146 .description(Some(""))
147 .build()
148 })
149 };
150
151 if self.ty.is("Bytes") {
152 let bytes_as_bytes_vec = parse_quote!(Vec<u8>);
153 let ty = TypeTree::from_type(&bytes_as_bytes_vec);
154 create_body_tokens("application/octet-stream", &ty);
155 } else if self.ty.is("Form") {
156 create_body_tokens("application/x-www-form-urlencoded", &actual_body);
157 } else {
158 create_body_tokens(actual_body.get_default_content_type(), &actual_body);
159 };
160 }
161}
162
163fn get_actual_body_type<'t>(ty: &'t TypeTree<'t>) -> Option<&'t TypeTree<'t>> {
164 ty.path
165 .as_deref()
166 .expect("RequestBody TypeTree must have syn::Path")
167 .segments
168 .iter()
169 .find_map(|segment| match &*segment.ident.to_string() {
170 "Json" => Some(
171 ty.children
172 .as_deref()
173 .expect("Json must have children")
174 .first()
175 .expect("Json must have one child"),
176 ),
177 "Form" => Some(
178 ty.children
179 .as_deref()
180 .expect("Form must have children")
181 .first()
182 .expect("Form must have one child"),
183 ),
184 "Option" => get_actual_body_type(
185 ty.children
186 .as_deref()
187 .expect("Option must have children")
188 .first()
189 .expect("Option must have one child"),
190 ),
191 "Bytes" => Some(ty),
192 _ => None,
193 })
194}
195
196fn find_option_type_tree<'t>(ty: &'t TypeTree) -> Option<&'t TypeTree<'t>> {
197 let eq = ty.generic_type == Some(crate::component::GenericType::Option);
198
199 if !eq {
200 ty.children
201 .as_ref()
202 .and_then(|children| children.iter().find_map(find_option_type_tree))
203 } else {
204 Some(ty)
205 }
206}
207
208#[cfg_attr(feature = "debug", derive(Debug))]
209pub struct MacroPath {
210 pub path: String,
211 pub args: Vec<MacroArg>,
212}
213
214#[cfg_attr(feature = "debug", derive(Debug))]
215pub enum MacroArg {
216 #[cfg_attr(
217 not(any(feature = "actix_extras", feature = "rocket_extras")),
218 allow(dead_code)
219 )]
220 Path(ArgValue),
221 #[cfg(feature = "rocket_extras")]
222 Query(ArgValue),
223}
224
225impl MacroArg {
226 #[cfg(feature = "rocket_extras")]
228 fn by_name(a: &MacroArg, b: &MacroArg) -> Ordering {
229 a.get_value().name.cmp(&b.get_value().name)
230 }
231
232 #[cfg(feature = "rocket_extras")]
233 fn get_value(&self) -> &ArgValue {
234 match self {
235 MacroArg::Path(path) => path,
236 MacroArg::Query(query) => query,
237 }
238 }
239}
240
241#[derive(PartialEq, Eq, PartialOrd, Ord)]
242#[cfg_attr(feature = "debug", derive(Debug))]
243pub struct ArgValue {
244 pub name: String,
245 pub original_name: String,
246}
247
248#[cfg_attr(feature = "debug", derive(Debug))]
249pub struct ResolvedOperation {
250 pub path_operation: PathOperation,
251 pub path: String,
252 pub body: String,
253}
254
255pub trait ArgumentResolver {
256 fn resolve_arguments(
257 _: &'_ Punctuated<syn::FnArg, Comma>,
258 _: Option<Vec<MacroArg>>,
259 _: String,
260 ) -> (
261 Option<Vec<ValueArgument<'_>>>,
262 Option<Vec<IntoParamsType<'_>>>,
263 Option<RequestBody<'_>>,
264 ) {
265 (None, None, None)
266 }
267}
268
269pub trait PathResolver {
270 fn resolve_path(_: &Option<String>) -> Option<MacroPath> {
271 None
272 }
273}
274
275pub trait PathOperationResolver {
276 fn resolve_operation(_: &ItemFn) -> Option<ResolvedOperation> {
277 None
278 }
279}
280
281pub struct PathOperations;
282
283#[cfg(not(any(
284 feature = "actix_extras",
285 feature = "rocket_extras",
286 feature = "axum_extras"
287)))]
288impl ArgumentResolver for PathOperations {}
289
290#[cfg(not(any(
291 feature = "actix_extras",
292 feature = "rocket_extras",
293 feature = "axum_extras"
294)))]
295impl PathResolver for PathOperations {}
296
297#[cfg(not(any(feature = "actix_extras", feature = "rocket_extras")))]
298impl PathOperationResolver for PathOperations {}
299
300#[cfg(any(
301 feature = "actix_extras",
302 feature = "axum_extras",
303 feature = "rocket_extras"
304))]
305pub mod fn_arg {
306
307 use proc_macro2::Ident;
308 use proc_macro_error::abort;
309 #[cfg(any(feature = "actix_extras", feature = "axum_extras"))]
310 use quote::quote;
311 use syn::PatStruct;
312 use syn::{punctuated::Punctuated, token::Comma, Pat, PatType};
313
314 use crate::component::TypeTree;
315 #[cfg(any(feature = "actix_extras", feature = "axum_extras"))]
316 use crate::component::ValueType;
317
318 #[cfg_attr(feature = "debug", derive(Debug))]
320 pub struct FnArg<'a> {
321 pub(super) ty: TypeTree<'a>,
322 pub(super) arg_type: FnArgType<'a>,
323 }
324
325 #[cfg_attr(feature = "debug", derive(Debug))]
326 #[derive(PartialEq, Eq, PartialOrd, Ord)]
327 pub enum FnArgType<'t> {
328 Single(&'t Ident),
329 Destructed(Vec<&'t Ident>),
330 }
331
332 impl FnArgType<'_> {
333 #[cfg(feature = "rocket_extras")]
336 pub(super) fn get_name(&self) -> &Ident {
337 match self {
338 Self::Single(ident) => ident,
339 Self::Destructed(tuple) => tuple
341 .first()
342 .expect("Expected at least one argument in FnArgType::Tuple"),
343 }
344 }
345 }
346
347 impl<'a> From<(TypeTree<'a>, FnArgType<'a>)> for FnArg<'a> {
348 fn from((ty, arg_type): (TypeTree<'a>, FnArgType<'a>)) -> Self {
349 Self { ty, arg_type }
350 }
351 }
352
353 impl<'a> Ord for FnArg<'a> {
354 fn cmp(&self, other: &Self) -> std::cmp::Ordering {
355 self.arg_type.cmp(&other.arg_type)
356 }
357 }
358
359 impl<'a> PartialOrd for FnArg<'a> {
360 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
361 self.arg_type.partial_cmp(&other.arg_type)
362 }
363 }
364
365 impl<'a> PartialEq for FnArg<'a> {
366 fn eq(&self, other: &Self) -> bool {
367 self.ty == other.ty && self.arg_type == other.arg_type
368 }
369 }
370
371 impl<'a> Eq for FnArg<'a> {}
372
373 pub fn get_fn_args(fn_args: &Punctuated<syn::FnArg, Comma>) -> impl Iterator<Item = FnArg<'_>> {
374 fn_args
375 .iter()
376 .filter_map(|arg| {
377 let pat_type = get_fn_arg_pat_type(arg);
378
379 match pat_type.pat.as_ref() {
380 syn::Pat::Wild(_) => None,
381 _ => {
382 let arg_name = get_pat_fn_arg_type(pat_type.pat.as_ref());
383 Some((TypeTree::from_type(&pat_type.ty), arg_name))
384 }
385 }
386 })
387 .map(FnArg::from)
388 }
389
390 #[inline]
391 fn get_pat_fn_arg_type(pat: &Pat) -> FnArgType {
392 let arg_name = match pat {
393 syn::Pat::Ident(ident) => FnArgType::Single(&ident.ident),
394 syn::Pat::Tuple(tuple) => {
395 FnArgType::Destructed(tuple.elems.iter().map(|item| {
396 match item {
397 syn::Pat::Ident(ident) => &ident.ident,
398 _ => abort!(item, "expected syn::Ident in get_pat_fn_arg_type Pat::Tuple")
399 }
400 }).collect::<Vec<_>>())
401 },
402 syn::Pat::TupleStruct(tuple_struct) => {
403 get_pat_fn_arg_type(tuple_struct.elems.first().as_ref().expect(
404 "PatTuple expected to have at least one element, cannot get fn argument",
405 ))
406 },
407 syn::Pat::Struct(PatStruct { fields, ..}) => {
408 let idents = fields.iter()
409 .map(|field| get_pat_fn_arg_type(&field.pat))
410 .fold(Vec::<&'_ Ident>::new(), |mut idents, field_type| {
411 if let FnArgType::Single(ident) = field_type {
412 idents.push(ident)
413 }
414 idents
415 });
416
417 FnArgType::Destructed(idents)
418 }
419 _ => abort!(pat,
420 "unexpected syn::Pat, expected syn::Pat::Ident,in get_fn_args, cannot get fn argument name"
421 ),
422 };
423 arg_name
424 }
425
426 #[inline]
427 fn get_fn_arg_pat_type(fn_arg: &syn::FnArg) -> &PatType {
428 match fn_arg {
429 syn::FnArg::Typed(value) => value,
430 _ => abort!(fn_arg, "unexpected fn argument type, expected FnArg::Typed"),
431 }
432 }
433
434 #[cfg(any(feature = "actix_extras", feature = "axum_extras"))]
435 pub(super) fn with_parameter_in(
436 arg: FnArg<'_>,
437 ) -> Option<(
438 Option<std::borrow::Cow<'_, syn::Path>>,
439 proc_macro2::TokenStream,
440 )> {
441 let parameter_in_provider = if arg.ty.is("Path") {
442 quote! { || Some (utoipa::openapi::path::ParameterIn::Path) }
443 } else if arg.ty.is("Query") {
444 quote! { || Some(utoipa::openapi::path::ParameterIn::Query) }
445 } else {
446 quote! { || None }
447 };
448
449 let type_path = arg
450 .ty
451 .children
452 .expect("FnArg TypeTree generic type Path must have children")
453 .into_iter()
454 .next()
455 .unwrap()
456 .path;
457
458 Some((type_path, parameter_in_provider))
459 }
460
461 #[cfg(any(feature = "actix_extras", feature = "axum_extras"))]
463 pub(super) fn is_into_params(fn_arg: &FnArg) -> bool {
464 use crate::component::GenericType;
465 let mut ty = &fn_arg.ty;
466
467 if fn_arg.ty.generic_type == Some(GenericType::Option) {
468 ty = fn_arg
469 .ty
470 .children
471 .as_ref()
472 .expect("FnArg Option must have children")
473 .first()
474 .expect("FnArg Option must have 1 child");
475 }
476
477 (ty.is("Path") || ty.is("Query"))
478 && ty
479 .children
480 .as_ref()
481 .map(|children| {
482 children.iter().all(|child| {
483 matches!(child.value_type, ValueType::Object)
484 && matches!(child.generic_type, None)
485 })
486 })
487 .unwrap_or(false)
488 }
489}