New to rust and friends. Using actix-web, utoipa, and utoipa-swagger-ui.
I'm trying to write a basic end point with an optional query parameter. For a minimal example, let's say it's a GET to list objects, with an optional limit parameter. The details don't matter as it's actually the OpenAPI stuff I'm struggling with
#[derive(Deserialize, ToSchema)]
struct QueryParams {
limit: Option<bool>
}
#[utoipa::path(get, path="/users", params(("limit", Query))]
#[actix-web::get("/users")]
pub async fn get_users(query_params: web::Query<QueryParams>) -> impl Responder {
HttResponse::Ok().body("Coming soon")
}
I have this endpoint working as is, and if I open the swagger-ui page I see the end-point and the query parameters.
But no-matter what I do, I can't get utoipa to mark the parameter optional in the generated schema: it is always marked as required.
This seems a fundamental ability, but I can't find any documentation explaining it.
This may well be a duplicate and, if so, I'm happy to close and delete this question, but I've been fighting with this for a couple of days
---- Update
Apparently, my example above was too minimal to show the problem. Here is a complete program, which I've cut down from my actual application.
use actix_web::{get, middleware, post, web, App, HttpResponse, HttpServer, Responder};
use utoipa::OpenApi;
use utoipa_swagger_ui::SwaggerUi;
use serde::Deserialize;
use utoipa::ToSchema;
#[derive(Deserialize, ToSchema)]
struct QFilename {
filename: Option<String>,
}
#[utoipa::path(get, path = "/v2/users", params(("filename", Query, description = "download filename")))]
#[get("/users")]
async fn get_users(_query_params: web::Query<QFilename>) -> impl Responder {
HttpResponse::Ok().body("Hello world!")
}
#[actix_web::main]
async fn main() -> std::io::Result<()> {
#[derive(OpenApi)]
#[openapi(paths(
get_users,
))]
struct OpenApiDoc;
let openapi = OpenApiDoc::openapi();
env_logger::init_from_env(env_logger::Env::new().default_filter_or("info"));
HttpServer::new(move || {
App::new()
.wrap(middleware::Logger::default())
.service(
web::scope("/v2")
.service(get_users),
)
.service(
SwaggerUi::new("/swagger-ui/{_:.*}").url("/api-docs/openapi.json", openapi.clone()),
)
})
.bind(("127.0.0.1", 8080))?
.run()
.await
}
This is my Cargo.toml
[package]
name = "minimal"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
utoipa = { version = "4", features = ["actix_extras"] }
actix-web = "4"
tokio = { version = "1", features = ["full"] }
utoipa-swagger-ui = { version = "6", features = ['actix-web'] }
env_logger = "0.11.2"
log = "0.4.20"
serde = { version = "1", features = ["derive"] }
This is the result
And this is the generated openapi spec
{
"openapi":"3.0.3",
"info":{
"title":"minimal",
"description":"",
"license":{
"name":""
},
"version":"0.1.0"
},
"paths":{
"/v2/users":{
"get":{
"tags":[
"crate"
],
"operationId":"get_users",
"parameters":[
{
"name":"filename",
"in":"query",
"description":"download filename",
"required":true
}
],
"responses":{
}
}
}
}
}

You didn't specify a type for the parameter.
It should be:
And then the JSON generated:
Screenshot:
This still shows "* required", because the field
filenameis optional, but the objectQFilenameis required (you never said otherwise). In JSON, (like Swagger's "Try it out" interface), that means we can send an empty object{}, but not nothing. In query language, there is no difference, which is why you expected it to not say "required".If you want the parameter to show up as non-required, you can wrap it in
Option. Not the real parameter - then you'll have doubleOption, but the type you describe toutoipa: