Skip to content

Commit

Permalink
Improved openapi_handler! macro
Browse files Browse the repository at this point in the history
  • Loading branch information
Flowneee committed Jul 17, 2024
1 parent 47470a8 commit 8f3fa43
Show file tree
Hide file tree
Showing 6 changed files with 113 additions and 121 deletions.
1 change: 0 additions & 1 deletion okapi-operation-macro/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ proc-macro = true

[dependencies]
darling = "0.14.3"
lazy_static = "1.4"
proc-macro2 = "1"
quote = "1"
# TODO: bump to 2.x
Expand Down
11 changes: 0 additions & 11 deletions okapi-operation-macro/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
use syn::{parse_macro_input, AttributeArgs, ItemFn};

mod error;
// mod handler;
mod operation;
mod utils;

Expand All @@ -22,13 +21,3 @@ pub fn openapi(
Err(err) => err.write().into(),
}
}

// /// Macro for expanding and binding OpenAPI operation specification
// /// generator to handler or service.
// #[proc_macro]
// pub fn openapi_handler(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
// match handler::openapi_handler(parse_macro_input!(input as Path)) {
// Ok(x) => x.into(),
// Err(err) => err.write().into(),
// }
// }
11 changes: 7 additions & 4 deletions okapi-operation/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,20 @@
All notable changes to this project will be documented in the changelog of the respective crates.
This project follows the [Semantic Versioning standard](https://semver.org/).

## [Unreleased]
### Fixed
- (breaking) Switched to using `indexmap` in place of `hashmap` to make produced specs deterministic.

## [Unreleased] - XXXX-XX-XX
### Add
- shorter version of `openapi_handler!` macro - `oh!`.

### Changed
- `#[request_body]` attribute can be used without braces.
- `#[request_body]` attribute can be used without braces;
- `openapi_handler` in axum integration now accept function with generic parameters;
- switched to using `indexmap` in place of `hashmap` to make produced specs deterministic.

### Fixed
- handler now accept `accept` header `*/*`.


## [0.3.0-rc1] - 2023-12-03
### Notable changes
- `axum` integration updated to be used with axum 0.7. Also this makes library unusable with older versions of `axum`;
Expand Down
136 changes: 38 additions & 98 deletions okapi-operation/src/axum_integration/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,6 @@ pub use self::{
router::{Router, DEFAULT_OPENAPI_PATH},
};

// #[cfg(feature = "macro")]
// #[doc(inline)]
// pub use okapi_operation_macro::openapi_handler;

#[cfg(feature = "yaml")]
mod yaml;

Expand Down Expand Up @@ -80,127 +76,71 @@ pub async fn serve_openapi_spec(spec: State<OpenApi>, headers: HeaderMap) -> Res
}
}

// $(:: <$($gen_param:tt),+>)?

