utoipa icon indicating copy to clipboard operation
utoipa copied to clipboard

Wrapping axum query params makes them required

Open TheDan64 opened this issue 1 year ago • 4 comments

I have some optional query params in axum:

#[derive(Debug, Deserialize, IntoParams, Validate)]
pub struct ReadManyParams {
    /// Name to start after
    start_after: Option<String>,
    /// Limits the number of results
    #[validate(range(min = 1, max = 1000))]
    per_page: Option<usize>,
}

I created a wrapper validation type to call the validator crate. For example, in this case it ensures per_page query param is in [1, 1000]:

#[derive(Clone, Copy, Debug)]
pub struct Validated<T>(pub T);

#[async_trait]
impl<T, S> FromRequestParts<S> for Validated<T>
where
    T: Deref + FromRequestParts<S>,
    T::Target: Validate,
    S: Sync,
{
    type Rejection = ValidationErrorOr<T::Rejection>;

    async fn from_request_parts(parts: &mut Parts, state: &S) -> Result<Self, Self::Rejection> {
        let value = T::from_request_parts(parts, state)
            .await
            .map_err(ValidationErrorOr::Other)?;
        value.validate()?;
        Ok(Validated(value))
    }
}

(This is recommended in the axum examples)

My endpoint looks something like this:

#[utoipa::path(
    get,
    path = "/api/whatever",
    params(ReadManyParams),
)]
pub async fn read_many(
    Validated(Query(params)): Validated<Query<ReadManyParams>>,
) -> ... {
    ...
}

This mostly works, without Validated, both of the query parameters are marked optional by utoipa, which is correct. However, after wrapping it in my Validated type, they still show up but are now marked as required, which is incorrect since they're both optional and swagger UI is now too strict to use

image

TheDan64 avatar May 18 '23 14:05 TheDan64

Maybe related to #609?

TheDan64 avatar May 18 '23 15:05 TheDan64

I also just noticed that, even if you specify those incorrectly required fields, they won't show up in the request

TheDan64 avatar May 18 '23 15:05 TheDan64

I've found I need to wrap the struct with #[into_params(parameter_in = Query)]:

#[derive(Debug, Deserialize, Default, IntoParams)]
#[into_params(parameter_in = Query)]
pub struct TreeQueryParams {
    pub parent_id: Option<Uuid>,
}

eblocha avatar Jul 11 '23 01:07 eblocha

@TheDan64

Maybe related to https://github.com/juhaku/utoipa/issues/609?

This might be related to #609 and #529. I just commented here https://github.com/juhaku/utoipa/issues/609#issuecomment-1630114176 about the nullability and requirability of the parameters.

I need to check whether the wrapping actually works as expected and I am not sure how bullet proof the implementation was. To what @eblocha commented, that you need to provide explicitly the #[into_params(parameter_in = Query)] attribute. If I remember this should not be necessary but might be useful when the automatic resolve fails just as can be seen from the attached picture. Yet this should not affect to the nullability or requirability but only default value for parameter_in attribute.

It might be as well that the wrapping makes the parameters behave weirdly not according to what is defined in the docs https://docs.rs/utoipa/latest/utoipa/derive.ToSchema.html#field-nullability-and-required-rules but that is something I need to check.

Related #479 #314

juhaku avatar Jul 11 '23 05:07 juhaku