1use std::{
2 sync::{
3 atomic::{AtomicBool, Ordering},
4 Arc,
5 },
6 time::Duration,
7};
8
9use clap::Args;
10use reqwest::StatusCode;
11
12use crate::cmd::run;
13use crate::config;
14
15use crate::net::{self, kill_port};
16
17#[derive(Args, Debug)]
19pub struct CheckOptions {
20 #[arg(long, default_value_t = 3000)]
23 port: u16,
24}
25#[derive(Debug)]
27pub enum OsedaCheckError {
28 MissingConfig(String),
29 BadConfig(String),
30 BadGitCredentials(String),
31 DirectoryNameMismatch(String),
32 CouldNotPingLocalPresentation(String),
33 MissingDescription(String),
34 MissingTags(String),
35}
36
37impl std::error::Error for OsedaCheckError {}
38
39impl std::fmt::Display for OsedaCheckError {
41 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
42 match self {
43 Self::MissingConfig(msg) => write!(f, "Missing config file: {}", msg),
44 Self::BadConfig(msg) => write!(f, "Bad config file: {}", msg),
45 Self::BadGitCredentials(msg) => write!(f, "Missing git credentials: {}", msg),
46 Self::DirectoryNameMismatch(msg) => {
47 write!(f, "Project name does not match directory: {}", msg)
48 }
49 Self::CouldNotPingLocalPresentation(msg) => {
50 write!(f, "Could not ping localhost after project was ran: {}", msg)
51 }
52 Self::MissingDescription(msg) => {
53 write!(f, "Config file is missing description: {}", msg)
54 }
55 Self::MissingTags(msg) => {
56 write!(f, "No tags detected: {}", msg)
57 }
58 }
59 }
60}
61
62pub fn check(opts: CheckOptions) -> Result<(), OsedaCheckError> {
71 match verify_project(opts.port) {
74 OsedaProjectStatus::DeployReady => Ok(()),
75 OsedaProjectStatus::NotDeploymentReady(err) => Err(err),
76 }
77}
78
79pub enum OsedaProjectStatus {
81 DeployReady,
82 NotDeploymentReady(OsedaCheckError),
83}
84
85fn verify_project(port_num: u16) -> OsedaProjectStatus {
95 let _conf = match config::read_and_validate_config() {
96 Ok(conf) => conf,
97 Err(err) => return OsedaProjectStatus::NotDeploymentReady(err),
98 };
99
100 let shutdown_flag = Arc::new(AtomicBool::new(false));
101 let shutdown_flag_clone = shutdown_flag.clone();
102
103 let run_handle = std::thread::spawn(move || {
105 let _ = run::run_with_shutdown(shutdown_flag_clone);
106 });
107
108 let addr = format!("http://localhost:{}", port_num);
109 let mut status = None;
110
111 let max_polls = 100;
113 let poll_delay = Duration::from_millis(200);
114
115 for i in 0..max_polls {
116 println!("polled {}", i);
117 if let Ok(res_status) = net::get_status(&addr) {
118 if res_status == StatusCode::OK {
119 status = Some(res_status);
120 break;
121 }
122 }
123 std::thread::sleep(poll_delay);
124 }
125
126 let status = match status {
127 Some(status) => status,
128 None => {
129 shutdown_flag.store(true, Ordering::SeqCst);
131 let _ = run_handle.join();
132 return OsedaProjectStatus::NotDeploymentReady(
133 OsedaCheckError::CouldNotPingLocalPresentation(
134 "Could not ping presentation".to_owned(),
135 ),
136 );
137 }
138 };
139
140 if status != StatusCode::OK {
141 shutdown_flag.store(true, Ordering::SeqCst);
143 let _ = run_handle.join();
144 return OsedaProjectStatus::NotDeploymentReady(
145 OsedaCheckError::CouldNotPingLocalPresentation(
146 "Presentation returned non 200 error status code".to_owned(),
147 ),
148 );
149 }
150
151 println!("Project returned status code {:?}", status);
152
153 shutdown_flag.store(true, Ordering::SeqCst);
162 if run_handle.join().is_err() {
163 if kill_port(port_num).is_err() {
164 println!("Warning: could not kill process on port, project could still be running");
165 }
166 } else {
167 println!("Project process sucessfully terminated");
168 }
169
170 OsedaProjectStatus::DeployReady
171}