Skip to main content

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/// Options for the `oseda check` command
12#[derive(Args, Debug)]
13pub struct CheckOptions {
14    /// Port to check for the Oseda project on
15    /// This is only useful if you have changed the default port that Oseda projects run on my default (3000)
16    #[arg(long, default_value_t = 3000)]
17    port: u16,
18}
19/// All common error types that could cause `oseda check` to fail
20#[derive(Debug)]
21pub enum OsedaCheckError {
22    MissingConfig(String),
23    BadConfig(String),
24    BadGitCredentials(String),
25    DirectoryNameMismatch(String),
26    CouldNotPingLocalPresentation(String),
27}
28
29impl std::error::Error for OsedaCheckError {}
30
31/// Display options with more verbose messagess
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
48/// Checks the Oseda project in the working directory for common oseda errors
49///
50/// # Arguments
51/// * `opts` - options parsed from CLI flags
52///
53/// # Returns
54/// * `Ok(())` if the project passes all checks and is considered as "deployabl"e
55/// * `Err(OsedaCheckError)` a problem was detected that prevents the user from doing a deployment
56pub fn check(opts: CheckOptions) -> Result<(), OsedaCheckError> {
57    // separate abstraction layer here, want the primary subcommand to call this
58    // verify can also be called from deploy (in theory)
59    match verify_project(opts.port) {
60        OsedaProjectStatus::DeployReady => Ok(()),
61        OsedaProjectStatus::NotDeploymentReady(err) => Err(err),
62    }
63}
64
65/// Status of Oseda project, plan to make this more verbose later
66pub enum OsedaProjectStatus {
67    DeployReady,
68    NotDeploymentReady(OsedaCheckError),
69}
70
71/// Verifies a project passes all common checks
72///
73/// # Arguments
74/// * `skip_git` - skips git authorship validation
75/// * `port_num` - the port to check for the running project (defaults to 3000)
76///
77/// # Returns
78/// * `OsedaProjectStatus::DeployReady` if the project passes all checks
79/// * `OsedaProjectStatus::NotDeploymentReady(err)` if something fails that is commonly seen
80fn verify_project(port_num: u16) -> OsedaProjectStatus {
81    // TODO: document me -> assumes working directory is the project folder
82
83    let _conf = match config::read_and_validate_config() {
84        Ok(conf) => conf,
85        Err(err) => return OsedaProjectStatus::NotDeploymentReady(err),
86    };
87
88    let _run_handle = std::thread::spawn(run::run);
89
90    std::thread::sleep(Duration::from_millis(10000));
91
92    let addr = format!("http://localhost:{}", port_num);
93    let status = match net::get_status(&addr) {
94        Ok(status) => status,
95        Err(_) => {
96            return OsedaProjectStatus::NotDeploymentReady(
97                OsedaCheckError::CouldNotPingLocalPresentation(
98                    "Could not ping presentation".to_owned(),
99                ),
100            );
101        }
102    };
103
104    if status != StatusCode::OK {
105        return OsedaProjectStatus::NotDeploymentReady(
106            OsedaCheckError::CouldNotPingLocalPresentation(
107                "Presentation returned non 200 error status code".to_owned(),
108            ),
109        );
110    }
111
112    println!("Project returned status code {:?}", status);
113
114    // due to memory issues, no nice way to kill run_handle
115    // eg -> no run_handle.kill();
116    // so we'll go through the OS instead.
117    // This can also be solved with an atomic boolean in run, this
118    // would also get rid of the mpsc stuff going on in run(), but honestly
119    // im just not that familiar with the mpsc pattern and rust api
120
121    if kill_port(port_num).is_err() {
122        println!("Warning: could not kill process on port, project could still be running");
123    } else {
124        println!("Project process sucessfully terminated");
125    }
126
127    OsedaProjectStatus::DeployReady
128}