Hello! I've recently discovered vld - it is like a missing piece in Axum stack, congratulations and thank you very much for your work!
I'm writing because of problem I found with Query parameters appearance in OpenAPI schema. I'll show a working example of Json for comparison:
vld::schema! {
#[derive(Debug, Serialize, Deserialize)]
struct BodyPayload {
sample: String => vld::string().min(1),
}
}
impl_to_schema!(BodyPayload);
// Later used in the Axum handler like this:
// async fn some_post_endpoint(
// VldJson(body): VldJson<BodyPayload>,
The above works perfectly fine - I have my payload validated in the endpoint. In OpenAPI schema it appears like this:
"properties": { "sample": {"type": "string", "minLength": 1 }}
However - with the Query it works only partially. With either of structs from code fragment below, I only have endpoint validation, but miss the OpenAPI definition.
// Neither this...
vld::schema! {
#[derive(Debug, Serialize, Deserialize, IntoParams)]
struct QueryParams {
sample: String => vld::string().min(16),
}
}
impl_to_schema!(QueryParams);
// ...or this works :(
#[derive(Debug, Deserialize, Validate, IntoParams)]
pub struct QueryParams {
#[vld(vld::string().min(16))]
sample: String,
}
// Later used in the Axum handler like this:
// async fn get_todos(
// Query(query): Query<QueryParams>,
The OpenAPI schema looks like this - it totally ignored the min length of string:
"parameters": {
"0":
"name": "sample",
"in": "query"
"required": true
"schema": {
"type": "string"
}
}
In order to actually have some validation rules printed in schema it is necessary to add param macro from utoipa's IntoParams explicitly, like this:
vld::schema! {
#[derive(Debug, Serialize, Deserialize, IntoParams)]
struct QueryParams {
#[param(min_length = 16)]
sample: String => vld::string().min(16),
}
}
impl_to_schema!(QueryParams);
As you can see though, this is error-prone as validation rules have to be placed in two places, instead of being derived like it's wonderfully done in Json's case.
Do you see I'm doing something wrong, or perhaps it's a bug we should try to tackle? I'm open for any help needed with this one, I'm just not sure where to start digging with this :) Thanks in advance for your time!
EDIT: Just noticed it also happens for Path, not only Query. So perhaps into all structs implementing IntoParams trait?
EDIT 2: After a few tries, I've actually managed to overcome the issue, this is a code example I modified from utoapi's axum example, with my notes marked as NOTE: comments:
use std::io::Error;
use std::net::{Ipv4Addr, SocketAddr};
use tokio::net::TcpListener;
use utoipa::{IntoParams, OpenApi};
use utoipa_axum::{router::OpenApiRouter, routes};
use utoipa_swagger_ui::SwaggerUi;
use vld;
use vld_axum::VldJson as Json;
use vld_axum::VldQuery as Query;
use vld_utoipa::impl_to_schema;
// NOTE: Step 1. I declare structs for Query params and Json (body):
vld::schema! {
#[derive(Debug, IntoParams)]
struct SearchParams {
sample: String => vld::string().min(1).max(200),
}
}
impl_to_schema!(SearchParams);
vld::schema! {
#[derive(Debug)]
struct BodyPayload {
sample: String => vld::string().min(1),
}
}
impl_to_schema!(BodyPayload);
#[derive(OpenApi)]
// NOTE: Step 2. I need to provide the struct manually into utoipa macro, it fills the "components" in OpenAPI schema
#[openapi(components(schemas(SearchParams)))]
struct ApiDoc;
fn router() -> OpenApiRouter {
OpenApiRouter::new().routes(routes!(search_todos))
}
#[utoipa::path(
get,
path = "/search",
// NOTE: Step 3. I need to explicitly add params here, as a Query one: this adds query parameter to endpoint's parameters
params(("SearchParams" = SearchParams, Query)),
responses(
(status = 200, description = "List matching todos by query")
)
)]
async fn search_todos(Query(query): Query<SearchParams>, Json(body): Json<BodyPayload>) -> String {
println!("Query {:?}", query);
println!("Body {:?}", body);
"Response".to_string()
}
#[tokio::main]
async fn main() -> Result<(), Error> {
let (router, api) = OpenApiRouter::with_openapi(ApiDoc::openapi())
.nest("/api/v1/todos", router())
.split_for_parts();
let router =
router.merge(SwaggerUi::new("/swagger-ui").url("/api-docs/openapi.json", api.clone()));
let address = SocketAddr::from((Ipv4Addr::UNSPECIFIED, 8080));
let listener = TcpListener::bind(&address).await?;
axum::serve(listener, router.into_make_service()).await
}
As we can observe, in steps 2 and 3 in need to explicitly add SearchParams into utoipa, while BodyPayload struct does not require this. Would that be an issue which could be solved on vld side, or it's rather utoipa's fault here?
Hello! I've recently discovered
vld- it is like a missing piece in Axum stack, congratulations and thank you very much for your work!I'm writing because of problem I found with Query parameters appearance in OpenAPI schema. I'll show a working example of
Jsonfor comparison:The above works perfectly fine - I have my payload validated in the endpoint. In OpenAPI schema it appears like this:
However - with the Query it works only partially. With either of structs from code fragment below, I only have endpoint validation, but miss the OpenAPI definition.
The OpenAPI schema looks like this - it totally ignored the min length of string:
In order to actually have some validation rules printed in schema it is necessary to add
parammacro from utoipa's IntoParams explicitly, like this:As you can see though, this is error-prone as validation rules have to be placed in two places, instead of being derived like it's wonderfully done in Json's case.
Do you see I'm doing something wrong, or perhaps it's a bug we should try to tackle? I'm open for any help needed with this one, I'm just not sure where to start digging with this :) Thanks in advance for your time!
EDIT: Just noticed it also happens for Path, not only Query. So perhaps into all structs implementing
IntoParamstrait?EDIT 2: After a few tries, I've actually managed to overcome the issue, this is a code example I modified from utoapi's axum example, with my notes marked as
NOTE:comments:As we can observe, in steps 2 and 3 in need to explicitly add SearchParams into utoipa, while BodyPayload struct does not require this. Would that be an issue which could be solved on vld side, or it's rather utoipa's fault here?