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