1use core::fmt;
6use std::cell::{Cell, RefCell};
7
8use actix_service::{IntoServiceFactory, ServiceFactory, Transform};
9use actix_web::body::MessageBody;
10use actix_web::dev::{AppService, HttpServiceFactory, ServiceRequest, ServiceResponse};
11use actix_web::guard::Guard;
12use actix_web::{Error, Route};
13
14use crate::service_config::ServiceConfig;
15use crate::OpenApiFactory;
16
17pub struct Scope<T>(
22 actix_web::Scope<T>,
23 RefCell<utoipa::openapi::OpenApi>,
24 Cell<String>,
25);
26
27impl<T> From<actix_web::Scope<T>> for Scope<T>
28where
29 T: ServiceFactory<ServiceRequest, Config = (), Error = Error, InitError = ()>,
30{
31 fn from(value: actix_web::Scope<T>) -> Self {
32 Self(
33 value,
34 RefCell::new(utoipa::openapi::OpenApiBuilder::new().build()),
35 Cell::new(String::new()),
36 )
37 }
38}
39
40impl<'s, T: ServiceFactory<ServiceRequest, Config = (), Error = Error, InitError = ()>>
41 From<&'s str> for Scope<T>
42where
43 Scope<T>: std::convert::From<actix_web::Scope>,
44{
45 fn from(value: &'s str) -> Self {
46 let scope = actix_web::Scope::new(value);
47 let s: Scope<T> = scope.into();
48 Scope(s.0, s.1, Cell::new(String::from(value)))
49 }
50}
51
52pub fn scope<
77 I: Into<Scope<T>>,
78 T: ServiceFactory<ServiceRequest, Config = (), Error = Error, InitError = ()>,
79>(
80 scope: I,
81) -> Scope<T> {
82 scope.into()
83}
84
85impl<T> Scope<T>
86where
87 T: ServiceFactory<ServiceRequest, Config = (), Error = Error, InitError = ()>,
88{
89 pub fn guard<G: Guard + 'static>(self, guard: G) -> Self {
91 let scope = self.0.guard(guard);
92 Self(scope, self.1, self.2)
93 }
94
95 pub fn app_data<U: 'static>(self, data: U) -> Self {
97 Self(self.0.app_data(data), self.1, self.2)
98 }
99
100 pub fn wrap<M, B>(
102 self,
103 middleware: M,
104 ) -> Scope<
105 impl ServiceFactory<
106 ServiceRequest,
107 Config = (),
108 Response = ServiceResponse<B>,
109 Error = Error,
110 InitError = (),
111 >,
112 >
113 where
114 M: Transform<
115 T::Service,
116 ServiceRequest,
117 Response = ServiceResponse<B>,
118 Error = Error,
119 InitError = (),
120 > + 'static,
121 B: MessageBody,
122 {
123 let scope = self.0.wrap(middleware);
124 Scope(scope, self.1, self.2)
125 }
126
127 pub fn configure<F>(self, cfg_fn: F) -> Self
131 where
132 F: FnOnce(&mut ServiceConfig),
133 {
134 let mut openapi = self.1.borrow_mut();
135
136 let scope = self.0.configure(|config| {
137 let mut service_config = ServiceConfig::new(config);
138
139 cfg_fn(&mut service_config);
140
141 let other_paths = service_config.1.take();
142 openapi.paths.merge(other_paths);
143 let schemas = service_config.2.take();
144 let components = openapi
145 .components
146 .get_or_insert(utoipa::openapi::Components::new());
147 components.schemas.extend(schemas);
148 });
149 drop(openapi);
150
151 Self(scope, self.1, self.2)
152 }
153
154 pub fn service<F>(self, factory: F) -> Self
158 where
159 F: HttpServiceFactory + OpenApiFactory + 'static,
160 {
161 let mut schemas = Vec::<(
162 String,
163 utoipa::openapi::RefOr<utoipa::openapi::schema::Schema>,
164 )>::new();
165 {
166 let mut openapi = self.1.borrow_mut();
167 let other_paths = factory.paths();
168 factory.schemas(&mut schemas);
169 openapi.paths.merge(other_paths);
170 let components = openapi
171 .components
172 .get_or_insert(utoipa::openapi::Components::new());
173 components.schemas.extend(schemas);
174 }
175
176 let app = self.0.service(factory);
177
178 Self(app, self.1, self.2)
179 }
180
181 pub fn route(self, path: &str, route: Route) -> Self {
183 Self(self.0.route(path, route), self.1, self.2)
184 }
185
186 pub fn default_service<F, U>(self, f: F) -> Self
188 where
189 F: IntoServiceFactory<U, ServiceRequest>,
190 U: ServiceFactory<
191 ServiceRequest,
192 Config = (),
193 Response = ServiceResponse,
194 Error = actix_web::Error,
195 > + 'static,
196 U::InitError: fmt::Debug,
197 {
198 Self(self.0.default_service(f), self.1, self.2)
199 }
200
201 pub fn map<
205 F: FnOnce(actix_web::Scope<T>) -> actix_web::Scope<NF>,
206 NF: ServiceFactory<ServiceRequest, Config = (), Error = Error, InitError = ()>,
207 >(
208 self,
209 op: F,
210 ) -> Scope<NF> {
211 let scope = op(self.0);
212 Scope(scope, self.1, self.2)
213 }
214}
215
216impl<T, B> HttpServiceFactory for Scope<T>
217where
218 T: ServiceFactory<
219 ServiceRequest,
220 Config = (),
221 Response = ServiceResponse<B>,
222 Error = Error,
223 InitError = (),
224 > + 'static,
225 B: MessageBody + 'static,
226{
227 fn register(self, config: &mut AppService) {
228 let Scope(scope, ..) = self;
229 scope.register(config);
230 }
231}
232
233impl<T> OpenApiFactory for Scope<T> {
234 fn paths(&self) -> utoipa::openapi::path::Paths {
235 let prefix = self.2.take();
236 let mut openapi = self.1.borrow_mut();
237 let mut paths = std::mem::take(&mut openapi.paths);
238
239 let prefixed_paths = paths
240 .paths
241 .into_iter()
242 .map(|(path, item)| {
243 let path = format!("{prefix}{path}");
244
245 (path, item)
246 })
247 .collect::<utoipa::openapi::path::PathsMap<_, _>>();
248 paths.paths = prefixed_paths;
249
250 paths
251 }
252
253 fn schemas(
254 &self,
255 schemas: &mut Vec<(
256 String,
257 utoipa::openapi::RefOr<utoipa::openapi::schema::Schema>,
258 )>,
259 ) {
260 let mut api = self.1.borrow_mut();
261 if let Some(components) = &mut api.components {
262 schemas.extend(std::mem::take(&mut components.schemas));
263 }
264 }
265}