1use crate::data::ApplicationData;
2use actix_web::{post, web, App, HttpResponse, HttpServer};
3use dmntk_common::{ColorPalette, Jsonify};
4use dmntk_feel::FeelScope;
5use dmntk_workspace::Workspaces;
6use std::borrow::Borrow;
7use std::net::IpAddr;
8use std::path::{Path, PathBuf};
9use std::str::FromStr;
10use std::sync::Arc;
11use std::{env, io};
12
13const DMNTK_DEFAULT_PORT: u16 = 22022;
14const DMNTK_DEFAULT_HOST: &str = "0.0.0.0";
15const DMNTK_HOST_VARIABLE: &str = "DMNTK_HOST";
16const DMNTK_PORT_VARIABLE: &str = "DMNTK_PORT";
17const DMNTK_DIR_VARIABLE: &str = "DMNTK_DIR";
18const CONTENT_TYPE: &str = "application/json";
19
20#[post("/evaluate/{path:.*}")]
23async fn evaluate(path: web::Path<String>, request_body: String, data: web::Data<ApplicationData>) -> HttpResponse {
24 let workspace: &Workspaces = data.workspaces.borrow();
25 match dmntk_evaluator::evaluate_context(&FeelScope::default(), &request_body).and_then(|input_data| workspace.evaluate(&path, &input_data)) {
26 Ok(value) => HttpResponse::Ok().content_type(CONTENT_TYPE).body(format!(r#"{{"data":{}}}"#, value.jsonify())),
27 Err(reason) => HttpResponse::Ok().content_type(CONTENT_TYPE).body(format!(r#"{{"errors":[{{"detail":"{reason}"}}]}}"#)),
28 }
29}
30
31async fn not_found() -> HttpResponse {
33 HttpResponse::NotFound().content_type(CONTENT_TYPE).body(r#"{"errors":[{"detail":"endpoint not found"}]}"#)
34}
35
36#[cfg(feature = "tck")]
37fn config(cfg: &mut web::ServiceConfig) {
38 cfg.service(crate::tck::post_tck_evaluate);
39}
40
41#[cfg(not(feature = "tck"))]
42fn config(cfg: &mut web::ServiceConfig) {
43 cfg.service(evaluate);
44}
45
46pub async fn start_server(opt_host: Option<String>, opt_port: Option<String>, opt_dir: Option<String>, colors: ColorPalette, verbose: bool) -> io::Result<()> {
48 let application_data = web::Data::new(ApplicationData {
49 workspaces: Arc::new(Workspaces::new(&get_root_dir(opt_dir), colors.clone(), verbose)),
50 });
51 let address = get_server_address(opt_host, opt_port);
52 println!("{1}dmntk{0} {2}{address}{0}", colors.reset(), colors.blue(), colors.yellow());
53 HttpServer::new(move || {
54 App::new()
55 .app_data(application_data.clone())
56 .app_data(web::PayloadConfig::new(4 * 1024 * 1024))
57 .configure(config)
58 .default_service(web::route().to(not_found))
59 })
60 .bind(address)?
61 .run()
62 .await
63}
64
65fn get_server_address(opt_host: Option<String>, opt_port: Option<String>) -> String {
79 let mut host = DMNTK_DEFAULT_HOST.to_string();
81 if let Ok(host_ip_address) = env::var(DMNTK_HOST_VARIABLE) {
82 if is_valid_ip_address(&host_ip_address) {
83 host = host_ip_address;
84 } else {
85 eprintln!("invalid host address specified in environment variable {}: {}", DMNTK_HOST_VARIABLE, host_ip_address);
86 }
87 }
88 if let Some(host_ip_address) = opt_host {
89 if is_valid_ip_address(&host_ip_address) {
90 host = host_ip_address;
91 } else {
92 eprintln!("invalid host address given as command option: {}", host_ip_address);
93 }
94 }
95 let mut port: u16 = DMNTK_DEFAULT_PORT;
97 if let Ok(p_str) = env::var(DMNTK_PORT_VARIABLE) {
98 if let Ok(p) = u16::from_str(&p_str) {
99 port = p;
100 } else {
101 eprintln!("invalid port number specified in environment variable {}: {}", DMNTK_PORT_VARIABLE, p_str);
102 }
103 }
104 if let Some(p_str) = opt_port {
105 if let Ok(p) = u16::from_str(&p_str) {
106 port = p;
107 } else {
108 eprintln!("invalid port number specified as command option: {}", p_str);
109 }
110 }
111 let server_address = format!("{host}:{port}");
112 server_address
113}
114
115fn is_valid_ip_address(ip: &str) -> bool {
121 ip == "localhost" || ip.parse::<IpAddr>().is_ok()
122}
123
124fn get_root_dir(opt_dir: Option<String>) -> PathBuf {
126 let current_dir_path = env::current_dir().expect("failed to retrieve current directory");
127 if let Ok(s) = env::var(DMNTK_DIR_VARIABLE) {
128 let dir_path = Path::new(&s);
129 if dir_path.exists() && dir_path.is_dir() {
130 return dir_path.into();
131 } else {
132 eprintln!("invalid directory specified in environment variable {}: {}", DMNTK_DIR_VARIABLE, s);
133 }
134 }
135 if let Some(s) = opt_dir {
136 let dir_path = Path::new(&s);
137 if dir_path.exists() && dir_path.is_dir() {
138 return dir_path.into();
139 } else {
140 eprintln!("invalid directory specified as command option: {}", s);
141 }
142 }
143 current_dir_path
144}