1use std::borrow::Cow;
2
3use proc_macro2::Ident;
4use proc_macro_error::abort;
5use regex::{Captures, Regex};
6use syn::{parse::Parse, punctuated::Punctuated, token::Comma, ItemFn, LitStr};
7
8use crate::{
9 component::{TypeTree, ValueType},
10 ext::ArgValue,
11 path::PathOperation,
12};
13
14use super::{
15 fn_arg::{self, FnArg},
16 ArgumentIn, ArgumentResolver, MacroArg, MacroPath, PathOperationResolver, PathOperations,
17 PathResolver, ResolvedOperation, ValueArgument,
18};
19
20impl ArgumentResolver for PathOperations {
21 fn resolve_arguments(
22 fn_args: &Punctuated<syn::FnArg, Comma>,
23 macro_args: Option<Vec<MacroArg>>,
24 _: String,
25 ) -> (
26 Option<Vec<super::ValueArgument<'_>>>,
27 Option<Vec<super::IntoParamsType<'_>>>,
28 Option<super::RequestBody<'_>>,
29 ) {
30 let (into_params_args, value_args): (Vec<FnArg>, Vec<FnArg>) =
31 fn_arg::get_fn_args(fn_args).partition(fn_arg::is_into_params);
32
33 if let Some(macro_args) = macro_args {
34 let (primitive_args, body) = split_path_args_and_request(value_args);
35
36 (
37 Some(
38 macro_args
39 .into_iter()
40 .zip(primitive_args)
41 .map(into_value_argument)
42 .collect(),
43 ),
44 Some(
45 into_params_args
46 .into_iter()
47 .flat_map(fn_arg::with_parameter_in)
48 .map(Into::into)
49 .collect(),
50 ),
51 body.into_iter().next().map(Into::into),
52 )
53 } else {
54 let (_, body) = split_path_args_and_request(value_args);
55 (
56 None,
57 Some(
58 into_params_args
59 .into_iter()
60 .flat_map(fn_arg::with_parameter_in)
61 .map(Into::into)
62 .collect(),
63 ),
64 body.into_iter().next().map(Into::into),
65 )
66 }
67 }
68}
69
70fn split_path_args_and_request(
71 value_args: Vec<FnArg>,
72) -> (
73 impl Iterator<Item = TypeTree>,
74 impl Iterator<Item = TypeTree>,
75) {
76 let (path_args, body_types): (Vec<FnArg>, Vec<FnArg>) = value_args
77 .into_iter()
78 .filter(|arg| {
79 arg.ty.is("Path") || arg.ty.is("Json") || arg.ty.is("Form") || arg.ty.is("Bytes")
80 })
81 .partition(|arg| arg.ty.is("Path"));
82
83 (
84 path_args
85 .into_iter()
86 .flat_map(|path_arg| {
87 path_arg
88 .ty
89 .children
90 .expect("Path argument must have children")
91 })
92 .flat_map(|path_arg| match path_arg.value_type {
93 ValueType::Primitive => vec![path_arg],
94 ValueType::Tuple => path_arg
95 .children
96 .expect("ValueType::Tuple will always have children"),
97 ValueType::Object | ValueType::Value => {
98 unreachable!("Value arguments does not have ValueType::Object arguments")
99 }
100 }),
101 body_types.into_iter().map(|json| json.ty),
102 )
103}
104
105fn into_value_argument((macro_arg, primitive_arg): (MacroArg, TypeTree)) -> ValueArgument {
106 ValueArgument {
107 name: match macro_arg {
108 MacroArg::Path(path) => Some(Cow::Owned(path.name)),
109 },
110 type_tree: Some(primitive_arg),
111 argument_in: ArgumentIn::Path,
112 }
113}
114
115impl PathOperationResolver for PathOperations {
116 fn resolve_operation(item_fn: &ItemFn) -> Option<ResolvedOperation> {
117 item_fn.attrs.iter().find_map(|attribute| {
118 if is_valid_request_type(attribute.path().get_ident()) {
119 match attribute.parse_args::<Path>() {
120 Ok(path) => Some(ResolvedOperation {
121 path: path.0,
122 path_operation: PathOperation::from_ident(
123 attribute.path().get_ident().unwrap(),
124 ),
125 body: String::new(),
126 }),
127 Err(error) => abort!(
128 error.span(),
129 "parse path of path operation attribute: {}",
130 error
131 ),
132 }
133 } else {
134 None
135 }
136 })
137 }
138}
139
140struct Path(String);
141
142impl Parse for Path {
143 fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
144 let path = input.parse::<LitStr>()?.value();
145
146 input.step(|cursor| {
148 let mut rest = *cursor;
149 while let Some((_, next)) = rest.token_tree() {
150 rest = next;
151 }
152 Ok(((), rest))
153 })?;
154
155 Ok(Self(path))
156 }
157}
158
159impl PathResolver for PathOperations {
160 fn resolve_path(path: &Option<String>) -> Option<MacroPath> {
161 path.as_ref().map(|path| {
162 let regex = Regex::new(r"\{[a-zA-Z0-9_][^{}]*}").unwrap();
163
164 let mut args = Vec::<MacroArg>::with_capacity(regex.find_iter(path).count());
165 MacroPath {
166 path: regex
167 .replace_all(path, |captures: &Captures| {
168 let mut capture = &captures[0];
169 let original_name = String::from(capture);
170
171 if capture.contains("_:") {
172 args.push(MacroArg::Path(ArgValue {
174 name: String::from("arg0"),
175 original_name,
176 }));
177 "{arg0}".to_string()
178 } else if let Some(colon) = capture.find(':') {
179 capture = &capture[1..colon];
181
182 args.push(MacroArg::Path(ArgValue {
183 name: String::from(capture),
184 original_name,
185 }));
186
187 format!("{{{capture}}}")
188 } else {
189 args.push(MacroArg::Path(ArgValue {
190 name: String::from(&capture[1..capture.len() - 1]),
191 original_name,
192 }));
193 capture.to_string()
195 }
196 })
197 .to_string(),
198 args,
199 }
200 })
201 }
202}
203
204#[inline]
205fn is_valid_request_type(ident: Option<&Ident>) -> bool {
206 matches!(ident, Some(operation) if ["get", "post", "put", "delete", "head", "connect", "options", "trace", "patch"]
207 .iter().any(|expected_operation| operation == expected_operation))
208}