oseda_cli/cmd/
check.rs

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