use super::{server::RAPID_SERVER_CONFIG, shift::util::is_valid_handler};
use colorful::{Color, Colorful};
use core::panic;
use log::warn;
use rapid_cli::rapid_config::config::{RapidConfig, ServerConfig};
use std::{env::current_dir, fs::File, io::Read, path::PathBuf};
use syn::{parse_file, parse_str, File as SynFile, Item};
use walkdir::WalkDir;
pub const REMIX_ROUTE_PATH: &'static str = "app/api/routes";
pub const NEXTJS_ROUTE_PATH: &'static str = "pages/api/routes";
pub fn check_for_invalid_handlers(dir: &str) {
for entry in WalkDir::new(dir).into_iter().filter_map(|e| e.ok()) {
if entry.path().extension().and_then(|ext| ext.to_str()) == Some("rs")
&& entry.path().file_name().and_then(|name| name.to_str()) != Some("mod.rs")
&& entry.path().file_name().and_then(|name| name.to_str()) != Some("_middleware.rs")
{
let mut file = File::open(entry.path()).unwrap();
let mut file_contents = String::new();
file.read_to_string(&mut file_contents).unwrap();
if !validate_route_handler(&file_contents) || !is_valid_route_function(&file_contents) {
warn!(
"found invalid route handler file at {}",
format!("`{}`", entry.path().to_str().expect("Error: could not parse invalid route handler")).color(Color::LightCyan)
);
}
}
}
}
pub fn validate_route_handler(handler_source: &String) -> bool {
if parse_file(handler_source).is_err() {
return false;
}
let parsed_file: SynFile = parse_str(handler_source.as_str()).expect("An error occurred when attempting to parse a rapid route handler file.");
let mut has_rapid_handler = false;
let mut handler_count = 0;
for item in parsed_file.items {
if let Item::Fn(function) = item {
let is_valid = is_valid_handler("rapid_handler", function.attrs);
if is_valid {
has_rapid_handler = true;
handler_count += 1;
}
}
}
has_rapid_handler && handler_count == 1
}
pub fn is_valid_route_function(file_contents: &str) -> bool {
if file_contents.contains("async fn get") {
return true;
} else if file_contents.contains("async fn post") {
return true;
} else if file_contents.contains("async fn delete") {
return true;
} else if file_contents.contains("async fn put") {
return true;
} else if file_contents.contains("async fn patch") {
return true;
} else if file_contents.contains("async fn query") {
return true;
} else if file_contents.contains("async fn mutation") {
return true;
}
false
}
pub fn get_routes_dir(rapid_server_config: Option<&ServerConfig>) -> String {
match rapid_server_config {
Some(server) => match server.routes_directory.clone() {
Some(dir) => match dir == "/" {
true => panic!("The 'routes_directory' variable cannot be set to a base path. Please use something nested!"),
false => dir,
},
None => panic!("Error: the 'routes_directory' variable must be set in your rapid config file!"),
},
None => panic!("You must have a valid rapid config file in the base project directory!"),
}
}
pub fn get_server_port(config: RapidConfig, fallback_port: u16) -> u16 {
let app_type = config.app_type;
match app_type.as_str() {
"server" => match config.server {
Some(val) => match val.port {
Some(p) => p,
None => fallback_port,
},
_ => fallback_port,
},
"remix" => match config.remix {
Some(val) => match val.server_port {
Some(s_port) => s_port,
None => fallback_port,
},
_ => fallback_port,
},
_ => match config.nextjs {
Some(val) => match val.server_port {
Some(s_port) => s_port,
None => fallback_port,
},
_ => fallback_port,
},
}
}
pub fn should_generate_types(config: RapidConfig) -> bool {
let app_type = config.app_type.as_str();
match app_type {
"server" => match config.server.as_ref() {
Some(server) => match server.typescript_generation.clone() {
Some(val) => val,
None => true,
},
None => true,
},
"remix" => match config.remix.as_ref() {
Some(remix) => match remix.typescript_generation.clone() {
Some(val) => val,
None => true,
},
None => true,
},
_ => match config.nextjs.as_ref() {
Some(nextjs) => match nextjs.typescript_generation.clone() {
Some(val) => val,
None => true,
},
None => true,
},
}
}
pub fn get_bindings_directory() -> PathBuf {
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!"),
},
_ => match RAPID_SERVER_CONFIG.nextjs.as_ref() {
Some(nextjs) => match nextjs.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!"),
},
}
}
pub fn is_logging() -> bool {
match RAPID_SERVER_CONFIG.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,
},
_ => match RAPID_SERVER_CONFIG.nextjs.as_ref() {
Some(value) => value.is_logging.unwrap_or(true),
None => true,
},
}
}
pub fn is_serving_static_files() -> bool {
match RAPID_SERVER_CONFIG.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,
},
_ => match RAPID_SERVER_CONFIG.nextjs.as_ref() {
Some(value) => value.serve_static_files.unwrap_or(true),
None => true,
},
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::io::Write;
#[test]
fn test_is_valid_route_function() {
let valid_handler = r#"
#[rapid_handler]
async fn query() -> Result<HttpResponse, Error> {
Ok(HttpResponse::Ok().body("Hello world!"))
}
"#;
let invalid_handler = r#"
#[rapid_handler]
async fn invalid() -> Result<HttpResponse, Error> {
Ok(HttpResponse::Ok().body("Hello world!"))
}
"#;
assert_eq!(is_valid_route_function(valid_handler), true);
assert_eq!(is_valid_route_function(invalid_handler), false);
}
#[test]
fn test_check_for_invalid_handlers() {
let valid_handler = r#"
#[rapid_handler]
async fn query() -> Result<HttpResponse, Error> {
Ok(HttpResponse::Ok().body("Hello world!"))
}
"#;
let invalid_handler = r#"
#[rapid_handler]
async fn invalid() -> Result<HttpResponse, Error> {
Ok(HttpResponse::Ok().body("Hello world!"))
}
"#;
let mut valid_file = File::create("test_valid.rs").unwrap();
let mut invalid_file = File::create("test_invalid.rs").unwrap();
valid_file.write_all(valid_handler.as_bytes()).unwrap();
invalid_file.write_all(invalid_handler.as_bytes()).unwrap();
check_for_invalid_handlers(".");
std::fs::remove_file("test_valid.rs").unwrap();
std::fs::remove_file("test_invalid.rs").unwrap();
}
#[test]
fn test_validate_route_handler() {
let valid_handler = r#"
#[rapid_handler]
pub async fn query() -> Result<HttpResponse, Error> {
Ok(HttpResponse::Ok().body("Hello world!"))
}
"#;
let invalid_handler = r#"
pub async fn invalid() -> Result<HttpResponse, Error> {
Ok(HttpResponse::Ok().body("Hello world!"))
}
"#;
assert_eq!(validate_route_handler(&valid_handler.to_string()), true);
assert_eq!(validate_route_handler(&invalid_handler.to_string()), false);
}
}