summaryrefslogtreecommitdiff
path: root/src/script.rs
blob: 4a94ba401cdef93d773f7608af28dd1566bbe63e (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
use std::{env, collections::HashMap, process::Stdio, fs::read_to_string, result::Result, error::Error};
use tokio::process::Command;
use tokio::io::AsyncWriteExt;

use crate::http::{response::Response, method::Method, header::Header};

pub struct Script {
    path: String
}

impl Script {
    fn new(path: String) -> Self {
        Self { path }
    }

    pub async fn run(&self, body: Option<&String>) -> Result<Response, String> {
        let mut res = Response::new();

        let mut command = Command::new(&self.path);
        command.stdin(Stdio::piped());
        command.stdout(Stdio::piped()); 

        let mut child = match command.spawn() {
            Ok(child) => child,
            Err(err) => return Err(format!("{err}"))
        };

        if let Some(body) = body {
            let Some(mut stdin) = child.stdin.take() else {
                return Err("failed to read stdin".into())
            };
            let _ = stdin.write(body.as_bytes()).await;
        }

        let output = match child.wait_with_output().await {
            Ok(output) => output,
            Err(err) => return Err(format!("{err}"))
        };

        let status = output.status.code().unwrap_or(500) as u16;

        let response = String::from_utf8_lossy(&output.stdout).into_owned();
        let mut lines = response.split('\n').into_iter();

        res.headers.serialize(&mut lines);

        let body: String = lines.collect::<Vec<&str>>().join("\n");
        res.headers.put(Header::new("Content-Length", &format!("{}", body.len())));
        
        if body.len() > 0 {
            res.body = Some(body);
        }

        res.status = status;

        Ok(res)
    }
}

pub struct Config {
    map: HashMap<String, Script>
}

impl Config {

    fn key(method: &Method, route: &str) -> String {
        format!("{}:{}", method.deserialize(), route)
    }

    pub fn load() -> Result<Self, Box<dyn Error>> {

        let config_path = env::var("CONFIG_PATH").unwrap_or_else(|_| String::from("config"));
        let config = read_to_string(&config_path)?;

        let mut map = HashMap::new();
        let lines = config.split("\n").into_iter();

        for (i, line) in lines.enumerate() {
            let mut parts = line.split_whitespace();

            let Some(method_str) = parts.next() else { continue };
            let method = Method::serialize(&method_str)
                .ok_or(format!("config line {i} has invalid http method: {method_str}"))?;

            let route = parts.next()
                .ok_or(format!("config line {i} missing http route"))?;

            let script = parts.next()
                .ok_or(format!{"config line {i} missing script path"})?;

            let key = Self::key(&method, route);
            let value = Script::new(script.to_owned());

            println!("adding script {script} for {} {route}", method.deserialize());
            map.insert(key, value);
        }

        if map.len() < 1 {
            return Err("cannot run server, config is empty".into())
        }

        Ok(Self { map })
    }

    pub fn get(&self, method: &Method, route: &str) -> Option<&Script> {
        self.map.get(&Self::key(method, route))
    }

}