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