Skip to main content

dsntk_server/
server.rs

1use crate::data::ApplicationData;
2use crate::utils;
3use actix_web::{App, HttpResponse, HttpServer, web};
4#[cfg(not(feature = "tck"))]
5use actix_web::{get, post};
6use antex::{ColorMode, StyledText, Text};
7#[cfg(not(feature = "tck"))]
8use dsntk_common::Jsonify;
9#[cfg(not(feature = "tck"))]
10use dsntk_feel::FeelScope;
11use dsntk_workspace::Workspaces;
12#[cfg(not(feature = "tck"))]
13use std::borrow::Borrow;
14use std::net::IpAddr;
15use std::path::PathBuf;
16use std::str::FromStr;
17use std::sync::Arc;
18use std::{env, io};
19
20const DEFAULT_PORT: u16 = 22022;
21const DEFAULT_HOST: &str = "0.0.0.0";
22const VARIABLE_HOST: &str = "DSNTK_HOST";
23const VARIABLE_PORT: &str = "DSNTK_PORT";
24const VARIABLE_DIR: &str = "DSNTK_DIR";
25const CONTENT_TYPE: &str = "application/json";
26
27/// GET handler for evaluating the invocable.
28#[cfg(not(feature = "tck"))]
29#[get("/evaluate/{path:.*}")]
30async fn evaluate_invocable_get(path: web::Path<String>, body: String, data: web::Data<ApplicationData>) -> HttpResponse {
31  evaluate(path, body, data)
32}
33
34/// POST handler for evaluating the invocable.
35#[cfg(not(feature = "tck"))]
36#[post("/evaluate/{path:.*}")]
37async fn evaluate_invocable_post(path: web::Path<String>, body: String, data: web::Data<ApplicationData>) -> HttpResponse {
38  evaluate(path, body, data)
39}
40
41#[cfg(not(feature = "tck"))]
42fn evaluate(path: web::Path<String>, request_body: String, data: web::Data<ApplicationData>) -> HttpResponse {
43  let workspace: &Workspaces = data.workspaces.borrow();
44  match dsntk_evaluator::evaluate_context(&FeelScope::default(), &request_body).and_then(|input_data| workspace.evaluate(&path, &input_data)) {
45    Ok(value) => HttpResponse::Ok().content_type(CONTENT_TYPE).body(format!(r#"{{"data":{}}}"#, value.jsonify())),
46    Err(reason) => HttpResponse::Ok().content_type(CONTENT_TYPE).body(format!(r#"{{"errors":[{{"detail":"{reason}"}}]}}"#)),
47  }
48}
49
50/// Handler for 404 errors.
51async fn not_found() -> HttpResponse {
52  HttpResponse::NotFound().content_type(CONTENT_TYPE).body(r#"{"errors":[{"detail":"endpoint not found"}]}"#)
53}
54
55#[cfg(feature = "tck")]
56fn config(cfg: &mut web::ServiceConfig) {
57  cfg.service(crate::tck::evaluate_tck_post);
58}
59
60#[cfg(not(feature = "tck"))]
61fn config(cfg: &mut web::ServiceConfig) {
62  cfg.service(evaluate_invocable_get);
63  cfg.service(evaluate_invocable_post);
64}
65
66/// Starts the server.
67pub async fn start_server(opt_host: Option<String>, opt_port: Option<String>, dirs: Vec<String>, cm: ColorMode, verbose: bool) -> io::Result<()> {
68  let application_data = web::Data::new(ApplicationData {
69    workspaces: Arc::new(Workspaces::new(&resolve_search_paths(dirs), cm, verbose)),
70  });
71  let address = get_server_address(opt_host, opt_port);
72  println!("{}", Text::new(cm).bright_blue().s("dsntk").reset().s(' ').bright_yellow().s(&address).reset());
73  HttpServer::new(move || {
74    App::new()
75      .app_data(application_data.clone())
76      .app_data(web::PayloadConfig::new(4 * 1024 * 1024))
77      .configure(config)
78      .default_service(web::route().to(not_found))
79  })
80  .bind(address)?
81  .run()
82  .await
83}
84
85/// Returns the host address and the port number, the server will start to listen on.
86///
87/// The default host and port are defined by `DSNTK_DEFAULT_HOST` and `DSNTK_DEFAULT_PORT` constants.
88/// When other values are given as parameters to this function, these will be the actual host and port.
89/// Host and port may be also controlled using environment variables:
90/// - `DSNTK_HOST` for the host name,
91/// - `DSNTK_PORT` for the port name.
92///
93/// Priority (from highest to lowest):
94/// - `opt_host` an `opt_port` parameters,
95/// - `DSNTK_HOST` and `DSNTK_PORT` environment variables
96/// - `DSNTK_DEFAULT_HOST` and `DSNTK_DEFAULT_PORT` constants.
97///
98fn get_server_address(opt_host: Option<String>, opt_port: Option<String>) -> String {
99  // resolve IP address
100  let mut host = DEFAULT_HOST.to_string();
101  if let Ok(host_ip_address) = env::var(VARIABLE_HOST) {
102    if is_valid_ip_address(&host_ip_address) {
103      host = host_ip_address;
104    } else {
105      eprintln!("invalid host address specified in environment variable {}: {}", VARIABLE_HOST, host_ip_address);
106    }
107  }
108  if let Some(host_ip_address) = opt_host {
109    if is_valid_ip_address(&host_ip_address) {
110      host = host_ip_address;
111    } else {
112      eprintln!("invalid host address given as command option: {}", host_ip_address);
113    }
114  }
115  // resolve IP port
116  let mut port: u16 = DEFAULT_PORT;
117  if let Ok(p_str) = env::var(VARIABLE_PORT) {
118    if let Ok(p) = u16::from_str(&p_str) {
119      port = p;
120    } else {
121      eprintln!("invalid port number specified in environment variable {}: {}", VARIABLE_PORT, p_str);
122    }
123  }
124  if let Some(p_str) = opt_port {
125    if let Ok(p) = u16::from_str(&p_str) {
126      port = p;
127    } else {
128      eprintln!("invalid port number specified as command option: {}", p_str);
129    }
130  }
131  let server_address = format!("{host}:{port}");
132  server_address
133}
134
135/// Checks if the specified IP address is correct.
136///
137/// This function may provide more detailed checks
138/// when the [Ipv4Addr](std::net::Ipv4Addr)
139/// and [Ipv6Addr](std::net::Ipv6Addr) stabilize.
140fn is_valid_ip_address(ip: &str) -> bool {
141  ip == "localhost" || ip.parse::<IpAddr>().is_ok()
142}
143
144/// Returns directories for loading workspaces.
145fn resolve_search_paths(args: Vec<String>) -> Vec<PathBuf> {
146  // PRIORITY 1: get search paths from the environment variable
147  let paths = utils::paths_from_variable(VARIABLE_DIR);
148  if !paths.is_empty() {
149    return paths;
150  }
151  // PRIORITY 2: get search paths from the command line arguments
152  let paths = utils::paths_from_arguments(args);
153  if !paths.is_empty() {
154    return paths;
155  }
156  // PRIORITY 3: the search path is the current directory
157  utils::current_dir()
158}