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