/// Macro for expanding and binding OpenAPI operation specification
/// generator to handler or service.
#[rustfmt::skip]
#[macro_export]
macro_rules! openapi_handler {
// Just name
($fn_name:ident) => {
openapi_handler!(@final $fn_name)
// Entry point
($($va:ident)::+ $(:: <$($gen_param:tt),+>)?) => {
$crate::openapi_handler!(@inner $($va)+; ; $($($gen_param)+)?)
};

// Path
($va:ident $(:: $vb:ident)*) => {
openapi_handler!(@path $va $($vb)*)
};
(@path $va:ident $($vb:ident)+) => {
openapi_handler!(@path $($vb)+; $va)
};
(@path $va:ident $($vb:ident)+; $($acc:ident)::+) => {
openapi_handler!(@path $($vb)+; $($acc)::+ :: $va)
// Each rule have semicolon-separated "arguments"

// Split input into path and function name, consuming left path segment
// and pushing it to accumulator.
//
// Arguments:
// - unprocessed input
// - accumulator
(@inner $va:ident $($vb:ident)+ ; $(:: $acc:ident)*; $($gen_param:tt)*) => {
$crate::openapi_handler!(@inner $($vb)+; $(:: $acc)* :: $va; $($gen_param)*)
};
(@path $va:ident; $($acc:ident)::+) => {
openapi_handler!(@final $va, $($acc)::+)
(@inner $va:ident ; $(:: $acc:ident)*; $($gen_param:tt)*) => {
$crate::openapi_handler!(@final $va; $($acc)::*; $($gen_param)*)
};

(@final $fn_name:ident $(, $($prefix_path_part:ident)::*)?) => {

// Generate code
//
// Arguments:
// - function name
// - path to function
(@final $fn_name:ident ; $($prefix_path_part:ident)::* ; $($gen_param:tt)*) => {
$crate::axum_integration::paste!{
{
#[allow(unused_imports)]
use $crate::axum_integration::{HandlerExt, ServiceExt};

$($($prefix_path_part)::* ::)? $fn_name
.with_openapi($($($prefix_path_part)::* ::)? [<$fn_name __openapi>])
$($prefix_path_part ::)* $fn_name :: <$($gen_param),*>
.with_openapi($($prefix_path_part ::)* [<$fn_name __openapi>])
}
}
};
}

/// Macro for expanding and binding OpenAPI operation specification
/// generator to handler or service (shortcut to [`openapi_handler`])
#[rustfmt::skip]
#[macro_export]
macro_rules! oh {
($($v:tt)+) => {
$crate::openapi_handler!($($v)+)
};

}

/// Macro for expanding and binding OpenAPI operation specification
/// generator to handler or service.
#[rustfmt::skip]
#[macro_export]
#[deprecated = "Use `openapi_handler` instead"]
macro_rules! openapi_service {
($($t:tt)*) => {
($($t:tt)+) => {
{
openapi_handler!($($t)*)
$crate::openapi_handler!($($t)+)
}
}
}

#[cfg(test)]
mod openapi_macro {
use axum::body::Body;
use http::Request;

use super::*;

#[test]
fn openapi_handler_name() {
#[openapi]
async fn handle() {}

let _ = Router::<()>::new().route("/", get(openapi_handler!(handle)));
}

#[test]
fn openapi_handler_path() {
mod outer {
pub mod inner {
use crate::*;

#[openapi]
pub async fn handle() {}
}
}

let _ = Router::<()>::new().route("/", get(openapi_handler!(outer::inner::handle)));
}

#[test]
fn openapi_handler_method() {
struct S {}

impl S {
#[openapi]
async fn handle() {}
}

let _ = Router::<()>::new().route("/", get(openapi_handler!(S::handle)));
}

// #[test]
// fn openapi_handler_typed() {
// #[openapi]
// async fn handle<T>() {}

// let _ = Router::<()>::new().route("/", get(openapi_handler!(handle::<()>)));
// }

// #[test]
// fn openapi_handler_method_typed() {
// struct S<T>(T);

// impl<T> S<T> {
// #[openapi]
// async fn handle<U>() {}
// }

// let _ = Router::<()>::new().route("/", get(openapi_handler!(S::<()>::handle::<()>)));
// }

#[test]
#[allow(deprecated)]
fn openapi_service_name() {
#[openapi]
async fn service(_: Request<Body>) {
unimplemented!()
}

let _ = Router::<()>::new().route("/", get(openapi_service!(service)));
}
}
// tests in tests/axum_integration_macros.rs because of https://github.com/rust-lang/rust/issues/52234
13 changes: 6 additions & 7 deletions okapi-operation/src/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -278,14 +278,13 @@ fn ensure_builder_deterministic() {
for _ in 0..100 {
let mut builder = OpenApiBuilder::new("title", "version");
for i in 0..2 {
builder.operation(
format!("/path/{}", i),
Method::GET,
|_| Ok(Operation::default()),
);
builder.operation(format!("/path/{}", i), Method::GET, |_| {
Ok(Operation::default())
});
}

let spec = builder.build()
let spec = builder
.build()
.map(|x| format!("{:?}", x))
.expect("Failed to build spec");
built_specs.push(spec);
Expand All @@ -295,4 +294,4 @@ fn ensure_builder_deterministic() {
for i in 1..built_specs.len() {
assert_eq!(built_specs[i - 1], built_specs[i]);
}
}
}
62 changes: 62 additions & 0 deletions okapi-operation/tests/axum_integration_macros.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
#[cfg(feature = "axum-integration")]
mod tests {
use axum::body::Body;
use http::Request;
use okapi_operation::{
axum_integration::{get, Router},
oh, openapi, openapi_handler, openapi_service, Components, ToResponses,
};

#[test]
fn openapi_handler_name() {
#[openapi]
async fn handle() {}

let _ = Router::<()>::new().route("/", get(oh!(handle)));
}

#[test]
fn openapi_handler_path() {
mod outer {
pub mod inner {
use okapi_operation::*;

#[openapi]
pub async fn handle() {}
}
}

let _ = Router::<()>::new().route("/", get(openapi_handler!(outer::inner::handle)));
}

#[test]
fn openapi_handler_method() {
struct S {}

impl S {
#[openapi]
async fn handle() {}
}

let _ = Router::<()>::new().route("/", get(openapi_handler!(S::handle)));
}

#[test]
fn openapi_handler_typed() {
#[openapi]
async fn handle<T>() {}

let _ = Router::<()>::new().route("/", get(openapi_handler!(handle::<()>)));
}

#[test]
#[allow(deprecated)]
fn openapi_service_name() {
#[openapi]
async fn service(_: Request<Body>) {
unimplemented!()
}

let _ = Router::<()>::new().route("/", get(openapi_service!(service)));
}
}

0 comments on commit 8f3fa43

Please sign in to comment.