1use super::{
2 actix::{
3 dev::{ServiceRequest, ServiceResponse},
4 middleware::{Condition, NormalizePath},
5 App, Error, HttpServer, Scope,
6 },
7 cors::Cors,
8 default_routes::static_files,
9 logger::RapidLogger,
10 shift::generate::create_typescript_types,
11 tui::server_init,
12 util::{
13 check_for_invalid_handlers, get_bindings_directory, get_routes_dir, get_server_port, is_logging, is_serving_static_files,
14 should_generate_types, NEXTJS_ROUTE_PATH, REMIX_ROUTE_PATH,
15 },
16};
17use crate::{logger::init_logger, tui::rapid_chevrons};
18use actix_http::{body::MessageBody, Request, Response};
19use actix_service::{IntoServiceFactory, ServiceFactory};
20use actix_web::dev::AppConfig;
21use colorful::{Color, Colorful};
22use lazy_static::lazy_static;
23use rapid_cli::rapid_config::config::{find_rapid_config, RapidConfig};
24use spinach::Spinach;
25use std::{
26 env::current_dir,
27 path::PathBuf,
28 thread,
29 time::{self, Instant},
30};
31extern crate proc_macro;
32
33#[derive(Clone)]
34pub struct RapidServer {
35 pub port: Option<u16>,
36 pub hostname: Option<String>,
37}
38
39lazy_static! {
41 pub static ref RAPID_SERVER_CONFIG: RapidConfig = find_rapid_config();
42}
43
44impl RapidServer {
57 pub fn create(port: Option<u16>, hostname: Option<String>) -> Self {
58 Self { port, hostname }
59 }
60
61 pub fn router(
65 cors: Option<Cors>,
66 log_type: Option<RapidLogger>,
67 ) -> App<impl ServiceFactory<ServiceRequest, Response = ServiceResponse<impl MessageBody>, Config = (), InitError = (), Error = Error>> {
68 let is_logging = is_logging();
70
71 let is_serving_static_files = is_serving_static_files();
73
74 let config_logging_server = {
75 if is_logging {
76 match log_type {
77 Some(logging_type) => App::new()
78 .wrap(cors.unwrap_or(Cors::default()))
79 .wrap(Condition::new(true, logging_type))
80 .wrap(NormalizePath::trim()),
81 None => App::new()
82 .wrap(cors.unwrap_or(Cors::default()))
83 .wrap(Condition::new(true, RapidLogger::minimal()))
84 .wrap(NormalizePath::trim()),
85 }
86 } else {
87 App::new()
88 .wrap(cors.unwrap_or(Cors::default()))
89 .wrap(Condition::new(false, RapidLogger::minimal()))
90 .wrap(NormalizePath::trim())
91 }
92 };
93
94 match is_serving_static_files {
96 true => config_logging_server.service(static_files::static_files()),
97 false => config_logging_server,
98 }
99 }
100
101 pub fn fs_router(
111 cors: Option<Cors>,
112 log_type: Option<RapidLogger>,
113 routes: Scope,
114 ) -> App<impl ServiceFactory<ServiceRequest, Response = ServiceResponse<impl MessageBody>, Config = (), InitError = (), Error = Error>> {
115 RapidServer::router(cors, log_type).service(routes)
117 }
118
119 pub async fn listen<F, I, S, B>(self, server: HttpServer<F, I, S, B>) -> std::io::Result<()>
126 where
127 F: Fn() -> I + Send + Clone + 'static,
128 I: IntoServiceFactory<S, Request>,
129 S: ServiceFactory<Request, Config = AppConfig> + 'static,
130 S::Error: Into<Error>,
131 S::InitError: std::fmt::Debug,
132 S::Response: Into<Response<B>>,
133 B: MessageBody + 'static,
134 {
135 init_logger();
137
138 let bind_config = get_default_bind_config(RAPID_SERVER_CONFIG.clone(), self.hostname, self.port);
141
142 let routes_dir = match RAPID_SERVER_CONFIG.app_type.as_str() {
145 "server" => get_routes_dir(RAPID_SERVER_CONFIG.server.as_ref()),
146 "remix" => REMIX_ROUTE_PATH.to_owned(),
147 _ => NEXTJS_ROUTE_PATH.to_owned(),
148 };
149
150 let bindings_out_dir = get_bindings_directory();
154
155 let should_generate_typescript_types = should_generate_types(RAPID_SERVER_CONFIG.clone());
157
158 server_init(bind_config.clone());
160
161 if should_generate_typescript_types && cfg!(debug_assertions) {
164 generate_typescript_types(bindings_out_dir, routes_dir.clone(), RAPID_SERVER_CONFIG.clone());
165 }
166
167 check_for_invalid_handlers(&routes_dir);
169
170 server.bind(bind_config)?.run().await
172 }
173}
174
175fn get_default_bind_config(config: RapidConfig, host_name: Option<String>, port: Option<u16>) -> (String, u16) {
177 let server_hostname = match host_name {
180 Some(value) => value,
181 None => String::from("localhost"),
182 };
183
184 let fallback_port = match port {
186 Some(value) => value,
187 None => 8080,
188 };
189
190 let port = get_server_port(config, fallback_port);
192
193 (server_hostname, port)
194}
195
196pub fn generate_typescript_types(bindings_out_dir: PathBuf, routes_dir: String, config: RapidConfig) {
197 let every_dir_types_gen = match config.app_type.as_str() {
199 "server" => match config.server {
200 Some(server) => match server.typescript_generation_directory {
201 Some(value) => value,
202 None => "".to_string(),
203 },
204 None => "".to_string(),
205 },
206 "remix" => match config.remix {
207 Some(remix) => match remix.typescript_generation_directory {
208 Some(value) => value,
209 None => "".to_string(),
210 },
211 None => "".to_string(),
212 },
213 _ => match config.nextjs {
214 Some(nextjs) => match nextjs.typescript_generation_directory {
215 Some(value) => value,
216 None => "".to_string(),
217 },
218 None => "".to_string(),
219 },
220 };
221
222 let routes_directory = current_dir()
223 .expect("Could not parse routes direcory path found in rapid config file.")
224 .join(PathBuf::from(routes_dir.clone()));
225
226 let type_generation_directory = if every_dir_types_gen != "" {
228 current_dir()
229 .expect("Could not parse current directory while executing type generation!")
230 .join(every_dir_types_gen)
231 } else {
232 routes_directory.clone()
234 };
235
236 let start_time = Instant::now();
237 let loading = Spinach::new(format!("{} Generating types...", rapid_chevrons()));
239 create_typescript_types(bindings_out_dir, routes_directory, type_generation_directory);
241
242 let timeout = time::Duration::from_millis(550);
244 thread::sleep(timeout);
245
246 loading.succeed(format!(
247 "Generated typescript types in {} ms\n",
248 start_time.elapsed().as_millis().to_string().color(Color::Blue).bold()
249 ));
250}
251
252#[cfg(test)]
253mod tests {
254 use super::*;
255 use actix_web::{http::header::ContentType, test, web};
256 use std::fs::File;
257 use std::io::prelude::*;
258
259 #[actix_web::test]
260 async fn test_server_and_router() {
261 let rapid_config_test = r#"app_type = "server"
264
265 [server]
266 serve_static_files = true
267 is_logging = true
268 typescript_generation = true
269 port = 8080
270 routes_directory = "src/routes"
271 bindings_export_path = "/"
272 "#;
273
274 let mut rapid_config = File::create("rapid.toml").unwrap();
275
276 rapid_config.write_all(rapid_config_test.as_bytes()).unwrap();
277
278 let app = test::init_service(RapidServer::router(None, None).route("/", web::get().to(|| async { "Hello World!" }))).await;
279
280 let req = test::TestRequest::default().insert_header(ContentType::plaintext()).to_request();
281 let resp = test::call_service(&app, req).await;
282 assert!(resp.status().is_success());
283
284 std::fs::remove_file("rapid.toml").unwrap();
285 }
286}