1#![allow(clippy::return_self_not_must_use)]
2
3extern crate actix_service2 as actix_service;
5extern crate actix_web4 as actix_web;
6
7pub use actix_web::{
8 web::{
9 block, service, to, Bytes, BytesMut, Data, Form, FormConfig, Json, JsonConfig, Path,
10 PathConfig, Payload, PayloadConfig, Query, QueryConfig, ReqData,
11 },
12 HttpRequest, HttpResponse,
13};
14
15use crate::Mountable;
16use actix_service::ServiceFactory;
17use actix_web::{
18 body::MessageBody,
19 dev::{AppService, Handler, HttpServiceFactory, ServiceRequest, ServiceResponse, Transform},
20 guard::Guard,
21 http::Method,
22 Error, FromRequest, Responder,
23};
24use paperclip_core::v2::{
25 models::{
26 DefaultOperationRaw, DefaultPathItemRaw, DefaultSchemaRaw, HttpMethod, SecurityScheme,
27 },
28 schema::Apiv2Operation,
29};
30
31use std::{collections::BTreeMap, fmt::Debug, future::Future, mem};
32
33const METHODS: &[Method] = &[
34 Method::GET,
35 Method::PUT,
36 Method::POST,
37 Method::DELETE,
38 Method::OPTIONS,
39 Method::HEAD,
40 Method::PATCH,
41];
42
43pub struct Resource<R = actix_web::Resource> {
47 path: String,
48 operations: BTreeMap<HttpMethod, DefaultOperationRaw>,
49 definitions: BTreeMap<String, DefaultSchemaRaw>,
50 security: BTreeMap<String, SecurityScheme>,
51 inner: R,
52}
53
54impl Resource {
55 pub fn new(path: &str) -> Resource {
57 Resource {
58 path: path.into(),
59 operations: BTreeMap::new(),
60 definitions: BTreeMap::new(),
61 security: BTreeMap::new(),
62 inner: actix_web::Resource::new(path),
63 }
64 }
65}
66
67impl<T, B> HttpServiceFactory for Resource<actix_web::Resource<T>>
68where
69 T: ServiceFactory<
70 ServiceRequest,
71 Config = (),
72 Response = ServiceResponse<B>,
73 Error = Error,
74 InitError = (),
75 > + 'static,
76 B: MessageBody + 'static,
77{
78 fn register(self, config: &mut AppService) {
79 self.inner.register(config)
80 }
81}
82
83impl<T> Mountable for Resource<T> {
84 fn path(&self) -> &str {
85 &self.path
86 }
87
88 fn operations(&mut self) -> BTreeMap<HttpMethod, DefaultOperationRaw> {
89 mem::take(&mut self.operations)
90 }
91
92 fn definitions(&mut self) -> BTreeMap<String, DefaultSchemaRaw> {
93 mem::take(&mut self.definitions)
94 }
95
96 fn security_definitions(&mut self) -> BTreeMap<String, SecurityScheme> {
97 mem::take(&mut self.security)
98 }
99}
100
101impl<T> Resource<actix_web::Resource<T>>
102where
103 T: ServiceFactory<ServiceRequest, Config = (), Error = Error, InitError = ()>,
104{
105 pub fn name(mut self, name: &str) -> Self {
109 self.inner = self.inner.name(name);
110 self
111 }
112
113 pub fn guard<G: Guard + 'static>(mut self, guard: G) -> Self {
117 self.inner = self.inner.guard(guard);
118 self
119 }
120
121 pub fn route(mut self, route: Route) -> Self {
123 let w = RouteWrapper::from(&self.path, route);
124 self.operations.extend(w.operations);
125 self.definitions.extend(w.definitions);
126 SecurityScheme::append_map(w.security, &mut self.security);
127 self.inner = self.inner.route(w.inner);
128 self
129 }
130
131 pub fn app_data<U: 'static>(mut self, data: U) -> Self {
135 let w = self.inner.app_data(data);
136 self.inner = w;
137 self
138 }
139
140 pub fn to<F, Args>(mut self, handler: F) -> Self
142 where
143 F: Handler<Args>,
144 Args: FromRequest + 'static,
145 F::Output: Responder + 'static,
146 F::Future: Apiv2Operation,
147 {
148 self.update_from_handler::<F::Future>();
149 self.inner = self.inner.to(handler);
150 self
151 }
152
153 pub fn wrap<M, B>(
157 self,
158 mw: M,
159 ) -> Resource<
160 actix_web::Resource<
161 impl ServiceFactory<
162 ServiceRequest,
163 Config = (),
164 Response = ServiceResponse<B>,
165 Error = Error,
166 InitError = (),
167 >,
168 >,
169 >
170 where
171 B: MessageBody,
172 M: Transform<
173 T::Service,
174 ServiceRequest,
175 Response = ServiceResponse<B>,
176 Error = Error,
177 InitError = (),
178 > + 'static,
179 {
180 Resource {
181 path: self.path,
182 operations: self.operations,
183 definitions: self.definitions,
184 security: self.security,
185 inner: self.inner.wrap(mw),
186 }
187 }
188
189 pub fn wrap_fn<F, R, B>(
193 self,
194 mw: F,
195 ) -> Resource<
196 actix_web::Resource<
197 impl ServiceFactory<
198 ServiceRequest,
199 Config = (),
200 Response = ServiceResponse<B>,
201 Error = Error,
202 InitError = (),
203 >,
204 >,
205 >
206 where
207 B: MessageBody,
208 F: Fn(ServiceRequest, &T::Service) -> R + Clone + 'static,
209 R: Future<Output = Result<ServiceResponse<B>, Error>>,
210 {
211 Resource {
212 path: self.path,
213 operations: self.operations,
214 definitions: self.definitions,
215 security: self.security,
216 inner: self.inner.wrap_fn(mw),
217 }
218 }
219
220 pub fn default_service<F, U>(mut self, f: F) -> Self
224 where
225 F: actix_service::IntoServiceFactory<U, ServiceRequest>,
226 U: ServiceFactory<
227 ServiceRequest,
228 Config = (),
229 Response = ServiceResponse,
230 Error = Error,
231 InitError = (),
232 > + 'static,
233 U::InitError: Debug,
234 {
235 self.inner = self.inner.default_service(f);
236 self
237 }
238
239 fn update_from_handler<U>(&mut self)
241 where
242 U: Apiv2Operation,
243 {
244 let mut op = U::operation();
245 if U::is_visible() {
246 op.set_parameter_names_from_path_template(&self.path);
247 for method in METHODS {
248 self.operations.insert(method.into(), op.clone());
249 }
250
251 self.definitions.extend(U::definitions());
252 SecurityScheme::append_map(U::security_definitions(), &mut self.security);
253 }
254 }
255}
256
257pub fn resource(path: &str) -> Resource {
259 Resource::new(path)
260}
261
262pub struct Scope<S = actix_web::Scope> {
266 path: String,
267 path_map: BTreeMap<String, DefaultPathItemRaw>,
268 definitions: BTreeMap<String, DefaultSchemaRaw>,
269 security: BTreeMap<String, SecurityScheme>,
270 inner: Option<S>,
271}
272
273impl Scope {
274 pub fn new(path: &str) -> Self {
276 Scope {
277 path: path.into(),
278 path_map: BTreeMap::new(),
279 definitions: BTreeMap::new(),
280 security: BTreeMap::new(),
281 inner: Some(actix_web::Scope::new(path)),
282 }
283 }
284}
285
286impl<T, B> HttpServiceFactory for Scope<actix_web::Scope<T>>
287where
288 T: ServiceFactory<
289 ServiceRequest,
290 Config = (),
291 Response = ServiceResponse<B>,
292 Error = Error,
293 InitError = (),
294 > + 'static,
295 B: MessageBody + 'static,
296{
297 fn register(self, config: &mut AppService) {
298 if let Some(s) = self.inner {
299 s.register(config);
300 }
301 }
302}
303
304impl<T> Scope<actix_web::Scope<T>>
305where
306 T: ServiceFactory<ServiceRequest, Config = (), Error = Error, InitError = ()>,
307{
308 pub fn guard<G: Guard + 'static>(mut self, guard: G) -> Self {
312 self.inner = self.inner.take().map(|s| s.guard(guard));
313 self
314 }
315
316 pub fn app_data<U: 'static>(mut self, data: U) -> Self {
320 self.inner = self.inner.take().map(|s| s.app_data(data));
321 self
322 }
323
324 pub fn configure<F>(mut self, f: F) -> Self
326 where
327 F: FnOnce(&mut ServiceConfig),
328 {
329 self.inner = self.inner.take().map(|s| {
330 s.configure(|c| {
331 let mut cfg = ServiceConfig::from(c);
332 f(&mut cfg);
333 self.update_from_mountable(&mut cfg);
334 })
335 });
336 self
337 }
338
339 pub fn service<F>(mut self, mut factory: F) -> Self
341 where
342 F: Mountable + HttpServiceFactory + 'static,
343 {
344 self.update_from_mountable(&mut factory);
345 self.inner = self.inner.take().map(|s| s.service(factory));
346 self
347 }
348
349 pub fn route(mut self, path: &str, route: Route) -> Self {
351 let mut w = RouteWrapper::from(path, route);
352 self.update_from_mountable(&mut w);
353 self.inner = self.inner.take().map(|s| s.route(path, w.inner));
354 self
355 }
356
357 pub fn default_service<F, U>(mut self, f: F) -> Self
361 where
362 F: actix_service::IntoServiceFactory<U, ServiceRequest>,
363 U: ServiceFactory<
364 ServiceRequest,
365 Config = (),
366 Response = ServiceResponse,
367 Error = Error,
368 InitError = (),
369 > + 'static,
370 U::InitError: Debug,
371 {
372 self.inner = self.inner.map(|s| s.default_service(f));
373 self
374 }
375
376 pub fn wrap<M, B>(
380 mut self,
381 mw: M,
382 ) -> Scope<
383 actix_web::Scope<
384 impl ServiceFactory<
385 ServiceRequest,
386 Config = (),
387 Response = ServiceResponse<B>,
388 Error = Error,
389 InitError = (),
390 >,
391 >,
392 >
393 where
394 M: Transform<
395 T::Service,
396 ServiceRequest,
397 Response = ServiceResponse<B>,
398 Error = Error,
399 InitError = (),
400 > + 'static,
401 B: MessageBody,
402 {
403 Scope {
404 path: self.path,
405 path_map: self.path_map,
406 definitions: self.definitions,
407 security: self.security,
408 inner: self.inner.take().map(|s| s.wrap(mw)),
409 }
410 }
411
412 pub fn wrap_fn<F, R>(
416 mut self,
417 mw: F,
418 ) -> Scope<
419 actix_web::Scope<
420 impl ServiceFactory<
421 ServiceRequest,
422 Config = (),
423 Response = ServiceResponse,
424 Error = Error,
425 InitError = (),
426 >,
427 >,
428 >
429 where
430 F: Fn(ServiceRequest, &T::Service) -> R + Clone + 'static,
431 R: Future<Output = Result<ServiceResponse, Error>>,
432 {
433 Scope {
434 path: self.path,
435 path_map: self.path_map,
436 definitions: self.definitions,
437 security: self.security,
438 inner: self.inner.take().map(|s| s.wrap_fn(mw)),
439 }
440 }
441
442 fn update_from_mountable<M>(&mut self, factory: &mut M)
444 where
445 M: Mountable,
446 {
447 self.definitions.extend(factory.definitions());
448 let mut path_map = BTreeMap::new();
449 factory.update_operations(&mut path_map);
450 for (path, mut map) in path_map {
451 let p = if !self.path.ends_with('/') && !path.starts_with('/') {
452 self.path.clone() + "/" + &path
453 } else {
454 self.path.clone() + &path
455 };
456 for op in map.methods.values_mut() {
457 op.set_parameter_names_from_path_template(&p);
458 }
459
460 if let Some(existing) = self.path_map.get_mut(&p) {
461 existing.methods.append(&mut map.methods);
462 existing.parameters.append(&mut map.parameters);
463 } else {
464 self.path_map.insert(p.clone(), map);
465 }
466 }
467
468 SecurityScheme::append_map(factory.security_definitions(), &mut self.security);
469 }
470}
471
472impl<T> Mountable for Scope<T> {
473 fn path(&self) -> &str {
474 unimplemented!("Scope has multiple paths. Use `update_operations` object instead.");
475 }
476
477 fn operations(&mut self) -> BTreeMap<HttpMethod, DefaultOperationRaw> {
478 unimplemented!("Scope has multiple operation maps. Use `update_operations` object instead.")
479 }
480
481 fn security_definitions(&mut self) -> BTreeMap<String, SecurityScheme> {
482 mem::take(&mut self.security)
483 }
484
485 fn definitions(&mut self) -> BTreeMap<String, DefaultSchemaRaw> {
486 mem::take(&mut self.definitions)
487 }
488
489 fn update_operations(&mut self, map: &mut BTreeMap<String, DefaultPathItemRaw>) {
490 for (path, item) in mem::take(&mut self.path_map) {
491 let op_map = map.entry(path).or_default();
492 op_map.methods.extend(item.methods);
493 }
494 }
495}
496
497pub fn scope(path: &str) -> Scope {
499 Scope::new(path)
500}
501
502pub struct Route {
506 method: Option<HttpMethod>,
507 operation: Option<DefaultOperationRaw>,
508 definitions: BTreeMap<String, DefaultSchemaRaw>,
509 security: BTreeMap<String, SecurityScheme>,
510 inner: actix_web::Route,
511}
512
513impl ServiceFactory<ServiceRequest> for Route {
514 type Config = ();
515 type Error = Error;
516 type InitError = ();
517 type Service = <actix_web::Route as ServiceFactory<ServiceRequest>>::Service;
518 type Future = <actix_web::Route as ServiceFactory<ServiceRequest>>::Future;
519 type Response =
520 <<actix_web::Route as ServiceFactory<ServiceRequest>>::Service as actix_service::Service<
521 ServiceRequest,
522 >>::Response;
523
524 #[allow(clippy::unit_arg)]
525 fn new_service(&self, cfg: Self::Config) -> Self::Future {
526 self.inner.new_service(cfg)
527 }
528}
529
530impl Route {
531 #[allow(clippy::new_without_default)]
533 pub fn new() -> Route {
534 Route {
535 method: None,
536 operation: None,
537 definitions: BTreeMap::new(),
538 security: BTreeMap::new(),
539 inner: actix_web::Route::new(),
540 }
541 }
542
543 pub fn method(mut self, method: Method) -> Self {
545 self.method = Some(HttpMethod::from(&method));
546 self.inner = self.inner.method(method);
547 self
548 }
549
550 pub fn guard<G: Guard + 'static>(mut self, guard: G) -> Self {
554 self.inner = self.inner.guard(guard);
555 self
556 }
557
558 pub fn to<F, Args>(mut self, handler: F) -> Self
560 where
561 F: Handler<Args>,
562 Args: FromRequest + 'static,
563 F::Output: Responder + 'static,
564 F::Future: Apiv2Operation,
565 {
566 if F::Future::is_visible() {
567 self.operation = Some(F::Future::operation());
568 self.definitions = F::Future::definitions();
569 self.security = F::Future::security_definitions();
570 }
571 self.inner = self.inner.to(handler);
572 self
573 }
574}
575
576pub fn method(method: Method) -> Route {
578 Route::new().method(method)
579}
580
581pub fn get() -> Route {
583 method(Method::GET)
584}
585
586pub fn put() -> Route {
588 method(Method::PUT)
589}
590
591pub fn post() -> Route {
593 method(Method::POST)
594}
595
596pub fn patch() -> Route {
598 method(Method::PATCH)
599}
600
601pub fn delete() -> Route {
603 method(Method::DELETE)
604}
605
606pub fn options() -> Route {
608 method(Method::OPTIONS)
609}
610
611pub fn head() -> Route {
613 method(Method::HEAD)
614}
615
616pub(crate) struct RouteWrapper<S> {
624 path: S,
625 pub(crate) operations: BTreeMap<HttpMethod, DefaultOperationRaw>,
626 pub(crate) definitions: BTreeMap<String, DefaultSchemaRaw>,
627 pub(crate) security: BTreeMap<String, SecurityScheme>,
628 pub(crate) inner: actix_web::Route,
629}
630
631impl<S> RouteWrapper<S>
632where
633 S: AsRef<str>,
634{
635 pub(crate) fn from(path: S, route: Route) -> Self {
636 let mut operations = BTreeMap::new();
637 if let Some(mut op) = route.operation {
638 op.set_parameter_names_from_path_template(path.as_ref());
639
640 if let Some(meth) = route.method {
641 operations.insert(meth, op);
642 } else {
643 for method in METHODS {
644 operations.insert(method.into(), op.clone());
645 }
646 }
647 }
648
649 RouteWrapper {
650 path,
651 operations,
652 definitions: route.definitions,
653 security: route.security,
654 inner: route.inner,
655 }
656 }
657}
658
659impl<S> Mountable for RouteWrapper<S>
660where
661 S: AsRef<str>,
662{
663 fn path(&self) -> &str {
664 self.path.as_ref()
665 }
666
667 fn operations(&mut self) -> BTreeMap<HttpMethod, DefaultOperationRaw> {
668 mem::take(&mut self.operations)
669 }
670
671 fn security_definitions(&mut self) -> BTreeMap<String, SecurityScheme> {
672 mem::take(&mut self.security)
673 }
674
675 fn definitions(&mut self) -> BTreeMap<String, DefaultSchemaRaw> {
676 mem::take(&mut self.definitions)
677 }
678}
679
680pub struct ServiceConfig<'a> {
684 path_map: BTreeMap<String, DefaultPathItemRaw>,
685 definitions: BTreeMap<String, DefaultSchemaRaw>,
686 security: BTreeMap<String, SecurityScheme>,
687 inner: &'a mut actix_web::web::ServiceConfig,
688}
689
690impl<'a> From<&'a mut actix_web::web::ServiceConfig> for ServiceConfig<'a> {
691 fn from(cfg: &'a mut actix_web::web::ServiceConfig) -> Self {
692 ServiceConfig {
693 path_map: BTreeMap::new(),
694 definitions: BTreeMap::new(),
695 security: BTreeMap::new(),
696 inner: cfg,
697 }
698 }
699}
700
701impl Mountable for ServiceConfig<'_> {
702 fn path(&self) -> &str {
703 unimplemented!("ServiceConfig has multiple paths. Use `update_operations` object instead.");
704 }
705
706 fn operations(&mut self) -> BTreeMap<HttpMethod, DefaultOperationRaw> {
707 unimplemented!(
708 "ServiceConfig has multiple operation maps. Use `update_operations` object instead."
709 )
710 }
711
712 fn security_definitions(&mut self) -> BTreeMap<String, SecurityScheme> {
713 mem::take(&mut self.security)
714 }
715
716 fn definitions(&mut self) -> BTreeMap<String, DefaultSchemaRaw> {
717 mem::take(&mut self.definitions)
718 }
719
720 fn update_operations(&mut self, map: &mut BTreeMap<String, DefaultPathItemRaw>) {
721 for (path, item) in mem::take(&mut self.path_map) {
722 let op_map = map.entry(path).or_default();
723 op_map.methods.extend(item.methods);
724 }
725 }
726}
727
728impl ServiceConfig<'_> {
729 pub fn route(&mut self, path: &str, route: Route) -> &mut Self {
731 let mut w = RouteWrapper::from(path, route);
732 self.definitions.extend(w.definitions());
733 w.update_operations(&mut self.path_map);
734 SecurityScheme::append_map(w.security, &mut self.security);
735 self.inner.route(path, w.inner);
736 self
737 }
738
739 pub fn service<F>(&mut self, mut factory: F) -> &mut Self
741 where
742 F: Mountable + HttpServiceFactory + 'static,
743 {
744 self.definitions.extend(factory.definitions());
745 factory.update_operations(&mut self.path_map);
746 SecurityScheme::append_map(factory.security_definitions(), &mut self.security);
747 self.inner.service(factory);
748 self
749 }
750
751 pub fn external_resource<N, U>(&mut self, name: N, url: U) -> &mut Self
755 where
756 N: AsRef<str>,
757 U: AsRef<str>,
758 {
759 self.inner.external_resource(name, url);
760 self
761 }
762
763 pub fn app_data<U: 'static>(&mut self, data: U) -> &mut Self {
767 self.inner.app_data(data);
768 self
769 }
770}