use std::{env::current_dir, path::PathBuf};
use super::{
actix::{
dev::{ServiceRequest, ServiceResponse},
middleware::{Condition, NormalizePath},
App, Error, HttpServer, Scope,
},
cors::Cors,
default_routes::static_files,
logger::{init_logger, RapidLogger},
shift::generate::create_typescript_types,
tui::{clean_console, server_init},
util::{check_for_invalid_handlers, get_routes_dir, get_server_port, should_generate_types},
};
use actix_http::{body::MessageBody, Request, Response};
use actix_service::{IntoServiceFactory, ServiceFactory};
use actix_web::dev::AppConfig;
use lazy_static::lazy_static;
use rapid_cli::{
cli::rapid_logo,
rapid_config::config::{find_rapid_config, RapidConfig},
};
use spinach::Spinach;
use std::{thread, time};
extern crate proc_macro;
#[derive(Clone)]
pub struct RapidServer {
pub port: Option<u16>,
pub hostname: Option<String>,
}
lazy_static! {
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 app_type = &RAPID_SERVER_CONFIG.app_type;
let is_logging = match app_type.as_str() {
"server" => match RAPID_SERVER_CONFIG.server.as_ref() {
Some(value) => value.is_logging.unwrap_or(true),
None => true,
},
"remix" => match RAPID_SERVER_CONFIG.remix.as_ref() {
Some(value) => value.is_logging.unwrap_or(true),
None => true,
}
_ => panic!("Error: invalid config type found in rapid config file. Please use either `server` or `remix`")
};
let is_serving_static_files = match app_type.as_str() {
"server" => match RAPID_SERVER_CONFIG.server.as_ref() {
Some(value) => value.serve_static_files.unwrap_or(true),
None => true,
},
"remix" => match RAPID_SERVER_CONFIG.remix.as_ref() {
Some(value) => value.serve_static_files.unwrap_or(true),
None => true,
}
_ => panic!("Error: invalid config type found in rapid config file. Please use either `server` or `remix`")
};
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" => String::from("app/api/routes"),
_ => panic!("Error: the 'routes_directory' variable must be set in your rapid config file!")
};
let bindings_out_dir = match RAPID_SERVER_CONFIG.app_type.as_str() {
"server" => match RAPID_SERVER_CONFIG.server.as_ref() {
Some(server) => match server.bindings_export_path.clone() {
Some(dir) => match dir == "/" {
true => current_dir().expect("Could not parse bindings export path found in rapid config file."),
false => current_dir()
.expect("Could not parse bindings export path found in rapid config file.")
.join(PathBuf::from(dir)),
},
None => panic!("Error: the 'bindings_export_path' variable must be set in your rapid config file!"),
},
None => panic!("You must have a valid rapid config file in the base project directory!"),
},
"remix" => match RAPID_SERVER_CONFIG.remix.as_ref() {
Some(remix) => match remix.bindings_export_path.clone() {
Some(dir) => match dir == "/" {
true => current_dir().expect("Could not parse bindings export path found in rapid config file."),
false => current_dir()
.expect("Could not parse bindings export path found in rapid config file.")
.join(PathBuf::from(dir)),
},
None => panic!("Error: the 'bindings_export_path' variable must be set in your rapid config file!"),
},
None => panic!("You must have a valid rapid config file in the base project directory!"),
},
_ => panic!("Error: the 'routes_directory' variable must be set in your rapid config file!")
};
let should_generate_typescript_types = should_generate_types(RAPID_SERVER_CONFIG.clone());
if should_generate_typescript_types && cfg!(debug_assertions) {
generate_typescript_types(bindings_out_dir, routes_dir.clone());
}
server_init(bind_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) {
clean_console();
let loading = Spinach::new(format!("{} Generating types...", rapid_logo()));
create_typescript_types(
bindings_out_dir,
current_dir()
.expect("Could not parse bindings export path found in rapid config file.")
.join(PathBuf::from(routes_dir.clone())),
);
let timeout = time::Duration::from_millis(650);
thread::sleep(timeout);
loading.stop();
}