oseda_cli/cmd/
check.rs

1use std::time::Duration;
2
3use clap::Args;
4use reqwest::StatusCode;
5
6use crate::cmd::run;
7use crate::config;
8
9use crate::net::{self, kill_port};
10
11#[derive(Args, Debug)]
12pub struct CheckOptions {
13    #[arg(long, default_value_t = false)]
14    skip_git: bool,
15    #[arg(long, default_value_t = 3000)]
16    port: u16,
17}
18
19#[derive(Debug)]
20pub enum OsedaCheckError {
21    MissingConfig(String),
22    BadConfig(String),
23    BadGitCredentials(String),
24    DirectoryNameMismatch(String),
25    CouldNotPingLocalPresentation(String),
26}
27
28impl std::error::Error for OsedaCheckError {}
29
30impl std::fmt::Display for OsedaCheckError {
31    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
32        match self {
33            Self::MissingConfig(msg) => write!(f, "Missing config file {}", msg),
34            Self::BadConfig(msg) => write!(f, "Bad config file {}", msg),
35            Self::BadGitCredentials(msg) => write!(f, "Missing git credentials {}", msg),
36            Self::DirectoryNameMismatch(msg) => {
37                write!(f, "Project name does not match directory {}", msg)
38            }
39            Self::CouldNotPingLocalPresentation(msg) => {
40                write!(f, "Could not ping localhost after project was ran {}", msg)
41            }
42        }
43    }
44}
45
46pub fn check(opts: CheckOptions) -> Result<(), OsedaCheckError> {
47    // separate abstraction layer here, want the primary subcommand to call this
48    // verify can also be called from deploy (in theory)
49    match verify_project(opts.skip_git, opts.port) {
50        OsedaProjectStatus::DeployReady => Ok(()),
51        OsedaProjectStatus::NotDeploymentReady(err) => Err(err),
52    }
53}
54
55pub enum OsedaProjectStatus {
56    DeployReady,
57    NotDeploymentReady(OsedaCheckError),
58}
59
60pub fn verify_project(skip_git: bool, port_num: u16) -> OsedaProjectStatus {
61    // TODO: document me -> assumes working directory is the project folder
62
63    let _conf = match config::read_and_validate_config(skip_git) {
64        Ok(conf) => conf,
65        Err(err) => return OsedaProjectStatus::NotDeploymentReady(err),
66    };
67
68    let _run_handle = std::thread::spawn(run::run);
69
70    std::thread::sleep(Duration::from_millis(5000));
71
72    let addr = format!("http://localhost:{}", port_num);
73    let status = match net::get_status(&addr) {
74        Ok(status) => status,
75        Err(_) => {
76            return OsedaProjectStatus::NotDeploymentReady(
77                OsedaCheckError::CouldNotPingLocalPresentation(
78                    "Could not ping presentation".to_owned(),
79                ),
80            );
81        }
82    };
83
84    if status != StatusCode::OK {
85        return OsedaProjectStatus::NotDeploymentReady(
86            OsedaCheckError::CouldNotPingLocalPresentation(
87                "Presentation returned non 200 error status code".to_owned(),
88            ),
89        );
90    }
91
92    println!("Project returned status code {:?}", status);
93
94    // due to memory issues, no nice way to kill run_handle
95    // run_handle.kill();
96    // so we'll go through the OS instead.
97    // This can also be solved with an atomic boolean in run, this
98    // would also get rid of the mpsc stuff going on in run(), but honestly
99    // im just not that familiar with the mpsc pattern and rust api
100
101    if kill_port(port_num).is_err() {
102        println!("Warning: could not kill process on port, project could still be running");
103    } else {
104        println!("Project process sucessfully terminated");
105    }
106
107    OsedaProjectStatus::DeployReady
108}