Skip to content

Commit de536e4

Browse files
authored
utopia-gen: Adjust params code to not set nullable on Option for Query params (#1248)
1 parent 66b80a4 commit de536e4

9 files changed

Lines changed: 132 additions & 104 deletions

File tree

utoipa-gen/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313

1414
### Changed
1515

16+
* Adjust params code to not set `nullable` on `Option` for `Query` params (https://github.com/juhaku/utoipa/pull/1248)
1617
* Use `insta` for snapshot testing (https://github.com/juhaku/utoipa/pull/1247)
1718
* Make `parse_named_attributes` a method of `MediaTypeAttr` (https://github.com/juhaku/utoipa/pull/1236)
1819
* Use a re-exported `serde_json` dependency in macros instead of implicitly requiring it as dependency in end projects (https://github.com/juhaku/utoipa/pull/1243)

utoipa-gen/src/component.rs

Lines changed: 36 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -694,6 +694,18 @@ pub struct ComponentSchemaProps<'c> {
694694
pub description: Option<&'c ComponentDescription<'c>>,
695695
}
696696

697+
impl ComponentSchemaProps<'_> {
698+
fn set_nullable(&mut self) {
699+
if !self
700+
.features
701+
.iter()
702+
.any(|feature| matches!(feature, Feature::Nullable(_)))
703+
{
704+
self.features.push(Nullable::new().into());
705+
}
706+
}
707+
}
708+
697709
#[cfg_attr(feature = "debug", derive(Debug))]
698710
pub enum ComponentDescription<'c> {
699711
CommentAttributes(&'c CommentAttributes),
@@ -750,11 +762,33 @@ pub struct ComponentSchema {
750762
}
751763

752764
impl ComponentSchema {
753-
pub fn new(
765+
pub fn for_params(
766+
mut schema_props: ComponentSchemaProps,
767+
option_is_nullable: bool,
768+
) -> Result<Self, Diagnostics> {
769+
// Add nullable feature if not already exists.
770+
// Option is always nullable, except when used in query parameters.
771+
if schema_props.type_tree.is_option() && option_is_nullable {
772+
schema_props.set_nullable()
773+
}
774+
775+
Self::new_inner(schema_props)
776+
}
777+
778+
pub fn new(mut schema_props: ComponentSchemaProps) -> Result<Self, Diagnostics> {
779+
// Add nullable feature if not already exists. Option is always nullable
780+
if schema_props.type_tree.is_option() {
781+
schema_props.set_nullable();
782+
}
783+
784+
Self::new_inner(schema_props)
785+
}
786+
787+
fn new_inner(
754788
ComponentSchemaProps {
755789
container,
756790
type_tree,
757-
mut features,
791+
features,
758792
description,
759793
}: ComponentSchemaProps,
760794
) -> Result<Self, Diagnostics> {
@@ -791,13 +825,6 @@ impl ComponentSchema {
791825
description,
792826
)?,
793827
Some(GenericType::Option) => {
794-
// Add nullable feature if not already exists. Option is always nullable
795-
if !features
796-
.iter()
797-
.any(|feature| matches!(feature, Feature::Nullable(_)))
798-
{
799-
features.push(Nullable::new().into());
800-
}
801828
let child = type_tree
802829
.children
803830
.as_ref()

utoipa-gen/src/component/features/attributes.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -414,6 +414,12 @@ impl_feature! {
414414
pub struct ParameterIn(parameter::ParameterIn);
415415
}
416416

417+
impl ParameterIn {
418+
pub fn is_query(&self) -> bool {
419+
matches!(self.0, parameter::ParameterIn::Query)
420+
}
421+
}
422+
417423
impl Parse for ParameterIn {
418424
fn parse(input: syn::parse::ParseStream, _: Ident) -> syn::Result<Self> {
419425
parse_utils::parse_next(input, || input.parse::<parameter::ParameterIn>().map(Self))

utoipa-gen/src/component/into_params.rs

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -407,12 +407,18 @@ impl Param {
407407
});
408408
tokens.extend(param_features.to_token_stream()?);
409409

410-
let schema = ComponentSchema::new(component::ComponentSchemaProps {
411-
type_tree: component,
412-
features: schema_features,
413-
description: None,
414-
container: &Container { generics },
415-
})?;
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+
)?;
416422
let schema_tokens = schema.to_token_stream();
417423

418424
tokens.extend(quote! { .schema(Some(#schema_tokens)).build() });

utoipa-gen/src/path/parameter.rs

Lines changed: 36 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -126,16 +126,21 @@ impl ToTokensDiagnostics for Parameter<'_> {
126126
))]
127127
impl<'a> From<crate::ext::ValueArgument<'a>> for Parameter<'a> {
128128
fn from(argument: crate::ext::ValueArgument<'a>) -> Self {
129+
let parameter_in = if argument.argument_in == crate::ext::ArgumentIn::Path {
130+
ParameterIn::Path
131+
} else {
132+
ParameterIn::Query
133+
};
134+
135+
let option_is_nullable = parameter_in != ParameterIn::Query;
136+
129137
Self::Value(ValueParameter {
130138
name: argument.name.unwrap_or_else(|| Cow::Owned(String::new())),
131-
parameter_in: if argument.argument_in == crate::ext::ArgumentIn::Path {
132-
ParameterIn::Path
133-
} else {
134-
ParameterIn::Query
135-
},
139+
parameter_in,
136140
parameter_schema: argument.type_tree.map(|type_tree| ParameterSchema {
137141
parameter_type: ParameterType::External(type_tree),
138142
features: Vec::new(),
143+
option_is_nullable,
139144
}),
140145
..Default::default()
141146
})
@@ -160,6 +165,7 @@ impl<'a> From<crate::ext::IntoParamsType<'a>> for Parameter<'a> {
160165
struct ParameterSchema<'p> {
161166
parameter_type: ParameterType<'p>,
162167
features: Vec<Feature>,
168+
option_is_nullable: bool,
163169
}
164170

165171
impl ToTokensDiagnostics for ParameterSchema<'_> {
@@ -178,14 +184,17 @@ impl ToTokensDiagnostics for ParameterSchema<'_> {
178184
let required: Required = (!type_tree.is_option()).into();
179185

180186
to_tokens(
181-
ComponentSchema::new(component::ComponentSchemaProps {
182-
type_tree,
183-
features: self.features.clone(),
184-
description: None,
185-
container: &Container {
186-
generics: &Generics::default(),
187+
ComponentSchema::for_params(
188+
component::ComponentSchemaProps {
189+
type_tree,
190+
features: self.features.clone(),
191+
description: None,
192+
container: &Container {
193+
generics: &Generics::default(),
194+
},
187195
},
188-
})?
196+
self.option_is_nullable,
197+
)?
189198
.to_token_stream(),
190199
required,
191200
);
@@ -199,14 +208,17 @@ impl ToTokensDiagnostics for ParameterSchema<'_> {
199208
schema_features.push(Feature::Inline(inline_type.is_inline.into()));
200209

201210
to_tokens(
202-
ComponentSchema::new(component::ComponentSchemaProps {
203-
type_tree: &type_tree,
204-
features: schema_features,
205-
description: None,
206-
container: &Container {
207-
generics: &Generics::default(),
211+
ComponentSchema::for_params(
212+
component::ComponentSchemaProps {
213+
type_tree: &type_tree,
214+
features: schema_features,
215+
description: None,
216+
container: &Container {
217+
generics: &Generics::default(),
218+
},
208219
},
209-
})?
220+
self.option_is_nullable,
221+
)?
210222
.to_token_stream(),
211223
required,
212224
);
@@ -267,6 +279,7 @@ impl Parse for ValueParameter<'_> {
267279
})
268280
})?),
269281
features: Vec::new(),
282+
option_is_nullable: true,
270283
});
271284
}
272285
} else {
@@ -289,6 +302,10 @@ impl Parse for ValueParameter<'_> {
289302
parameter.features = (schema_features.clone(), parameter_features);
290303
if let Some(parameter_schema) = &mut parameter.parameter_schema {
291304
parameter_schema.features = schema_features;
305+
306+
if parameter.parameter_in == ParameterIn::Query {
307+
parameter_schema.option_is_nullable = false;
308+
}
292309
}
293310

294311
Ok(parameter)

0 commit comments

Comments
 (0)