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    MissingDescription(String),
28}
29
30impl std::error::Error for OsedaCheckError {}
31
32/// Display options with more verbose messagess
33impl std::fmt::Display for OsedaCheckError {
34    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
35        match self {
36            Self::MissingConfig(msg) => write!(f, "Missing config file {}", msg),
37            Self::BadConfig(msg) => write!(f, "Bad config file {}", msg),
38            Self::BadGitCredentials(msg) => write!(f, "Missing git credentials {}", msg),
39            Self::DirectoryNameMismatch(msg) => {
40                write!(f, "Project name does not match directory {}", msg)
41            }
42            Self::CouldNotPingLocalPresentation(msg) => {
43                write!(f, "Could not ping localhost after project was ran {}", msg)
44            }
45            Self::MissingDescription(msg) => {
46                write!(f, "Config file is missing description {}", msg)
47            }
48        }
49    }
50}
51
52/// Checks the Oseda project in the working directory for common oseda errors
53///
54/// # Arguments
55/// * `opts` - options parsed from CLI flags
56///
57/// # Returns
58/// * `Ok(())` if the project passes all checks and is considered as "deployabl"e
59/// * `Err(OsedaCheckError)` a problem was detected that prevents the user from doing a deployment
60pub fn check(opts: CheckOptions) -> Result<(), OsedaCheckError> {
61    // separate abstraction layer here, want the primary subcommand to call this
62    // verify can also be called from deploy (in theory)
63    match verify_project(opts.port) {
64        OsedaProjectStatus::DeployReady => Ok(()),
65        OsedaProjectStatus::NotDeploymentReady(err) => Err(err),
66    }
67}
68
69/// Status of Oseda project, plan to make this more verbose later
70pub enum OsedaProjectStatus {
71    DeployReady,
72    NotDeploymentReady(OsedaCheckError),
73}
74
75/// Verifies a project passes all common checks
76///
77/// # Arguments
78/// * `skip_git` - skips git authorship validation
79/// * `port_num` - the port to check for the running project (defaults to 3000)
80///
81/// # Returns
82/// * `OsedaProjectStatus::DeployReady` if the project passes all checks
83/// * `OsedaProjectStatus::NotDeploymentReady(err)` if something fails that is commonly seen
84fn verify_project(port_num: u16) -> OsedaProjectStatus {
85    // TODO: document me -> assumes working directory is the project folder
86
87    let _conf = match config::read_and_validate_config() {
88        Ok(conf) => conf,
89        Err(err) => return OsedaProjectStatus::NotDeploymentReady(err),
90    };
91
92    let _run_handle = std::thread::spawn(run::run);
93
94    std::thread::sleep(Duration::from_millis(10000));
95
96    let addr = format!("http://localhost:{}", port_num);
97    let status = match net::get_status(&addr) {
98        Ok(status) => status,
99        Err(_) => {
100            return OsedaProjectStatus::NotDeploymentReady(
101                OsedaCheckError::CouldNotPingLocalPresentation(
102                    "Could not ping presentation".to_owned(),
103                ),
104            );
105        }
106    };
107
108    if status != StatusCode::OK {
109        return OsedaProjectStatus::NotDeploymentReady(
110            OsedaCheckError::CouldNotPingLocalPresentation(
111                "Presentation returned non 200 error status code".to_owned(),
112            ),
113        );
114    }
115
116    println!("Project returned status code {:?}", status);
117
118    // due to memory issues, no nice way to kill run_handle
119    // eg -> no run_handle.kill();
120    // so we'll go through the OS instead.
121    // This can also be solved with an atomic boolean in run, this
122    // would also get rid of the mpsc stuff going on in run(), but honestly
123    // im just not that familiar with the mpsc pattern and rust api
124
125    if kill_port(port_num).is_err() {
126        println!("Warning: could not kill process on port, project could still be running");
127    } else {
128        println!("Project process sucessfully terminated");
129    }
130
131    OsedaProjectStatus::DeployReady
132}