-
-
Notifications
You must be signed in to change notification settings - Fork 383
feat: OpenAPI integration #984
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
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: |
|
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.rspragnate/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? |
|
|
There are some issues with some of the options, like creating code generation in the future might be harder, here are some options for converting this pr to a initializer: option 1 - probably not possibleGet the original function from a option 2have the user manually add the data to the initializer, similar to #855 # controllers::auth
pub fn api_routes() -> OpenApiRouter<AppContext> {
OpenApiRouter::new().routes(routes!(register, verify, login, forgot, reset))
}then pass let (_, api) = OpenApiRouter::with_openapi(ApiDoc::openapi())
.merge(controllers::auth::api_routes())
.merge(controllers::responses::api_routes())
.split_for_parts();option 3Merge these two into loco:
option 4This comment is also a good option but has these drawbacks, as stated in the comment:
|
|
All the options look legitimate to me when we move them to the initializer. WDYT? |
|
What do you mean? We cannot convert this feature to an initializer at least not without dropping 50% of the features. |
|
Could you please clarify which features are going to be supported and which ones won’t,and why? I’d prefer to support this feature as an initializer (that is supported by Loco teams) and keep them separate from the base Loco codebase. |
There are probably some other things I forgot but the integration wouldn't feel like an integration |
Why not make the internal Loco route responses public so they can be accessed externally? This way, the initializer can retrieve the app context, which provides access to everything required by OpenAPI.
Same for here, we can expose it |
|
I am not sure what you mean. How can you "expose" something that needs to replaced when you want to use it with utoipa? Also the struct is already exposed but the derive(Schema) is missing. |
option 2:this option removes automatic schema collection option 3:this option has automatic schema collection the only things that can be extracted is: the config, the tests, and this section app_routes.rs#L239-L290 option 1:
this is talking about option 1, which is probably not possible or I can't figure it out, maybe if you do something that looks bad like this: |
|
I was very hopefully following this thread a couple of weeks ago - I think this would be an incredible feature for a loco to implement out of the box. (Auto /docs was always a driving reason for me to choose fastapi in the python ecosystem.) Not sure how qualified I am, but would be more than happy to give this a stab if needed. |
|
@kaplanelad The initializer is working, with automatic schema collection! some things from this pr still need to get merged into loco |
related #855
TODO:
enable: truesince that's handled by the feature.merge(Redoc::with_url("/redoc", api.clone()))openapiinto featureall_openapiswagger-uiredocscalarSecurityAddonimpl Modify for SecurityAddonsomewhere, maybe with configsrc/tests_cfg/db.rs:86:1src/tests_cfg/config.rstest_from_folder_openapi()utoipa::pathif possiblegetinget(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.layercargo testis broken withJWT_LOCATION.get_or_init,nextestworks correctlycargo loco generate controller --openapiutoipa::pathroutes!macrocc @DenuxPlays