use super::{
actix::{
dev::{ServiceRequest, ServiceResponse},
middleware::{Condition, NormalizePath},
App, Error, HttpServer, Scope,
},
cors::Cors,
default_routes::static_files,
logger::RapidLogger,
shift::generate::create_typescript_types,
tui::server_init,
util::{
check_for_invalid_handlers, get_bindings_directory, get_routes_dir, get_server_port, is_logging, is_serving_static_files,
should_generate_types, NEXTJS_ROUTE_PATH, REMIX_ROUTE_PATH,
},
};
use crate::{logger::init_logger, tui::rapid_chevrons};
use actix_http::{body::MessageBody, Request, Response};
use actix_service::{IntoServiceFactory, ServiceFactory};
use actix_web::dev::AppConfig;
use colorful::{Color, Colorful};
use lazy_static::lazy_static;
use rapid_cli::rapid_config::config::{find_rapid_config, RapidConfig};
use spinach::Spinach;
use std::{
env::current_dir,
path::PathBuf,
thread,
time::{self, Instant},
};
extern crate proc_macro;
#[derive(Clone)]
pub struct RapidServer {
pub port: Option<u16>,
pub hostname: Option<String>,
}
lazy_static! {
pub static ref RAPID_SERVER_CONFIG: RapidConfig = find_rapid_config();
}
impl RapidServer {
pub fn create(port: Option<u16>, hostname: Option<String>) -> Self {
Self { port, hostname }
}
pub fn router(
cors: Option<Cors>,
log_type: Option<RapidLogger>,
) -> App<impl ServiceFactory<ServiceRequest, Response = ServiceResponse<impl MessageBody>, Config = (), InitError = (), Error = Error>> {
let is_logging = is_logging();
let is_serving_static_files = is_serving_static_files();
let config_logging_server = {
if is_logging {
match log_type {
Some(logging_type) => App::new()
.wrap(cors.unwrap_or(Cors::default()))
.wrap(Condition::new(true, logging_type))
.wrap(NormalizePath::trim()),
None => App::new()
.wrap(cors.unwrap_or(Cors::default()))
.wrap(Condition::new(true, RapidLogger::minimal()))
.wrap(NormalizePath::trim()),
}
} else {
App::new()
.wrap(cors.unwrap_or(Cors::default()))
.wrap(Condition::new(false, RapidLogger::minimal()))
.wrap(NormalizePath::trim())
}
};
match is_serving_static_files {
true => config_logging_server.service(static_files::static_files()),
false => config_logging_server,
}
}
pub fn fs_router(
cors: Option<Cors>,
log_type: Option<RapidLogger>,
routes: Scope,
) -> App<impl ServiceFactory<ServiceRequest, Response = ServiceResponse<impl MessageBody>, Config = (), InitError = (), Error = Error>> {
RapidServer::router(cors, log_type).service(routes)
}
pub async fn listen<F, I, S, B>(self, server: HttpServer<F, I, S, B>) -> std::io::Result<()>
where
F: Fn() -> I + Send + Clone + 'static,
I: IntoServiceFactory<S, Request>,
S: ServiceFactory<Request, Config = AppConfig> + 'static,
S::Error: Into<Error>,
S::InitError: std::fmt::Debug,
S::Response: Into<Response<B>>,
B: MessageBody + 'static,
{
init_logger();
let bind_config = get_default_bind_config(RAPID_SERVER_CONFIG.clone(), self.hostname, self.port);
let routes_dir = match RAPID_SERVER_CONFIG.app_type.as_str() {
"server" => get_routes_dir(RAPID_SERVER_CONFIG.server.as_ref()),
"remix" => REMIX_ROUTE_PATH.to_owned(),
_ => NEXTJS_ROUTE_PATH.to_owned(),
};
let bindings_out_dir = get_bindings_directory();
let should_generate_typescript_types = should_generate_types(RAPID_SERVER_CONFIG.clone());
server_init(bind_config.clone());
if should_generate_typescript_types && cfg!(debug_assertions) {
generate_typescript_types(bindings_out_dir, routes_dir.clone(), RAPID_SERVER_CONFIG.clone());
}
check_for_invalid_handlers(&routes_dir);
server.bind(bind_config)?.run().await
}
}
fn get_default_bind_config(config: RapidConfig, host_name: Option<String>, port: Option<u16>) -> (String, u16) {
let server_hostname = match host_name {
Some(value) => value,
None => String::from("localhost"),
};
let fallback_port = match port {
Some(value) => value,
None => 8080,
};
let port = get_server_port(config, fallback_port);
(server_hostname, port)
}
pub fn generate_typescript_types(bindings_out_dir: PathBuf, routes_dir: String, config: RapidConfig) {
let every_dir_types_gen = match config.app_type.as_str() {
"server" => match config.server {
Some(server) => match server.typescript_generation_directory {
Some(value) => value,
None => "".to_string(),
},
None => "".to_string(),
},
"remix" => match config.remix {
Some(remix) => match remix.typescript_generation_directory {
Some(value) => value,
None => "".to_string(),
},
None => "".to_string(),
},
_ => match config.nextjs {
Some(nextjs) => match nextjs.typescript_generation_directory {
Some(value) => value,
None => "".to_string(),
},
None => "".to_string(),
},
};
let routes_directory = current_dir()
.expect("Could not parse routes direcory path found in rapid config file.")
.join(PathBuf::from(routes_dir.clone()));
let type_generation_directory = if every_dir_types_gen != "" {
current_dir()
.expect("Could not parse current directory while executing type generation!")
.join(every_dir_types_gen)
} else {
routes_directory.clone()
};
let start_time = Instant::now();
let loading = Spinach::new(format!("{} Generating types...", rapid_chevrons()));
create_typescript_types(bindings_out_dir, routes_directory, type_generation_directory);
let timeout = time::Duration::from_millis(550);
thread::sleep(timeout);
loading.succeed(format!(
"Generated typescript types in {} ms\n",
start_time.elapsed().as_millis().to_string().color(Color::Blue).bold()
));
}
#[cfg(test)]
mod tests {
use super::*;
use actix_web::{http::header::ContentType, test, web};
use std::fs::File;
use std::io::prelude::*;
#[actix_web::test]
async fn test_server_and_router() {
let rapid_config_test = r#"app_type = "server"
[server]
serve_static_files = true
is_logging = true
typescript_generation = true
port = 8080
routes_directory = "src/routes"
bindings_export_path = "/"
"#;
let mut rapid_config = File::create("rapid.toml").unwrap();
rapid_config.write_all(rapid_config_test.as_bytes()).unwrap();
let app = test::init_service(RapidServer::router(None, None).route("/", web::get().to(|| async { "Hello World!" }))).await;
let req = test::TestRequest::default().insert_header(ContentType::plaintext()).to_request();
let resp = test::call_service(&app, req).await;
assert!(resp.status().is_success());
std::fs::remove_file("rapid.toml").unwrap();
}
}