okapi_operation/axum_integration/
handler_traits.rs1use std::marker::PhantomData;
2
3use axum::{extract::Request, handler::Handler, response::IntoResponse};
4use tower::Service;
5
6use crate::OperationGenerator;
7
8pub struct HandlerWithOperation<H, T, S>
10where
11 H: Handler<T, S>,
12{
13 pub(super) handler: H,
14 pub(super) operation: Option<OperationGenerator>,
15 _t: PhantomData<T>,
16 _s: PhantomData<S>,
17}
18
19impl<H, T, S> From<H> for HandlerWithOperation<H, T, S>
20where
21 H: Handler<T, S>,
22{
23 fn from(value: H) -> Self {
24 Self {
25 handler: value,
26 operation: None,
27 _t: PhantomData,
28 _s: PhantomData,
29 }
30 }
31}
32
33impl<H, T, S> HandlerWithOperation<H, T, S>
34where
35 H: Handler<T, S>,
36{
37 pub fn new(handler: H, operation: Option<OperationGenerator>) -> Self {
38 Self {
39 handler,
40 operation,
41 _t: PhantomData,
42 _s: PhantomData,
43 }
44 }
45}
46
47pub trait HandlerExt<H, T, S>
49where
50 H: Handler<T, S>,
51{
52 fn into_handler_with_operation(self) -> HandlerWithOperation<H, T, S>;
53
54 fn with_openapi(self, operation: OperationGenerator) -> HandlerWithOperation<H, T, S>
56 where
57 Self: Sized,
58 {
59 let mut h = self.into_handler_with_operation();
60 h.operation = Some(operation);
61 h
62 }
63}
64
65impl<H, T, S> HandlerExt<H, T, S> for H
66where
67 H: Handler<T, S>,
68{
69 fn into_handler_with_operation(self) -> HandlerWithOperation<H, T, S> {
70 HandlerWithOperation::new(self, None)
71 }
72}
73
74impl<H, T, S> HandlerExt<H, T, S> for HandlerWithOperation<H, T, S>
75where
76 H: Handler<T, S>,
77{
78 fn into_handler_with_operation(self) -> HandlerWithOperation<H, T, S> {
79 self
80 }
81}
82
83pub struct ServiceWithOperation<Svc, E>
85where
86 Svc: Service<Request, Error = E> + Clone + Send + 'static,
87 Svc::Response: IntoResponse + 'static,
88 Svc::Future: Send + 'static,
89{
90 pub(crate) service: Svc,
91 pub(crate) operation: Option<OperationGenerator>,
92 _e: PhantomData<E>,
93}
94
95impl<Svc, E> ServiceWithOperation<Svc, E>
96where
97 Svc: Service<Request, Error = E> + Clone + Send + 'static,
98 Svc::Response: IntoResponse + 'static,
99 Svc::Future: Send + 'static,
100{
101 pub(crate) fn new(service: Svc, operation: Option<OperationGenerator>) -> Self {
102 Self {
103 service,
104 operation,
105 _e: PhantomData,
106 }
107 }
108}
109
110impl<Svc, E> From<Svc> for ServiceWithOperation<Svc, E>
111where
112 Svc: Service<Request, Error = E> + Clone + Send + 'static,
113 Svc::Response: IntoResponse + 'static,
114 Svc::Future: Send + 'static,
115{
116 fn from(value: Svc) -> Self {
117 Self::new(value, None)
118 }
119}
120
121pub trait ServiceExt<Svc, E>
123where
124 Svc: Service<Request, Error = E> + Clone + Send + 'static,
125 Svc::Response: IntoResponse + 'static,
126 Svc::Future: Send + 'static,
127{
128 fn into_service_with_operation(self) -> ServiceWithOperation<Svc, E>
129where;
130
131 fn with_openapi(self, operation: OperationGenerator) -> ServiceWithOperation<Svc, E>
133 where
134 Self: Sized,
135 {
136 let mut h = self.into_service_with_operation();
137 h.operation = Some(operation);
138 h
139 }
140}
141
142impl<Svc, E> ServiceExt<Svc, E> for Svc
143where
144 Svc: Service<Request, Error = E> + Clone + Send + 'static,
145 Svc::Response: IntoResponse + 'static,
146 Svc::Future: Send + 'static,
147{
148 fn into_service_with_operation(self) -> ServiceWithOperation<Svc, E> {
149 ServiceWithOperation::new(self, None)
150 }
151}
152
153impl<Svc, E> ServiceExt<Svc, E> for ServiceWithOperation<Svc, E>
154where
155 Svc: Service<Request, Error = E> + Clone + Send + 'static,
156 Svc::Response: IntoResponse + 'static,
157 Svc::Future: Send + 'static,
158{
159 fn into_service_with_operation(self) -> ServiceWithOperation<Svc, E> {
160 self
161 }
162}
163
164#[cfg(test)]
165mod tests {
166 #![allow(clippy::let_underscore_future)]
167
168 use std::convert::Infallible;
169
170 use axum::{
171 body::Body, extract::Request, http::Method, response::Response, routing::MethodFilter,
172 };
173 use okapi::openapi3::Operation;
174 use tokio::net::TcpListener;
175 use tower::service_fn;
176
177 use super::*;
178 use crate::{
179 BuilderOptions, Components,
180 axum_integration::{MethodRouter, Router},
181 };
182
183 fn openapi_generator(
184 _: &mut Components,
185 _: &BuilderOptions,
186 ) -> Result<Operation, anyhow::Error> {
187 unimplemented!()
188 }
189
190 #[test]
191 fn handler_with_operation() {
192 async fn handler() {}
193
194 let mr: MethodRouter = MethodRouter::new()
195 .on(
196 MethodFilter::GET,
197 (|| async {}).with_openapi(openapi_generator),
198 )
199 .on(
200 MethodFilter::POST,
201 handler
202 .with_openapi(openapi_generator)
203 .with_openapi(openapi_generator),
204 )
205 .on(MethodFilter::PUT, handler)
206 .on(MethodFilter::DELETE, || async {});
207 let (app, ops) = Router::new().route("/", mr).into_parts();
208 assert!(ops.get("/", &Method::GET).is_some());
209 assert!(ops.get("/", &Method::POST).is_some());
210
211 let make_service = app.into_make_service();
212 let _ = async move {
213 let listener = TcpListener::bind("").await.unwrap();
214 axum::serve(listener, make_service).await.unwrap()
215 };
216 }
217
218 #[test]
219 fn service_with_operation() {
220 async fn service(_request: Request) -> Result<Response<Body>, Infallible> {
221 Ok::<_, Infallible>(Response::new(Body::empty()))
222 }
223
224 let service2 = service_fn(|_request: Request| async {
225 Ok::<_, Infallible>(Response::new(Body::empty()))
226 });
227
228 let mr: MethodRouter = MethodRouter::new()
229 .on_service(
230 MethodFilter::GET,
231 service_fn(service).with_openapi(openapi_generator),
232 )
233 .on_service(
234 MethodFilter::POST,
235 service2
236 .with_openapi(openapi_generator)
237 .with_openapi(openapi_generator),
238 )
239 .on_service(MethodFilter::PUT, service_fn(service))
240 .on_service(MethodFilter::DELETE, service2);
241 let (app, ops) = Router::new().route("/", mr).into_parts();
242 assert!(ops.get("/", &Method::GET).is_some());
243 assert!(ops.get("/", &Method::POST).is_some());
244
245 let make_service = app.into_make_service();
246 let _ = async move {
247 let listener = TcpListener::bind("").await.unwrap();
248 axum::serve(listener, make_service).await.unwrap()
249 };
250 }
251}