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