-
Notifications
You must be signed in to change notification settings - Fork 224
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Document may not be right #1054
Comments
@ipconfiger Indeed the docs might be incorrect, and in 4.x.x version there are lot of inconsistencies in enum schema processing. This is being fixed in PR #1059. Yet if you have only a plain enum having only unit variants, then |
@ipconfiger I belive this has been solved with the 5.0.0 release. If you encounter more issues this can of course be reopened but for now closing. |
For me this still fails with |
Does the above example of @ipconfiger fail or do you have some other code that fails? Perhaps this needs some future investigation for possible fix. |
It still does complain about |
#[derive(ToSchema)]
enum ErrorResponse {
#[schema(title = "InvalidCredentials")]
InvalidCredentials,
#[schema(title = "NotFound")]
NotFound(String),
} The above enum with So this will not work: #[derive(ToSchema)]
enum ErrorResponse {
#[schema(title = "InvalidCredentials")]
InvalidCredentials,
#[schema(title = "NotFound")]
NotFound,
} There are plenty of tests for enums with title e.g. this one in |
The real question, which I did not find answered anywhere, is how do you properly express a string enum with utoipa:
#[derive(ToSchema)]
pub struct Foo {
// whatever we need to insert here
}
#[derive(utoipa::OpenApi)]
#[openapi(
components(schemas(Foo)),
)]
pub struct ApiDoc; I (and I do not seem to be alone) assumed that utoipa is intelligent enough to generate bindings which bridge the divide between Rust and OpenApi enums. pub enum MachineState {
Free,
InUse,
ToCheck,
Blocked,
Disabled,
Reserved,
} |
The above will generate the spec as follows: type: string
enum: [Free, InUse, ToCheck, Blocked, Disabled, Reserved] |
I have this code and somehow the use crate::resources::modules::fabaccess::Status;
use crate::rest::user::User;
use crate::ResourcesHandle;
use crate::SessionManager;
use axum::extract::Path;
use axum::extract::State;
use axum::response::IntoResponse;
use axum::Json;
use http::StatusCode;
use std::sync::Arc;
use tracing::info_span;
use utoipa_axum::router::OpenApiRouter;
use utoipa_axum::routes;
use uuid::Uuid;
#[utoipa::path(
post,
path = "/machines",
summary = "Register machine",
responses(
(status = StatusCode::OK, response = responses::Machine),
(status = StatusCode::BAD_REQUEST),
(status = StatusCode::UNAUTHORIZED, response = crate::rest::authentication::responses::Unauthorized),
(status = StatusCode::FORBIDDEN),
(status = StatusCode::NOT_IMPLEMENTED),
),
)]
async fn register_machine(State(_ctx): State<Arc<MachineContext>>) -> impl IntoResponse {
(StatusCode::NOT_IMPLEMENTED, Json(())).into_response()
}
#[utoipa::path(
delete,
path = "/machines/{id}",
params(
("id" = String, description = "Machine ID"),
),
summary = "Unregister machine",
responses(
(status = StatusCode::OK),
(status = StatusCode::BAD_REQUEST),
(status = StatusCode::UNAUTHORIZED, response = crate::rest::authentication::responses::Unauthorized),
(status = StatusCode::FORBIDDEN),
(status = StatusCode::NOT_IMPLEMENTED),
(status = StatusCode::NOT_FOUND),
),
)]
async fn unregister_machine(
State(_ctx): State<Arc<MachineContext>>,
Path(_id): Path<String>,
) -> impl IntoResponse {
(StatusCode::NOT_IMPLEMENTED, Json(())).into_response()
}
#[utoipa::path(
get,
path = "/machines/{id}",
params(
("id" = String, description = "Machine ID"),
),
summary = "Get machine",
responses(
(status = StatusCode::OK, response = responses::Machine),
(status = StatusCode::BAD_REQUEST),
(status = StatusCode::UNAUTHORIZED, response = crate::rest::authentication::responses::Unauthorized),
(status = StatusCode::FORBIDDEN),
(status = StatusCode::NOT_FOUND),
),
)]
async fn get_machine(
State(ctx): State<Arc<MachineContext>>,
Path(id): Path<String>,
) -> impl IntoResponse {
let parent = info_span!("get_machine");
if let Some(resource) = ctx.resources.get_by_id(id.as_str()) {
let uid = "123";
match ctx.sessionmanager.try_open(&parent, uid) {
Some(session) => {
if resource.visible(&session) {
responses::Machine::from(resource).into_response()
} else {
(StatusCode::FORBIDDEN).into_response()
}
}
None => (StatusCode::NOT_FOUND).into_response(),
}
} else {
(StatusCode::NOT_FOUND).into_response()
}
}
#[utoipa::path(
get,
path = "/machines",
summary = "Get machine list",
responses(
(status = StatusCode::OK, response = responses::Machines),
(status = StatusCode::BAD_REQUEST),
(status = StatusCode::UNAUTHORIZED, response = crate::rest::authentication::responses::Unauthorized),
(status = StatusCode::FORBIDDEN),
),
)]
async fn list_machines(State(ctx): State<Arc<MachineContext>>) -> impl IntoResponse {
let parent = info_span!("list_machines");
let uid = "123";
match ctx.sessionmanager.try_open(&parent, uid) {
Some(session) => {
let machine_list: Vec<&crate::Resource> = ctx
.resources
.list_all()
.into_iter()
.filter(|resource| resource.visible(&session))
.collect();
responses::Machines::from(machine_list).into_response()
}
None => (StatusCode::NOT_FOUND).into_response(),
}
}
#[derive(utoipa::ToSchema, serde::Deserialize, serde::Serialize)]
pub struct SetStateRequest {
pub state: MachineState,
}
#[utoipa::path(
post,
path = "/machines/{id}/state",
summary = "Set state of machine",
params(
("id" = String, description = "Machine ID"),
),
request_body = SetStateRequest,
responses(
(status = StatusCode::OK),
(status = StatusCode::BAD_REQUEST),
(status = StatusCode::UNAUTHORIZED, response = crate::rest::authentication::responses::Unauthorized),
(status = StatusCode::FORBIDDEN),
(status = StatusCode::NOT_FOUND),
),
)]
#[axum::debug_handler]
async fn set_state(
State(ctx): State<Arc<MachineContext>>,
Path(_id): Path<String>,
Json(req): Json<SetStateRequest>,
) -> impl IntoResponse {
let resource = ctx.resources.get_by_id(_id.as_str());
if resource.is_none() {
return (StatusCode::NOT_FOUND).into_response();
}
let resource = resource.unwrap();
let parent = info_span!("list_machines");
let uid = "123";
let session = ctx.sessionmanager.try_open(&parent, uid);
if session.is_none() {
return (StatusCode::NOT_FOUND).into_response();
}
let session = session.unwrap();
if !resource.visible(&session) {
return (StatusCode::FORBIDDEN).into_response();
}
let state = match req.state {
MachineState::Blocked => Status::Blocked(session.get_user_ref()),
MachineState::Disabled => Status::Disabled,
MachineState::Free => Status::Free,
MachineState::InUse => Status::InUse(session.get_user_ref()),
MachineState::Reserved => Status::Reserved(session.get_user_ref()),
MachineState::ToCheck => Status::ToCheck(session.get_user_ref()),
};
resource.try_update(session.clone(), state).await;
(StatusCode::OK).into_response()
}
#[utoipa::path(
get,
path = "/machine/{id}/reservations",
summary = "Return reservations for resource",
params(
("id" = String, description = "Machine ID"),
),
responses(
(status = StatusCode::OK, body = Vec<Reservation>),
(status = StatusCode::BAD_REQUEST),
(status = StatusCode::UNAUTHORIZED, response = crate::rest::authentication::responses::Unauthorized),
(status = StatusCode::FORBIDDEN),
(status = StatusCode::NOT_FOUND),
),
)]
async fn get_reservations(State(_ctx): State<Arc<MachineContext>>) -> impl IntoResponse {
(StatusCode::NOT_IMPLEMENTED, Json(())).into_response()
}
#[derive(utoipa::ToSchema)]
pub struct Space {
pub id: Uuid,
pub name: String,
pub info: String,
}
#[derive(utoipa::ToSchema, serde::Deserialize, serde::Serialize)]
pub enum MachineState {
Free,
InUse,
ToCheck,
Blocked,
Disabled,
Reserved,
}
#[derive(utoipa::ToSchema)]
pub struct Machine {
pub id: String,
pub space: Space,
pub name: String,
pub description: String,
pub state: MachineState,
pub manager: Option<User>,
pub wiki: String,
pub urn: String,
pub category: String,
}
#[derive(utoipa::ToSchema, utoipa::ToResponse)]
pub struct Reservation {
pub user: User,
#[schema(format = DateTime)]
pub start: String,
#[schema(format = DateTime)]
pub end: String,
}
pub mod responses {
pub use crate::rest::authentication::responses::Unauthorized;
use axum::body::Body;
use axum::response::IntoResponse;
use axum::Json;
use http::StatusCode;
use serde::Serialize;
#[derive(Debug, Serialize, utoipa::ToResponse, utoipa::ToSchema)]
pub struct Machine {
pub id: String,
pub name: String,
}
impl From<&crate::Resource> for Machine {
fn from(resource: &crate::Resource) -> Self {
Self {
id: resource.get_id().to_string(),
name: resource.get_name().to_string(),
}
}
}
impl IntoResponse for Machine {
fn into_response(self) -> http::Response<Body> {
(StatusCode::OK, Json(self)).into_response()
}
}
#[derive(Debug, Serialize, utoipa::ToResponse)]
pub struct Machines(Vec<Machine>);
impl IntoResponse for Machines {
fn into_response(self) -> http::Response<Body> {
(StatusCode::OK, Json(self)).into_response()
}
}
impl<'a> From<Vec<&crate::Resource>> for Machines {
fn from(resources: Vec<&crate::Resource>) -> Self {
Self(resources.iter().map(|r| Machine::from(*r)).collect())
}
}
}
pub fn router(sessionmanager: &SessionManager, ressources: &ResourcesHandle) -> OpenApiRouter {
let ctx = Arc::new(MachineContext {
resources: ressources.clone(),
sessionmanager: sessionmanager.clone(),
});
OpenApiRouter::new()
.routes(routes!(
register_machine,
unregister_machine,
get_machine,
list_machines,
set_state,
get_reservations,
))
.with_state(ctx)
}
struct MachineContext {
sessionmanager: SessionManager,
resources: ResourcesHandle,
}
#[derive(utoipa::OpenApi)]
#[openapi(
info(description = "FabAccess Machine API"),
modifiers(&SecurityAddon),
paths(
register_machine,
unregister_machine,
get_machine,
list_machines,
set_state,
get_reservations,
),
components(responses(
responses::Unauthorized,
responses::Machine,
responses::Machines,
),
schemas(SetStateRequest)),
)]
pub struct ApiDoc; |
in :
Doc
It is possible to specify the title of each variant to help generators create named structures.
but build will fault:
Cargo.toml
What i miss?
The text was updated successfully, but these errors were encountered: