1use std::borrow::Cow;
2
3use proc_macro2::TokenStream;
4use proc_macro_error::abort;
5use quote::{quote, ToTokens};
6use syn::{
7 parse::Parse, punctuated::Punctuated, token::Comma, Attribute, Data, Field, Generics, Ident,
8};
9
10use crate::{
11 component::{
12 self,
13 features::{
14 self, AdditionalProperties, AllowReserved, Example, ExclusiveMaximum, ExclusiveMinimum,
15 Explode, Format, Inline, MaxItems, MaxLength, Maximum, MinItems, MinLength, Minimum,
16 MultipleOf, Names, Nullable, Pattern, ReadOnly, Rename, RenameAll, SchemaWith, Style,
17 WriteOnly, XmlAttr,
18 },
19 FieldRename,
20 },
21 doc_comment::CommentAttributes,
22 Array, Required, ResultExt,
23};
24
25use super::{
26 features::{
27 impl_into_inner, impl_merge, parse_features, pop_feature, pop_feature_as_inner, Feature,
28 FeaturesExt, IntoInner, Merge, ToTokensExt,
29 },
30 serde::{self, SerdeContainer, SerdeValue},
31 ComponentSchema, TypeTree,
32};
33
34impl_merge!(IntoParamsFeatures, FieldFeatures);
35
36pub struct IntoParamsFeatures(Vec<Feature>);
38
39impl Parse for IntoParamsFeatures {
40 fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
41 Ok(Self(parse_features!(
42 input as Style,
43 features::ParameterIn,
44 Names,
45 RenameAll
46 )))
47 }
48}
49
50impl_into_inner!(IntoParamsFeatures);
51
52#[cfg_attr(feature = "debug", derive(Debug))]
53pub struct IntoParams {
54 pub attrs: Vec<Attribute>,
56 pub generics: Generics,
58 pub data: Data,
60 pub ident: Ident,
62}
63
64impl ToTokens for IntoParams {
65 fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
66 let ident = &self.ident;
67 let (impl_generics, ty_generics, where_clause) = self.generics.split_for_impl();
68
69 let mut into_params_features = self
70 .attrs
71 .iter()
72 .filter(|attr| attr.path().is_ident("into_params"))
73 .map(|attribute| {
74 attribute
75 .parse_args::<IntoParamsFeatures>()
76 .unwrap_or_abort()
77 .into_inner()
78 })
79 .reduce(|acc, item| acc.merge(item));
80 let serde_container = serde::parse_container(&self.attrs);
81
82 if self.attrs.iter().any(|attr| attr.path().is_ident("param")) {
84 abort! {
85 ident,
86 "found `param` attribute in unsupported context";
87 help = "Did you mean `into_params`?",
88 }
89 }
90
91 let names = into_params_features.as_mut().and_then(|features| {
92 features
93 .pop_by(|feature| matches!(feature, Feature::IntoParamsNames(_)))
94 .and_then(|feature| match feature {
95 Feature::IntoParamsNames(names) => Some(names.into_values()),
96 _ => None,
97 })
98 });
99
100 let style = pop_feature!(into_params_features => Feature::Style(_));
101 let parameter_in = pop_feature!(into_params_features => Feature::ParameterIn(_));
102 let rename_all = pop_feature!(into_params_features => Feature::RenameAll(_));
103
104 let params = self
105 .get_struct_fields(&names.as_ref())
106 .enumerate()
107 .filter_map(|(index, field)| {
108 let field_params = serde::parse_value(&field.attrs);
109 if matches!(&field_params, Some(params) if !params.skip) {
110 Some((index, field, field_params))
111 } else {
112 None
113 }
114 })
115 .map(|(index, field, field_serde_params)| {
116 Param {
117 field,
118 field_serde_params,
119 container_attributes: FieldParamContainerAttributes {
120 rename_all: rename_all.as_ref().and_then(|feature| {
121 match feature {
122 Feature::RenameAll(rename_all) => Some(rename_all),
123 _ => None
124 }
125 }),
126 style: &style,
127 parameter_in: ¶meter_in,
128 name: names.as_ref()
129 .map(|names| names.get(index).unwrap_or_else(|| abort!(
130 ident,
131 "There is no name specified in the names(...) container attribute for tuple struct field {}",
132 index
133 ))),
134 },
135 serde_container: serde_container.as_ref(),
136 }
137 })
138 .collect::<Array<Param>>();
139
140 tokens.extend(quote! {
141 impl #impl_generics utoipa::IntoParams for #ident #ty_generics #where_clause {
142 fn into_params(parameter_in_provider: impl Fn() -> Option<utoipa::openapi::path::ParameterIn>) -> Vec<utoipa::openapi::path::Parameter> {
143 #params.to_vec()
144 }
145 }
146 });
147 }
148}
149
150impl IntoParams {
151 fn get_struct_fields(
152 &self,
153 field_names: &Option<&Vec<String>>,
154 ) -> impl Iterator<Item = &Field> {
155 let ident = &self.ident;
156 let abort = |note: &str| {
157 abort! {
158 ident,
159 "unsupported data type, expected struct with named fields `struct {} {{...}}` or unnamed fields `struct {}(...)`",
160 ident.to_string(),
161 ident.to_string();
162 note = note
163 }
164 };
165
166 match &self.data {
167 Data::Struct(data_struct) => match &data_struct.fields {
168 syn::Fields::Named(named_fields) => {
169 if field_names.is_some() {
170 abort! {ident, "`#[into_params(names(...))]` is not supported attribute on a struct with named fields"}
171 }
172 named_fields.named.iter()
173 }
174 syn::Fields::Unnamed(unnamed_fields) => {
175 self.validate_unnamed_field_names(&unnamed_fields.unnamed, field_names);
176 unnamed_fields.unnamed.iter()
177 }
178 _ => abort("Unit type struct is not supported"),
179 },
180 _ => abort("Only struct type is supported"),
181 }
182 }
183
184 fn validate_unnamed_field_names(
185 &self,
186 unnamed_fields: &Punctuated<Field, Comma>,
187 field_names: &Option<&Vec<String>>,
188 ) {
189 let ident = &self.ident;
190 match field_names {
191 Some(names) => {
192 if names.len() != unnamed_fields.len() {
193 abort! {
194 ident,
195 "declared names amount '{}' does not match to the unnamed fields amount '{}' in type: {}",
196 names.len(), unnamed_fields.len(), ident;
197 help = r#"Did you forget to add a field name to `#[into_params(names(... , "field_name"))]`"#;
198 help = "Or have you added extra name but haven't defined a type?"
199 }
200 }
201 }
202 None => {
203 abort! {
204 ident,
205 "struct with unnamed fields must have explicit name declarations.";
206 help = "Try defining `#[into_params(names(...))]` over your type: {}", ident,
207 }
208 }
209 }
210 }
211}
212
213#[cfg_attr(feature = "debug", derive(Debug))]
214pub struct FieldParamContainerAttributes<'a> {
215 style: &'a Option<Feature>,
217 name: Option<&'a String>,
219 parameter_in: &'a Option<Feature>,
221 rename_all: Option<&'a RenameAll>,
223}
224
225struct FieldFeatures(Vec<Feature>);
226
227impl_into_inner!(FieldFeatures);
228
229impl Parse for FieldFeatures {
230 fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
231 Ok(Self(parse_features!(
232 input as component::features::ValueType,
234 Rename,
235 Style,
236 AllowReserved,
237 Example,
238 Explode,
239 SchemaWith,
240 component::features::Required,
241 Inline,
243 Format,
244 component::features::Default,
245 WriteOnly,
246 ReadOnly,
247 Nullable,
248 XmlAttr,
249 MultipleOf,
250 Maximum,
251 Minimum,
252 ExclusiveMaximum,
253 ExclusiveMinimum,
254 MaxLength,
255 MinLength,
256 Pattern,
257 MaxItems,
258 MinItems,
259 AdditionalProperties
260 )))
261 }
262}
263
264#[cfg_attr(feature = "debug", derive(Debug))]
265struct Param<'a> {
266 field: &'a Field,
268 field_serde_params: Option<SerdeValue>,
270 container_attributes: FieldParamContainerAttributes<'a>,
272 serde_container: Option<&'a SerdeContainer>,
274}
275
276impl Param<'_> {
277 fn resolve_field_features(&self) -> (Vec<Feature>, Vec<Feature>) {
282 let mut field_features = self
283 .field
284 .attrs
285 .iter()
286 .filter(|attribute| attribute.path().is_ident("param"))
287 .map(|attribute| {
288 attribute
289 .parse_args::<FieldFeatures>()
290 .unwrap_or_abort()
291 .into_inner()
292 })
293 .reduce(|acc, item| acc.merge(item))
294 .unwrap_or_default();
295
296 if let Some(ref style) = self.container_attributes.style {
297 if !field_features
298 .iter()
299 .any(|feature| matches!(&feature, Feature::Style(_)))
300 {
301 field_features.push(style.clone()); };
303 }
304
305 field_features.into_iter().fold(
306 (Vec::<Feature>::new(), Vec::<Feature>::new()),
307 |(mut schema_features, mut param_features), feature| {
308 match feature {
309 Feature::Inline(_)
310 | Feature::Format(_)
311 | Feature::Default(_)
312 | Feature::WriteOnly(_)
313 | Feature::ReadOnly(_)
314 | Feature::Nullable(_)
315 | Feature::XmlAttr(_)
316 | Feature::MultipleOf(_)
317 | Feature::Maximum(_)
318 | Feature::Minimum(_)
319 | Feature::ExclusiveMaximum(_)
320 | Feature::ExclusiveMinimum(_)
321 | Feature::MaxLength(_)
322 | Feature::MinLength(_)
323 | Feature::Pattern(_)
324 | Feature::MaxItems(_)
325 | Feature::MinItems(_)
326 | Feature::AdditionalProperties(_) => {
327 schema_features.push(feature);
328 }
329 _ => {
330 param_features.push(feature);
331 }
332 };
333
334 (schema_features, param_features)
335 },
336 )
337 }
338}
339
340impl ToTokens for Param<'_> {
341 fn to_tokens(&self, tokens: &mut TokenStream) {
342 let field = self.field;
343 let field_serde_params = &self.field_serde_params;
344 let ident = &field.ident;
345 let mut name = &*ident
346 .as_ref()
347 .map(|ident| ident.to_string())
348 .or_else(|| self.container_attributes.name.cloned())
349 .unwrap_or_else(|| abort!(
350 field, "No name specified for unnamed field.";
351 help = "Try adding #[into_params(names(...))] container attribute to specify the name for this field"
352 ));
353
354 if name.starts_with("r#") {
355 name = &name[2..];
356 }
357
358 let (schema_features, mut param_features) = self.resolve_field_features();
359
360 let rename = param_features
361 .pop_rename_feature()
362 .map(|rename| rename.into_value());
363 let rename_to = field_serde_params
364 .as_ref()
365 .and_then(|field_param_serde| field_param_serde.rename.as_deref().map(Cow::Borrowed))
366 .or_else(|| rename.map(Cow::Owned));
367 let rename_all = self
368 .serde_container
369 .as_ref()
370 .and_then(|serde_container| serde_container.rename_all.as_ref())
371 .or_else(|| {
372 self.container_attributes
373 .rename_all
374 .map(|rename_all| rename_all.as_rename_rule())
375 });
376 let name = super::rename::<FieldRename>(name, rename_to, rename_all)
377 .unwrap_or(Cow::Borrowed(name));
378 let type_tree = TypeTree::from_type(&field.ty);
379
380 tokens.extend(quote! { utoipa::openapi::path::ParameterBuilder::new()
381 .name(#name)
382 });
383 tokens.extend(
384 if let Some(ref parameter_in) = self.container_attributes.parameter_in {
385 parameter_in.into_token_stream()
386 } else {
387 quote! {
388 .parameter_in(parameter_in_provider().unwrap_or_default())
389 }
390 },
391 );
392
393 if let Some(deprecated) = super::get_deprecated(&field.attrs) {
394 tokens.extend(quote! { .deprecated(Some(#deprecated)) });
395 }
396
397 let schema_with = pop_feature!(param_features => Feature::SchemaWith(_));
398 if let Some(schema_with) = schema_with {
399 tokens.extend(quote! { .schema(Some(#schema_with)).build() });
400 } else {
401 let description =
402 CommentAttributes::from_attributes(&field.attrs).as_formatted_string();
403 if !description.is_empty() {
404 tokens.extend(quote! { .description(Some(#description))})
405 }
406
407 let value_type = param_features.pop_value_type_feature();
408 let component = value_type
409 .as_ref()
410 .map(|value_type| value_type.as_type_tree())
411 .unwrap_or(type_tree);
412
413 let required = pop_feature_as_inner!(param_features => Feature::Required(_v))
414 .as_ref()
415 .map(super::features::Required::is_true)
416 .unwrap_or(false);
417
418 let non_required = (component.is_option() && !required)
419 || !component::is_required(field_serde_params.as_ref(), self.serde_container);
420 let required: Required = (!non_required).into();
421
422 tokens.extend(quote! {
423 .required(#required)
424 });
425 tokens.extend(param_features.to_token_stream());
426
427 let schema = ComponentSchema::new(component::ComponentSchemaProps {
428 type_tree: &component,
429 features: Some(schema_features),
430 description: None,
431 deprecated: None,
432 object_name: "",
433 });
434
435 tokens.extend(quote! { .schema(Some(#schema)).build() });
436 }
437 }
438}