-
-
Notifications
You must be signed in to change notification settings - Fork 296
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
feat: OpenAPI integration #984
base: master
Are you sure you want to change the base?
Conversation
Looks like a good start. I think the yaml also should contain a few other things:
|
9055d99
to
3e106d0
Compare
cfg for tests
3e106d0
to
ea48919
Compare
Hey @NexVeridian Can I help you finish this pr? |
@DenuxPlays yeah of course |
How can I help? Also just one Note to your pr description. |
thanks! this would be nice if possible: impl Routes {
/// .add_openapi(routes!(get_action, post_action))
pub fn add_openapi(mut self, method: UtoipaMethodRouter<AppContext>) -> Self {}
} any of the unchecked ones in the pr description would be great: |
9acc912
to
819b45e
Compare
14e09c1
to
0a7df8c
Compare
static JWT_LOCATION: OnceLock<Option<JWTLocation>> = OnceLock::new(); | ||
|
||
#[must_use] | ||
pub fn get_jwt_location_from_ctx(ctx: &AppContext) -> JWTLocation { | ||
ctx.config | ||
.auth | ||
.as_ref() | ||
.and_then(|auth| auth.jwt.as_ref()) | ||
.and_then(|jwt| jwt.location.as_ref()) | ||
.unwrap_or(&JWTLocation::Bearer) | ||
.clone() | ||
} | ||
|
||
pub fn set_jwt_location_ctx(ctx: &AppContext) { | ||
set_jwt_location(get_jwt_location_from_ctx(ctx)); | ||
} | ||
|
||
pub fn set_jwt_location(jwt_location: JWTLocation) -> &'static Option<JWTLocation> { | ||
JWT_LOCATION.get_or_init(|| Some(jwt_location)) | ||
} | ||
|
||
fn get_jwt_location() -> Option<&'static JWTLocation> { | ||
JWT_LOCATION.get().unwrap_or(&None).as_ref() | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
seems like this might be flaky in cargo test
because the global state isn't reset between test runs
tests are good in cargo nextest
and should be fine for users
if it's used in the utoipa::path then yeah, I'm not sure if they would be, I haven't used those before |
They would to provide Basic paginagtion metadata |
I had to do something similar recently and If I understand correctly you would need to generate something like this. I had a problem where the generated openapi schema didn't see that X struct was a query parameter.
|
5f4c63f still has some errors that I don't know how to fix, one of you two should take a look maybe |
I think the Solution would be something like cfg_of and we have to define the struct two times. |
I think this would work, I tested it on your branch. The cfg_attr for the trait bounds is pretty ugly so I think @DenuxPlays has a point with using cfg_of and having two structs. Pragnation.rs
pragnate/mod.rs
|
I had something like the following in mind. pagination.rs use cfg_if::cfg_if;
cfg_if! {
if #[cfg(any(
feature = "openapi_swagger",
feature = "openapi_redoc",
feature = "openapi_scalar"
))] {
#[derive(Debug, serde::Deserialize, serde::Serialize, utoipa::ToSchema)]
pub struct Pager<T: utoipa::ToSchema> {
#[serde(rename(serialize = "results"))]
pub results: T,
#[serde(rename(serialize = "pagination"))]
pub info: PagerMeta,
}
} else {
#[derive(Debug, serde::Deserialize, serde::Serialize)]
pub struct Pager<T> {
#[serde(rename(serialize = "results"))]
pub results: T,
#[serde(rename(serialize = "pagination"))]
pub info: PagerMeta,
}
}
}
cfg_if! {
if #[cfg(any(
feature = "openapi_swagger",
feature = "openapi_redoc",
feature = "openapi_scalar"
))] {
#[derive(Debug, serde::Deserialize, serde::Serialize, utoipa::ToSchema)]
pub struct PagerMeta {
#[serde(rename(serialize = "page"))]
pub page: u64,
#[serde(rename(serialize = "page_size"))]
pub page_size: u64,
#[serde(rename(serialize = "total_pages"))]
pub total_pages: u64,
#[serde(rename(serialize = "total_items"))]
pub total_items: u64,
}
} else {
#[derive(Debug, serde::Deserialize, serde::Serialize)]
pub struct PagerMeta {
#[serde(rename(serialize = "page"))]
pub page: u64,
#[serde(rename(serialize = "page_size"))]
pub page_size: u64,
#[serde(rename(serialize = "total_pages"))]
pub total_pages: u64,
#[serde(rename(serialize = "total_items"))]
pub total_items: u64,
}
}
}
cfg_if! {
if #[cfg(any(
feature = "openapi_swagger",
feature = "openapi_redoc",
feature = "openapi_scalar"
))] {
impl<T: utoipa::ToSchema> Pager<T> {
#[must_use]
pub const fn new(results: T, meta: PagerMeta) -> Self {
Self {
results,
info: meta,
}
}
}
} else {
impl<T> Pager<T> {
#[must_use]
pub const fn new(results: T, meta: PagerMeta) -> Self {
Self {
results,
info: meta,
}
}
}
}
} I think that @SorenEdwards implementation for the query is good but we should also add ToSchema to it. #[cfg_attr(
any(
feature = "openapi_swagger",
feature = "openapi_redoc",
feature = "openapi_scalar"
),
derive(utoipa::IntoParams, utoipa::ToSchema)
)]
#[derive(Debug, serde::Deserialize, serde::Serialize)]
pub struct PaginationQuery {
#[serde(
default = "default_page_size",
rename = "page_size",
deserialize_with = "deserialize_pagination_filter"
)]
pub page_size: u64,
#[serde(
default = "default_page",
rename = "page",
deserialize_with = "deserialize_pagination_filter"
)]
pub page: u64,
} |
After reviewing this implementation again, I feel we should consider moving it into an initializer maintained in a separate repository. For example: Could you outline the changes needed in Loco to allow managing OpenAPI as an initializer? |
Note: I don't think thats possible. |
Everything is possible. When initializing is public create, you need to load |
@kaplanelad @DenuxPlays @NexVeridian I am actually using it in the initializer right now on my project/work. If you are interested I can add a post about it. I think the real upside of adding a feature in loco would be the code generation aspect as I do a lot of this by hand currently and it can get pretty tedious. If you have any suggestions I would love to help out making the initializer approach work. That being said seaorms --extra-model-derive could fix ToSchema not being added to the generated db entities. |
I am not sure what you mean. So unless you copy the structs it isnt possible right? |
Yeah I also use it as an initializer. It is just a Smoother experience. Just a small note you should Never Serve entities over your api. |
Yes, it can be great. can you also share how you implement it with an example repo? |
related #855
TODO:
enable: true
since that's handled by the feature.merge(Redoc::with_url("/redoc", api.clone()))
openapi
into featureall_openapi
swagger-ui
redoc
scalar
SecurityAddon
impl Modify for SecurityAddon
somewhere, maybe with configsrc/tests_cfg/db.rs:86:1
src/tests_cfg/config.rs
test_from_folder_openapi()
utoipa::path
if possibleget
inget(get_action_openapi)
is still grabbed withroutes!(get_action_openapi)
AppContext
- check thatapi_router.routes(method.with_state::<AppContext>(()))
doesn't break the ctx with.layer
cargo test
is broken withJWT_LOCATION.get_or_init
,nextest
works correctlycargo loco generate controller --openapi
utoipa::path
routes!
macrocc @DenuxPlays