1#![doc = include_str!("../README.md")]
3#![doc(html_favicon_url = "https://summer-rs.github.io/favicon.ico")]
4#![doc(html_logo_url = "https://summer-rs.github.io/logo.svg")]
5
6pub mod config;
8pub mod error;
10pub mod extractor;
12pub mod handler;
14pub mod middleware;
15#[cfg(feature = "openapi")]
16pub mod openapi;
17pub mod problem_details;
19
20pub use summer_macros::ProblemDetails;
21
22#[cfg(feature = "socket_io")]
23pub use {rmpv, socketioxide};
24
25pub use axum;
26pub use summer::async_trait;
27use summer::signal;
28pub use summer_macros::middlewares;
31pub use summer_macros::nest;
32
33pub use summer_macros::delete;
35pub use summer_macros::get;
36pub use summer_macros::head;
37pub use summer_macros::options;
38pub use summer_macros::patch;
39pub use summer_macros::post;
40pub use summer_macros::put;
41pub use summer_macros::route;
42pub use summer_macros::routes;
43pub use summer_macros::trace;
44
45#[cfg(feature = "socket_io")]
47pub use summer_macros::on_connection;
48#[cfg(feature = "socket_io")]
49pub use summer_macros::on_disconnect;
50#[cfg(feature = "socket_io")]
51pub use summer_macros::on_fallback;
52#[cfg(feature = "socket_io")]
53pub use summer_macros::subscribe_message;
54
55#[cfg(feature = "openapi")]
57pub use summer_macros::api_route;
58#[cfg(feature = "openapi")]
59pub use summer_macros::api_routes;
60#[cfg(feature = "openapi")]
61pub use summer_macros::delete_api;
62#[cfg(feature = "openapi")]
63pub use summer_macros::get_api;
64#[cfg(feature = "openapi")]
65pub use summer_macros::head_api;
66#[cfg(feature = "openapi")]
67pub use summer_macros::options_api;
68#[cfg(feature = "openapi")]
69pub use summer_macros::patch_api;
70#[cfg(feature = "openapi")]
71pub use summer_macros::post_api;
72#[cfg(feature = "openapi")]
73pub use summer_macros::put_api;
74#[cfg(feature = "openapi")]
75pub use summer_macros::trace_api;
76
77pub use axum::routing::MethodFilter;
79
80#[cfg(not(feature = "openapi"))]
82pub type Router = axum::Router;
83pub use axum::routing::MethodRouter;
85
86#[cfg(feature = "openapi")]
87pub use aide;
88#[cfg(feature = "openapi")]
89pub use aide::openapi::OpenApi;
90#[cfg(feature = "openapi")]
91pub type Router = aide::axum::ApiRouter;
92#[cfg(feature = "openapi")]
93pub use aide::axum::routing::ApiMethodRouter;
94
95#[cfg(feature = "openapi")]
96use aide::transform::TransformOpenApi;
97
98use anyhow::Context;
99use axum::Extension;
100use config::ServerConfig;
101use config::WebConfig;
102use std::{net::SocketAddr, ops::Deref, sync::Arc};
103use summer::plugin::component::ComponentRef;
104use summer::plugin::ComponentRegistry;
105use summer::plugin::MutableComponentRegistry;
106use summer::{
107 app::{App, AppBuilder},
108 config::ConfigRegistry,
109 error::Result,
110 event::EventPublisher,
111 plugin::Plugin,
112};
113
114#[cfg(feature = "socket_io")]
115use config::SocketIOConfig;
116
117#[cfg(feature = "openapi")]
118use crate::config::OpenApiConfig;
119
120#[cfg(feature = "openapi")]
122pub type Routers = Vec<aide::axum::ApiRouter>;
123#[cfg(not(feature = "openapi"))]
124pub type Routers = Vec<axum::Router>;
125
126pub type RouterLayer = Arc<dyn Fn(Router) -> Router + Send + Sync>;
142
143pub type RouterLayers = Vec<RouterLayer>;
145
146pub trait LayerConfigurator {
148 fn add_router_layer<F>(&mut self, layer: F) -> &mut Self
162 where
163 F: Fn(Router) -> Router + Send + Sync + 'static;
164}
165
166impl LayerConfigurator for AppBuilder {
167 fn add_router_layer<F>(&mut self, layer: F) -> &mut Self
168 where
169 F: Fn(Router) -> Router + Send + Sync + 'static,
170 {
171 if let Some(layers) = self.get_component_ref::<RouterLayers>() {
172 unsafe {
173 let raw_ptr = ComponentRef::into_raw(layers);
174 let layers = &mut *(raw_ptr as *mut RouterLayers);
175 layers.push(Arc::new(layer));
176 }
177 self
178 } else {
179 let layers: RouterLayers = vec![Arc::new(layer)];
180 self.add_component(layers)
181 }
182 }
183}
184
185#[cfg(feature = "openapi")]
187type OpenApiTransformer = fn(TransformOpenApi) -> TransformOpenApi;
188
189pub trait WebConfigurator {
191 fn add_router(&mut self, router: Router) -> &mut Self;
193
194 #[cfg(feature = "openapi")]
196 fn openapi(&mut self, openapi: OpenApi) -> &mut Self;
197
198 #[cfg(feature = "openapi")]
200 fn api_docs(&mut self, api_docs: OpenApiTransformer) -> &mut Self;
201}
202
203impl WebConfigurator for AppBuilder {
204 fn add_router(&mut self, router: Router) -> &mut Self {
205 if let Some(routers) = self.get_component_ref::<Routers>() {
206 unsafe {
207 let raw_ptr = ComponentRef::into_raw(routers);
208 let routers = &mut *(raw_ptr as *mut Routers);
209 routers.push(router);
210 }
211 self
212 } else {
213 self.add_component(vec![router])
214 }
215 }
216
217 #[cfg(feature = "openapi")]
219 fn openapi(&mut self, openapi: OpenApi) -> &mut Self {
220 self.add_component(openapi)
221 }
222
223 #[cfg(feature = "openapi")]
224 fn api_docs(&mut self, api_docs: OpenApiTransformer) -> &mut Self {
225 self.add_component(api_docs)
226 }
227}
228
229#[derive(Clone)]
231pub struct AppState {
232 pub app: Arc<App>,
234}
235
236pub struct WebPlugin;
238
239pub use summer::event::{ServerProtocol, ServerStartedEvent};
240
241#[async_trait]
242impl Plugin for WebPlugin {
243 async fn build(&self, app: &mut AppBuilder) {
244 let mut config = app
245 .get_config::<WebConfig>()
246 .expect("web plugin config load failed");
247
248 config.normalize_prefixes();
249
250 #[cfg(feature = "socket_io")]
251 let socketio_config = app.get_config::<SocketIOConfig>().ok();
252
253 let routers = app.get_component_ref::<Routers>();
255 let mut router: Router = match routers {
256 Some(rs) => {
257 let mut router = Router::new();
258 for r in rs.deref().iter() {
259 router = router.merge(r.to_owned());
260 }
261 router
262 }
263 None => Router::new(),
264 };
265 if let Some(middlewares) = config.middlewares {
266 router = crate::middleware::apply_middleware(router, middlewares);
267 }
268
269 #[cfg(feature = "socket_io")]
270 if let Some(socketio_config) = socketio_config {
271 router = enable_socketio(socketio_config, app, router);
272 }
273
274 app.add_component(router);
275
276 let server_conf = config.server;
277 #[cfg(feature = "openapi")]
278 {
279 let openapi_conf = config.openapi;
280 app.add_component(openapi_conf.clone());
281 }
282
283 app.add_scheduler(move |app: Arc<App>| Box::new(Self::schedule(app, server_conf)));
284 }
285}
286
287impl WebPlugin {
288 async fn schedule(app: Arc<App>, config: ServerConfig) -> Result<String> {
289 let mut router = app.get_expect_component::<Router>();
290
291 if let Some(layers) = app.get_component_ref::<RouterLayers>() {
295 for layer_fn in layers.deref().iter() {
296 router = layer_fn(router);
297 }
298 }
299
300 let addr = SocketAddr::from((config.binding, config.port));
302 let listener = tokio::net::TcpListener::bind(addr)
303 .await
304 .with_context(|| format!("bind tcp listener failed:{addr}"))?;
305 tracing::info!("bind tcp listener: {addr}");
306
307 #[cfg(feature = "openapi")]
309 let router = {
310 let openapi_conf = app.get_expect_component::<OpenApiConfig>();
311 finish_openapi(&app, router, openapi_conf, &config.global_prefix)
312 };
313
314 let mut router = router.layer(Extension(AppState { app: app.clone() }));
316
317 if !config.global_prefix.is_empty() {
318 router = axum::Router::new().nest(&config.global_prefix, router)
319 };
320
321 tracing::info!("axum server started");
322 app.publish(ServerStartedEvent {
324 addr,
325 protocol: ServerProtocol::Http,
326 })
327 .await?;
328 if config.connect_info {
329 let service = router.into_make_service_with_connect_info::<SocketAddr>();
331 let server = axum::serve(listener, service);
332 if config.graceful {
333 server
334 .with_graceful_shutdown(signal::shutdown_signal("axum web server"))
335 .await
336 } else {
337 server.await
338 }
339 } else {
340 let service = router.into_make_service();
341 let server = axum::serve(listener, service);
342 if config.graceful {
343 server
344 .with_graceful_shutdown(signal::shutdown_signal("axum web server"))
345 .await
346 } else {
347 server.await
348 }
349 }
350 .context("start axum server failed")?;
351
352 Ok("axum schedule finished".to_string())
353 }
354}
355
356#[cfg(feature = "openapi")]
357pub fn enable_openapi() {
358 aide::generate::on_error(|error| {
359 tracing::error!("{error}");
360 });
361 aide::generate::extract_schemas(false);
362}
363
364#[cfg(feature = "socket_io")]
365pub fn enable_socketio(
366 socketio_config: SocketIOConfig,
367 app: &mut AppBuilder,
368 router: Router,
369) -> Router {
370 tracing::info!(
371 "Configuring SocketIO with namespace: {}",
372 socketio_config.default_namespace
373 );
374
375 let (layer, io) = socketioxide::SocketIo::builder().build_layer();
376
377 let ns_path = socketio_config.default_namespace.clone();
378 let ns_path_for_closure = ns_path.clone();
379 io.ns(ns_path, move |socket: socketioxide::extract::SocketRef| async move {
380 use summer::tracing::info;
381
382 info!(socket_id = ?socket.id, "New socket connected to namespace: {}", ns_path_for_closure);
383
384 crate::handler::auto_socketio_setup(&socket);
385 });
386
387 app.add_component(io);
388 router.layer(layer)
389}
390
391#[cfg(feature = "openapi")]
392fn finish_openapi(
393 app: &App,
394 router: aide::axum::ApiRouter,
395 openapi_conf: OpenApiConfig,
396 global_prefix: &str,
397) -> axum::Router {
398 let router = router.nest_api_service(
399 &openapi_conf.doc_prefix,
400 docs_routes(&openapi_conf, global_prefix),
401 );
402
403 let mut api = app.get_component::<OpenApi>().unwrap_or_else(|| OpenApi {
404 info: openapi_conf.info,
405 ..Default::default()
406 });
407
408 let router = if let Some(api_docs) = app.get_component::<OpenApiTransformer>() {
409 router.finish_api_with(&mut api, api_docs)
410 } else {
411 router.finish_api(&mut api)
412 };
413
414 if !global_prefix.is_empty() {
417 if let Some(ref mut paths) = api.paths {
418 let old_paths = std::mem::take(&mut paths.paths);
419 for (path, item) in old_paths {
420 paths.paths.insert(format!("{global_prefix}{path}"), item);
421 }
422 }
423 }
424
425 router.layer(Extension(Arc::new(api)))
426}
427
428#[cfg(feature = "openapi")]
429pub fn docs_routes(
430 OpenApiConfig { doc_prefix, info }: &OpenApiConfig,
431 global_prefix: &str,
432) -> aide::axum::ApiRouter {
433 let router = aide::axum::ApiRouter::new();
434 let _openapi_path = &format!("{global_prefix}{doc_prefix}/openapi.json");
435 let _doc_title = &info.title;
436
437 #[cfg(feature = "openapi-scalar")]
438 let router = router.route(
439 "/scalar",
440 aide::scalar::Scalar::new(_openapi_path)
441 .with_title(_doc_title)
442 .axum_route(),
443 );
444 #[cfg(feature = "openapi-redoc")]
445 let router = router.route(
446 "/redoc",
447 aide::redoc::Redoc::new(_openapi_path)
448 .with_title(_doc_title)
449 .axum_route(),
450 );
451 #[cfg(feature = "openapi-swagger")]
452 let router = router.route(
453 "/swagger",
454 aide::swagger::Swagger::new(_openapi_path)
455 .with_title(_doc_title)
456 .axum_route(),
457 );
458
459 router.route("/openapi.json", axum::routing::get(serve_docs))
460}
461
462#[cfg(feature = "openapi")]
463async fn serve_docs(Extension(api): Extension<Arc<OpenApi>>) -> impl aide::axum::IntoApiResponse {
464 axum::response::IntoResponse::into_response(axum::Json(api.as_ref()))
465}
466
467#[cfg(feature = "openapi")]
468pub fn default_transform<'a>(
469 path_item: aide::transform::TransformPathItem<'a>,
470) -> aide::transform::TransformPathItem<'a> {
471 path_item
472}