1use std::{borrow::Cow, fmt::Display};
2
3use proc_macro2::{Ident, Span, TokenStream};
4use proc_macro_error::abort;
5use quote::{quote, quote_spanned, ToTokens};
6use syn::{
7 parenthesized,
8 parse::{Parse, ParseBuffer, ParseStream},
9 Error, LitStr, Token, TypePath,
10};
11
12use crate::{
13 component::{
14 self,
15 features::{
16 impl_into_inner, parse_features, AllowReserved, Description, Example, ExclusiveMaximum,
17 ExclusiveMinimum, Explode, Feature, Format, MaxItems, MaxLength, Maximum, MinItems,
18 MinLength, Minimum, MultipleOf, Nullable, Pattern, ReadOnly, Style, ToTokensExt,
19 WriteOnly, XmlAttr,
20 },
21 ComponentSchema,
22 },
23 parse_utils, Required,
24};
25
26use super::InlineType;
27
28#[cfg_attr(feature = "debug", derive(Debug))]
40#[derive(PartialEq, Eq)]
41pub enum Parameter<'a> {
42 Value(ValueParameter<'a>),
43 IntoParamsIdent(IntoParamsIdentParameter<'a>),
45}
46
47#[cfg(any(
48 feature = "actix_extras",
49 feature = "rocket_extras",
50 feature = "axum_extras"
51))]
52impl<'p> Parameter<'p> {
53 pub fn merge(&mut self, other: Parameter<'p>) {
54 match (self, other) {
55 (Self::Value(value), Parameter::Value(other)) => {
56 value.parameter_schema = other.parameter_schema;
57 }
58 (Self::IntoParamsIdent(into_params), Parameter::IntoParamsIdent(other)) => {
59 *into_params = other;
60 }
61 _ => (),
62 }
63 }
64}
65
66impl Parse for Parameter<'_> {
67 fn parse(input: ParseStream) -> syn::Result<Self> {
68 if input.fork().parse::<TypePath>().is_ok() {
69 Ok(Self::IntoParamsIdent(IntoParamsIdentParameter {
70 path: Cow::Owned(input.parse::<TypePath>()?.path),
71 parameter_in_fn: None,
72 }))
73 } else {
74 Ok(Self::Value(input.parse()?))
75 }
76 }
77}
78
79impl ToTokens for Parameter<'_> {
80 fn to_tokens(&self, tokens: &mut TokenStream) {
81 match self {
82 Parameter::Value(parameter) => tokens.extend(quote! { .parameter(#parameter) }),
83 Parameter::IntoParamsIdent(IntoParamsIdentParameter {
84 path,
85 parameter_in_fn,
86 }) => {
87 let last_ident = &path.segments.last().unwrap().ident;
88
89 let default_parameter_in_provider = "e! { || None };
90 let parameter_in_provider = parameter_in_fn
91 .as_ref()
92 .unwrap_or(default_parameter_in_provider);
93 tokens.extend(quote_spanned! {last_ident.span()=>
94 .parameters(
95 Some(<#path as utoipa::IntoParams>::into_params(#parameter_in_provider))
96 )
97 })
98 }
99 }
100 }
101}
102
103#[cfg(any(
104 feature = "actix_extras",
105 feature = "rocket_extras",
106 feature = "axum_extras"
107))]
108impl<'a> From<crate::ext::ValueArgument<'a>> for Parameter<'a> {
109 fn from(argument: crate::ext::ValueArgument<'a>) -> Self {
110 Self::Value(ValueParameter {
111 name: argument.name.unwrap_or_else(|| Cow::Owned(String::new())),
112 parameter_in: if argument.argument_in == crate::ext::ArgumentIn::Path {
113 ParameterIn::Path
114 } else {
115 ParameterIn::Query
116 },
117 parameter_schema: argument.type_tree.map(|type_tree| ParameterSchema {
118 parameter_type: ParameterType::External(type_tree),
119 features: Vec::new(),
120 }),
121 ..Default::default()
122 })
123 }
124}
125
126#[cfg(any(
127 feature = "actix_extras",
128 feature = "rocket_extras",
129 feature = "axum_extras"
130))]
131impl<'a> From<crate::ext::IntoParamsType<'a>> for Parameter<'a> {
132 fn from(value: crate::ext::IntoParamsType<'a>) -> Self {
133 Self::IntoParamsIdent(IntoParamsIdentParameter {
134 path: value.type_path.expect("IntoParams type must have a path"),
135 parameter_in_fn: Some(value.parameter_in_provider),
136 })
137 }
138}
139
140#[cfg_attr(feature = "debug", derive(Debug))]
141struct ParameterSchema<'p> {
142 parameter_type: ParameterType<'p>,
143 features: Vec<Feature>,
144}
145
146impl ToTokens for ParameterSchema<'_> {
147 fn to_tokens(&self, tokens: &mut TokenStream) {
148 let mut to_tokens = |param_schema, required| {
149 tokens.extend(quote! { .schema(Some(#param_schema)).required(#required) });
150 };
151
152 match &self.parameter_type {
153 #[cfg(any(
154 feature = "actix_extras",
155 feature = "rocket_extras",
156 feature = "axum_extras"
157 ))]
158 ParameterType::External(type_tree) => {
159 let required: Required = (!type_tree.is_option()).into();
160
161 to_tokens(
162 ComponentSchema::new(component::ComponentSchemaProps {
163 type_tree,
164 features: Some(self.features.clone()),
165 description: None,
166 deprecated: None,
167 object_name: "",
168 }),
169 required,
170 )
171 }
172 ParameterType::Parsed(inline_type) => {
173 let type_tree = inline_type.as_type_tree();
174 let required: Required = (!type_tree.is_option()).into();
175 let mut schema_features = Vec::<Feature>::new();
176 schema_features.clone_from(&self.features);
177 schema_features.push(Feature::Inline(inline_type.is_inline.into()));
178
179 to_tokens(
180 ComponentSchema::new(component::ComponentSchemaProps {
181 type_tree: &type_tree,
182 features: Some(schema_features),
183 description: None,
184 deprecated: None,
185 object_name: "",
186 }),
187 required,
188 )
189 }
190 }
191 }
192}
193
194#[cfg_attr(feature = "debug", derive(Debug))]
195enum ParameterType<'p> {
196 #[cfg(any(
197 feature = "actix_extras",
198 feature = "rocket_extras",
199 feature = "axum_extras"
200 ))]
201 External(crate::component::TypeTree<'p>),
202 Parsed(InlineType<'p>),
203}
204
205#[derive(Default)]
206#[cfg_attr(feature = "debug", derive(Debug))]
207pub struct ValueParameter<'a> {
208 pub name: Cow<'a, str>,
209 parameter_in: ParameterIn,
210 parameter_schema: Option<ParameterSchema<'a>>,
211 features: (Vec<Feature>, Vec<Feature>),
212}
213
214impl PartialEq for ValueParameter<'_> {
215 fn eq(&self, other: &Self) -> bool {
216 self.name == other.name && self.parameter_in == other.parameter_in
217 }
218}
219
220impl Eq for ValueParameter<'_> {}
221
222impl Parse for ValueParameter<'_> {
223 fn parse(input_with_parens: ParseStream) -> syn::Result<Self> {
224 let input: ParseBuffer;
225 parenthesized!(input in input_with_parens);
226
227 let mut parameter = ValueParameter::default();
228
229 if input.peek(LitStr) {
230 let name = input.parse::<LitStr>()?.value();
232 parameter.name = Cow::Owned(name);
233
234 if input.peek(Token![=]) {
235 parameter.parameter_schema = Some(ParameterSchema {
236 parameter_type: ParameterType::Parsed(parse_utils::parse_next(&input, || {
237 input.parse().map_err(|error| {
238 Error::new(
239 error.span(),
240 format!("unexpected token, expected type such as String, {error}"),
241 )
242 })
243 })?),
244 features: Vec::new(),
245 });
246 }
247 } else {
248 return Err(input.error("unparseable parameter name, expected literal string"));
249 }
250
251 input.parse::<Token![,]>()?;
252
253 if input.fork().parse::<ParameterIn>().is_ok() {
254 parameter.parameter_in = input.parse()?;
255 input.parse::<Token![,]>()?;
256 }
257
258 let (schema_features, parameter_features) = input
259 .parse::<ParameterFeatures>()?
260 .split_for_parameter_type();
261
262 parameter.features = (schema_features.clone(), parameter_features);
263 if let Some(parameter_schema) = &mut parameter.parameter_schema {
264 parameter_schema.features = schema_features;
265 }
266
267 Ok(parameter)
268 }
269}
270
271#[derive(Default)]
272#[cfg_attr(feature = "debug", derive(Debug))]
273struct ParameterFeatures(Vec<Feature>);
274
275impl Parse for ParameterFeatures {
276 fn parse(input: ParseStream) -> syn::Result<Self> {
277 Ok(Self(parse_features!(
278 input as Style,
280 Explode,
281 AllowReserved,
282 Example,
283 crate::component::features::Deprecated,
284 Description,
285 Format,
287 WriteOnly,
288 ReadOnly,
289 Nullable,
290 XmlAttr,
291 MultipleOf,
292 Maximum,
293 Minimum,
294 ExclusiveMaximum,
295 ExclusiveMinimum,
296 MaxLength,
297 MinLength,
298 Pattern,
299 MaxItems,
300 MinItems
301 )))
302 }
303}
304
305impl ParameterFeatures {
306 fn split_for_parameter_type(self) -> (Vec<Feature>, Vec<Feature>) {
311 self.0.into_iter().fold(
312 (Vec::new(), Vec::new()),
313 |(mut schema_features, mut param_features), feature| {
314 match feature {
315 Feature::Format(_)
316 | Feature::WriteOnly(_)
317 | Feature::ReadOnly(_)
318 | Feature::Nullable(_)
319 | Feature::XmlAttr(_)
320 | Feature::MultipleOf(_)
321 | Feature::Maximum(_)
322 | Feature::Minimum(_)
323 | Feature::ExclusiveMaximum(_)
324 | Feature::ExclusiveMinimum(_)
325 | Feature::MaxLength(_)
326 | Feature::MinLength(_)
327 | Feature::Pattern(_)
328 | Feature::MaxItems(_)
329 | Feature::MinItems(_) => {
330 schema_features.push(feature);
331 }
332 _ => {
333 param_features.push(feature);
334 }
335 };
336
337 (schema_features, param_features)
338 },
339 )
340 }
341}
342
343impl_into_inner!(ParameterFeatures);
344
345impl ToTokens for ValueParameter<'_> {
346 fn to_tokens(&self, tokens: &mut TokenStream) {
347 let name = &*self.name;
348 tokens.extend(quote! {
349 utoipa::openapi::path::ParameterBuilder::from(utoipa::openapi::path::Parameter::new(#name))
350 });
351 let parameter_in = &self.parameter_in;
352 tokens.extend(quote! { .parameter_in(#parameter_in) });
353
354 let (schema_features, param_features) = &self.features;
355
356 tokens.extend(param_features.to_token_stream());
357
358 if !schema_features.is_empty() && self.parameter_schema.is_none() {
359 abort!(
360 Span::call_site(),
361 "Missing `parameter_type` attribute, cannot define schema features without it.";
362 help = "See docs for more details <https://docs.rs/utoipa/latest/utoipa/attr.path.html#parameter-type-attributes>"
363
364 );
365 }
366
367 if let Some(parameter_schema) = &self.parameter_schema {
368 parameter_schema.to_tokens(tokens);
369 }
370 }
371}
372
373#[cfg_attr(feature = "debug", derive(Debug))]
374pub struct IntoParamsIdentParameter<'i> {
375 pub path: Cow<'i, syn::Path>,
376 parameter_in_fn: Option<TokenStream>,
378}
379
380impl PartialEq for IntoParamsIdentParameter<'_> {
382 fn eq(&self, other: &Self) -> bool {
383 self.path
384 .segments
385 .iter()
386 .map(|segment| &segment.ident)
387 .collect::<Vec<_>>()
388 == other
389 .path
390 .segments
391 .iter()
392 .map(|segment| &segment.ident)
393 .collect::<Vec<_>>()
394 }
395}
396
397impl Eq for IntoParamsIdentParameter<'_> {}
398
399#[cfg_attr(feature = "debug", derive(Debug))]
400#[derive(PartialEq, Eq, Clone, Copy)]
401pub enum ParameterIn {
402 Query,
403 Path,
404 Header,
405 Cookie,
406}
407
408impl ParameterIn {
409 pub const VARIANTS: &'static [Self] = &[Self::Query, Self::Path, Self::Header, Self::Cookie];
410}
411
412impl Display for ParameterIn {
413 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
414 match self {
415 ParameterIn::Query => write!(f, "Query"),
416 ParameterIn::Path => write!(f, "Path"),
417 ParameterIn::Header => write!(f, "Header"),
418 ParameterIn::Cookie => write!(f, "Cookie"),
419 }
420 }
421}
422
423impl Default for ParameterIn {
424 fn default() -> Self {
425 Self::Path
426 }
427}
428
429impl Parse for ParameterIn {
430 fn parse(input: ParseStream) -> syn::Result<Self> {
431 fn expected_style() -> String {
432 let variants: String = ParameterIn::VARIANTS
433 .iter()
434 .map(ToString::to_string)
435 .collect::<Vec<_>>()
436 .join(", ");
437 format!("unexpected in, expected one of: {variants}")
438 }
439 let style = input.parse::<Ident>()?;
440
441 match &*style.to_string() {
442 "Path" => Ok(Self::Path),
443 "Query" => Ok(Self::Query),
444 "Header" => Ok(Self::Header),
445 "Cookie" => Ok(Self::Cookie),
446 _ => Err(Error::new(style.span(), expected_style())),
447 }
448 }
449}
450
451impl ToTokens for ParameterIn {
452 fn to_tokens(&self, tokens: &mut TokenStream) {
453 tokens.extend(match self {
454 Self::Path => quote! { utoipa::openapi::path::ParameterIn::Path },
455 Self::Query => quote! { utoipa::openapi::path::ParameterIn::Query },
456 Self::Header => quote! { utoipa::openapi::path::ParameterIn::Header },
457 Self::Cookie => quote! { utoipa::openapi::path::ParameterIn::Cookie },
458 })
459 }
460}
461
462#[derive(Copy, Clone)]
464#[cfg_attr(feature = "debug", derive(Debug))]
465pub enum ParameterStyle {
466 Matrix,
467 Label,
468 Form,
469 Simple,
470 SpaceDelimited,
471 PipeDelimited,
472 DeepObject,
473}
474
475impl Parse for ParameterStyle {
476 fn parse(input: ParseStream) -> syn::Result<Self> {
477 const EXPECTED_STYLE: &str = "unexpected style, expected one of: Matrix, Label, Form, Simple, SpaceDelimited, PipeDelimited, DeepObject";
478 let style = input.parse::<Ident>()?;
479
480 match &*style.to_string() {
481 "Matrix" => Ok(ParameterStyle::Matrix),
482 "Label" => Ok(ParameterStyle::Label),
483 "Form" => Ok(ParameterStyle::Form),
484 "Simple" => Ok(ParameterStyle::Simple),
485 "SpaceDelimited" => Ok(ParameterStyle::SpaceDelimited),
486 "PipeDelimited" => Ok(ParameterStyle::PipeDelimited),
487 "DeepObject" => Ok(ParameterStyle::DeepObject),
488 _ => Err(Error::new(style.span(), EXPECTED_STYLE)),
489 }
490 }
491}
492
493impl ToTokens for ParameterStyle {
494 fn to_tokens(&self, tokens: &mut TokenStream) {
495 match self {
496 ParameterStyle::Matrix => {
497 tokens.extend(quote! { utoipa::openapi::path::ParameterStyle::Matrix })
498 }
499 ParameterStyle::Label => {
500 tokens.extend(quote! { utoipa::openapi::path::ParameterStyle::Label })
501 }
502 ParameterStyle::Form => {
503 tokens.extend(quote! { utoipa::openapi::path::ParameterStyle::Form })
504 }
505 ParameterStyle::Simple => {
506 tokens.extend(quote! { utoipa::openapi::path::ParameterStyle::Simple })
507 }
508 ParameterStyle::SpaceDelimited => {
509 tokens.extend(quote! { utoipa::openapi::path::ParameterStyle::SpaceDelimited })
510 }
511 ParameterStyle::PipeDelimited => {
512 tokens.extend(quote! { utoipa::openapi::path::ParameterStyle::PipeDelimited })
513 }
514 ParameterStyle::DeepObject => {
515 tokens.extend(quote! { utoipa::openapi::path::ParameterStyle::DeepObject })
516 }
517 }
518 }
519}