sfr_server/
server.rs

1//! The implementation of server.
2
3use sfr_core as sc;
4use sfr_types as st;
5
6use crate::extract::SlackSlashCommand;
7use crate::handler::home::HomeHandler;
8use crate::handler::oauth::OauthHandler;
9use crate::handler::slash_command::SlashCommandHandler;
10use crate::log::try_init_logger;
11use crate::{Config, HomeHandlerTrait, OauthHandlerTrait, ResponseError, SlashCommandHandlerTrait};
12use axum::extract::Extension;
13use axum::extract::Query;
14use axum::http::{StatusCode, Uri};
15use axum::response::IntoResponse;
16use axum::routing::{get, get_service, post};
17use axum::Router;
18use sc::{OauthRedirectQuery, Slack};
19use std::sync::Arc;
20use tower_http::services::ServeDir;
21use tower_http::trace::TraceLayer;
22
23/// The server type.
24pub struct Server<HH, SH, OH>
25where
26    HH: HomeHandlerTrait,
27    SH: SlashCommandHandlerTrait,
28    OH: OauthHandlerTrait,
29{
30    /// The configuration about server.
31    config: Config,
32
33    /// The commonly Slack process handler.
34    slack: Slack,
35
36    /// The HTTP Client.
37    client: reqwest::Client,
38
39    /// The home handler.
40    home_handler: HH,
41
42    /// The slash command handler.
43    sc_handler: SH,
44
45    /// The OAuth handler.
46    oauth_handler: OH,
47}
48
49impl<HH, SH, OH> Server<HH, SH, OH>
50where
51    HH: HomeHandlerTrait + 'static,
52    SH: SlashCommandHandlerTrait + 'static,
53    OH: OauthHandlerTrait + 'static,
54{
55    /// The constructor.
56    pub fn new(
57        config: Config,
58        slack: Slack,
59        client: reqwest::Client,
60        home_handler: HH,
61        sc_handler: SH,
62        oauth_handler: OH,
63    ) -> Self {
64        Self {
65            config,
66            slack,
67            client,
68            home_handler,
69            sc_handler,
70            oauth_handler,
71        }
72    }
73
74    /// Starts serving.
75    pub async fn serve(self) -> Result<(), st::Error> {
76        if let Some(log) = self.config.log {
77            let _ = try_init_logger(log.as_str())
78                .inspect_err(|e| tracing::warn!("logger already initialized: {e:?}"));
79        }
80
81        let app = Router::new()
82            .route(&self.config.home_path, get(home_handler_fn::<HH>))
83            .route(
84                &self.config.slash_command_path,
85                post(slash_command_handler_fn::<SH, OH>),
86            )
87            .route(
88                &self.config.oauth_path,
89                get(handler_oauth_redirect_fn::<OH>),
90            )
91            .nest_service(
92                &self.config.static_path.http,
93                get_service(ServeDir::new(&self.config.static_path.local))
94                    .fallback(handle_static_file_error),
95            )
96            .layer(Extension(self.slack))
97            .layer(Extension(self.client))
98            .layer(Extension(Arc::new(HomeHandler::new(self.home_handler))))
99            .layer(Extension(Arc::new(SlashCommandHandler::new(
100                self.sc_handler,
101            ))))
102            .layer(Extension(Arc::new(OauthHandler::new(self.oauth_handler))))
103            .layer(TraceLayer::new_for_http());
104
105        tracing::info!("listening on {}", self.config.sock);
106        let listener = tokio::net::TcpListener::bind(&self.config.sock)
107            .await
108            .map_err(st::Error::failed_to_bind_socket)?;
109        axum::serve(listener, app)
110            .await
111            .map_err(st::Error::failed_to_serve)
112    }
113}
114
115/// The wrapper function for a home page.
116async fn home_handler_fn<HH>(
117    Extension(hh): Extension<Arc<HomeHandler<HH>>>,
118) -> Result<impl IntoResponse, ResponseError>
119where
120    HH: HomeHandlerTrait + 'static,
121{
122    hh.handle().await
123}
124
125/// The wrapper function for a slash command.
126async fn slash_command_handler_fn<SH, OH>(
127    Extension(client): Extension<reqwest::Client>,
128    Extension(sc): Extension<Arc<SlashCommandHandler<SH>>>,
129    Extension(oauth): Extension<Arc<OauthHandler<OH>>>,
130    SlackSlashCommand(body): SlackSlashCommand,
131) -> Result<impl IntoResponse, ResponseError>
132where
133    SH: SlashCommandHandlerTrait,
134    OH: OauthHandlerTrait,
135{
136    sc.handle(client, &oauth, body).await
137}
138
139/// The wrapper function for a OAuth.
140async fn handler_oauth_redirect_fn<OH>(
141    Extension(client): Extension<reqwest::Client>,
142    Extension(slack): Extension<Slack>,
143    Extension(oauth): Extension<Arc<OauthHandler<OH>>>,
144    Query(query): Query<OauthRedirectQuery>,
145) -> Result<impl IntoResponse, ResponseError>
146where
147    OH: OauthHandlerTrait,
148{
149    oauth.handle(client, slack, query).await
150}
151
152/// The handler returns error if error occurred around static files.
153async fn handle_static_file_error(_uri: Uri) -> impl IntoResponse {
154    (StatusCode::INTERNAL_SERVER_ERROR, "error")
155}