summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/config.rs4
-rw-r--r--src/error.rs47
-rw-r--r--src/main.rs22
-rw-r--r--src/requests/device.rs39
-rw-r--r--src/requests/start.rs108
5 files changed, 109 insertions, 111 deletions
diff --git a/src/config.rs b/src/config.rs
index 78795a3..d28e111 100644
--- a/src/config.rs
+++ b/src/config.rs
@@ -9,8 +9,8 @@ pub struct Config {
9impl Config { 9impl Config {
10 pub fn load() -> Result<Config, config::ConfigError> { 10 pub fn load() -> Result<Config, config::ConfigError> {
11 let builder = config::Config::builder() 11 let builder = config::Config::builder()
12 .add_source(config::File::with_name("~/.config/webol-cli.toml")) 12 .add_source(config::File::with_name("~/.config/webol-cli").required(false))
13 .add_source(config::File::with_name("webol-cli.toml")) 13 .add_source(config::File::with_name("webol-cli").required(false))
14 .add_source(config::Environment::with_prefix("WEBOL_CLI_").separator("_")) 14 .add_source(config::Environment::with_prefix("WEBOL_CLI_").separator("_"))
15 .build()?; 15 .build()?;
16 16
diff --git a/src/error.rs b/src/error.rs
index 531528f..15e4308 100644
--- a/src/error.rs
+++ b/src/error.rs
@@ -1,21 +1,34 @@
1use std::{fmt::Debug, num::ParseIntError}; 1use std::{fmt::Debug, num::ParseIntError};
2 2
3pub enum CliError { 3use reqwest::header::InvalidHeaderValue;
4 Reqwest(reqwest::Error),
5 Config(config::ConfigError),
6 Serde(serde_json::Error),
7 Parse(ParseIntError),
8 WsResponse,
9}
10 4
11impl Debug for CliError { 5#[derive(Debug, thiserror::Error)]
12 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 6pub enum Error {
13 match self { 7 #[error("request: {source}")]
14 Self::Reqwest(err) => err.fmt(f), 8 Reqwest {
15 Self::Config(err) => err.fmt(f), 9 #[from]
16 Self::Serde(err) => err.fmt(f), 10 source: reqwest::Error,
17 Self::Parse(err) => err.fmt(f), 11 },
18 Self::WsResponse => f.write_str("Error in Response"), 12 #[error("config: {source}")]
19 } 13 Config {
20 } 14 #[from]
15 source: config::ConfigError,
16 },
17 #[error("serde: {source}")]
18 Serde {
19 #[from]
20 source: serde_json::Error,
21 },
22 #[error("parse int: {source}")]
23 Parse {
24 #[from]
25 source: ParseIntError,
26 },
27 #[error("parse header: {source}")]
28 InvalidHeaderValue {
29 #[from]
30 source: InvalidHeaderValue
31 },
32 #[error("ws")]
33 WsResponse,
21} 34}
diff --git a/src/main.rs b/src/main.rs
index 0393183..cdca6cb 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -3,7 +3,7 @@ use std::{fmt::Display, time::Duration};
3use crate::config::Config; 3use crate::config::Config;
4use clap::{Command, CommandFactory, Parser, Subcommand}; 4use clap::{Command, CommandFactory, Parser, Subcommand};
5use clap_complete::{generate, Generator, Shell}; 5use clap_complete::{generate, Generator, Shell};
6use error::CliError; 6use error::Error;
7use indicatif::{MultiProgress, ProgressBar, ProgressStyle}; 7use indicatif::{MultiProgress, ProgressBar, ProgressStyle};
8use requests::{device, start::start}; 8use requests::{device, start::start};
9use reqwest::header::{HeaderMap, HeaderValue}; 9use reqwest::header::{HeaderMap, HeaderValue};
@@ -66,8 +66,8 @@ enum DeviceCmd {
66} 66}
67 67
68#[tokio::main] 68#[tokio::main]
69async fn main() -> Result<(), CliError> { 69async fn main() -> Result<(), Error> {
70 let config = Config::load().map_err(CliError::Config)?; 70 let config = Config::load()?;
71 71
72 let cli = Args::parse(); 72 let cli = Args::parse();
73 73
@@ -99,7 +99,7 @@ async fn main() -> Result<(), CliError> {
99 Commands::CliGen { id } => { 99 Commands::CliGen { id } => {
100 eprintln!("Generating completion file for {id:?}..."); 100 eprintln!("Generating completion file for {id:?}...");
101 let mut cmd = Args::command(); 101 let mut cmd = Args::command();
102 print_completions(id, &mut cmd) 102 print_completions(id, &mut cmd);
103 } 103 }
104 } 104 }
105 105
@@ -110,26 +110,26 @@ fn print_completions<G: Generator>(gen: G, cmd: &mut Command) {
110 generate(gen, cmd, cmd.get_name().to_string(), &mut std::io::stdout()); 110 generate(gen, cmd, cmd.get_name().to_string(), &mut std::io::stdout());
111} 111}
112 112
113fn default_headers(config: &Config) -> Result<HeaderMap, CliError> { 113fn default_headers(config: &Config) -> Result<HeaderMap, Error> {
114 let mut map = HeaderMap::new(); 114 let mut map = HeaderMap::new();
115 map.append( 115 map.append(
116 "Accept-Content", 116 "Accept-Content",
117 HeaderValue::from_str("application/json").unwrap(), 117 HeaderValue::from_str("application/json")?
118 ); 118 );
119 map.append( 119 map.append(
120 "Content-Type", 120 "Content-Type",
121 HeaderValue::from_str("application/json").unwrap(), 121 HeaderValue::from_str("application/json")?
122 ); 122 );
123 map.append( 123 map.append(
124 "Authorization", 124 "Authorization",
125 HeaderValue::from_str(&config.apikey).unwrap(), 125 HeaderValue::from_str(&config.apikey)?
126 ); 126 );
127 127
128 Ok(map) 128 Ok(map)
129} 129}
130 130
131fn format_url(config: &Config, path: &str, protocol: Protocols) -> Result<String, CliError> { 131fn format_url(config: &Config, path: &str, protocol: &Protocols) -> String {
132 Ok(format!("{}://{}/{}", protocol, config.server, path)) 132 format!("{}://{}/{}", protocol, config.server, path)
133} 133}
134 134
135fn add_pb(mp: &MultiProgress, template: &str, message: String) -> ProgressBar { 135fn add_pb(mp: &MultiProgress, template: &str, message: String) -> ProgressBar {
@@ -141,7 +141,7 @@ fn add_pb(mp: &MultiProgress, template: &str, message: String) -> ProgressBar {
141 pb 141 pb
142} 142}
143 143
144fn finish_pb(pb: ProgressBar, message: String, template: &str) { 144fn finish_pb(pb: &ProgressBar, message: String, template: &str) {
145 pb.set_style(ProgressStyle::with_template(template).unwrap()); 145 pb.set_style(ProgressStyle::with_template(template).unwrap());
146 pb.finish_with_message(message); 146 pb.finish_with_message(message);
147} 147}
diff --git a/src/requests/device.rs b/src/requests/device.rs
index 5003c4a..a612978 100644
--- a/src/requests/device.rs
+++ b/src/requests/device.rs
@@ -1,4 +1,4 @@
1use crate::{config::Config, default_headers, error::CliError, format_url, Protocols}; 1use crate::{config::Config, default_headers, error::Error, format_url, Protocols};
2 2
3pub async fn put( 3pub async fn put(
4 config: &Config, 4 config: &Config,
@@ -6,38 +6,35 @@ pub async fn put(
6 mac: String, 6 mac: String,
7 broadcast_addr: String, 7 broadcast_addr: String,
8 ip: String, 8 ip: String,
9) -> Result<(), CliError> { 9) -> Result<(), Error> {
10 let url = format_url(config, "device", Protocols::Http)?; 10 let url = format_url(config, "device", &Protocols::Http);
11 println!("{}", url); 11 println!("{url}");
12 let res = reqwest::Client::new() 12 let res = reqwest::Client::new()
13 .put(url) 13 .put(url)
14 .headers(default_headers(config)?) 14 .headers(default_headers(config)?)
15 .body(format!( 15 .body(format!(
16 r#"{{"id": "{}", "mac": "{}", "broadcast_addr": "{}", "ip": "{}"}}"#, 16 r#"{{"id": "{id}", "mac": "{mac}", "broadcast_addr": "{broadcast_addr}", "ip": "{ip}"}}"#,
17 id, mac, broadcast_addr, ip
18 )) 17 ))
19 .send() 18 .send()
20 .await 19 .await?
21 .map_err(CliError::Reqwest)?
22 .text() 20 .text()
23 .await; 21 .await;
24 22
25 println!("{:?}", res); 23 println!("{res:?}");
26 Ok(()) 24 Ok(())
27} 25}
28 26
29pub async fn get(config: &Config, id: String) -> Result<(), CliError> { 27pub async fn get(config: &Config, id: String) -> Result<(), Error> {
30 let res = reqwest::Client::new() 28 let res = reqwest::Client::new()
31 .get(format_url(config, "device", Protocols::Http)?) 29 .get(format_url(config, "device", &Protocols::Http))
32 .headers(default_headers(config)?) 30 .headers(default_headers(config)?)
33 .body(format!(r#"{{"id": "{}"}}"#, id)) 31 .body(format!(r#"{{"id": "{id}"}}"#))
34 .send() 32 .send()
35 .await 33 .await?
36 .map_err(CliError::Reqwest)?
37 .text() 34 .text()
38 .await; 35 .await;
39 36
40 println!("{:?}", res); 37 println!("{res:?}");
41 Ok(()) 38 Ok(())
42} 39}
43 40
@@ -47,20 +44,18 @@ pub async fn post(
47 mac: String, 44 mac: String,
48 broadcast_addr: String, 45 broadcast_addr: String,
49 ip: String, 46 ip: String,
50) -> Result<(), CliError> { 47) -> Result<(), Error> {
51 let res = reqwest::Client::new() 48 let res = reqwest::Client::new()
52 .post(format_url(config, "device", Protocols::Http)?) 49 .post(format_url(config, "device", &Protocols::Http))
53 .headers(default_headers(config)?) 50 .headers(default_headers(config)?)
54 .body(format!( 51 .body(format!(
55 r#"{{"id": "{}", "mac": "{}", "broadcast_addr": "{}", "ip": "{}"}}"#, 52 r#"{{"id": "{id}", "mac": "{mac}", "broadcast_addr": "{broadcast_addr}", "ip": "{ip}"}}"#,
56 id, mac, broadcast_addr, ip
57 )) 53 ))
58 .send() 54 .send()
59 .await 55 .await?
60 .map_err(CliError::Reqwest)?
61 .text() 56 .text()
62 .await; 57 .await;
63 58
64 println!("{:?}", res); 59 println!("{res:?}");
65 Ok(()) 60 Ok(())
66} 61}
diff --git a/src/requests/start.rs b/src/requests/start.rs
index bc63303..7abbbe0 100644
--- a/src/requests/start.rs
+++ b/src/requests/start.rs
@@ -5,64 +5,54 @@ use serde::Deserialize;
5use tokio_tungstenite::{connect_async, tungstenite::Message}; 5use tokio_tungstenite::{connect_async, tungstenite::Message};
6 6
7use crate::{ 7use crate::{
8 add_pb, config::Config, default_headers, error::CliError, finish_pb, format_url, ErrorResponse, 8 add_pb, config::Config, default_headers, error::Error, finish_pb, format_url, ErrorResponse,
9 Protocols, DEFAULT_STYLE, DONE_STYLE, ERROR_STYLE, OVERVIEW_DONE, OVERVIEW_ERROR, 9 Protocols, DEFAULT_STYLE, DONE_STYLE, ERROR_STYLE, OVERVIEW_DONE, OVERVIEW_ERROR,
10 OVERVIEW_STYLE, 10 OVERVIEW_STYLE,
11}; 11};
12 12
13pub async fn start(config: &Config, id: String, ping: bool) -> Result<(), CliError> { 13pub async fn start(config: &Config, id: String, ping: bool) -> Result<(), Error> {
14 let send_start = MultiProgress::new(); 14 let send_start = MultiProgress::new();
15 let overview = add_pb(&send_start, OVERVIEW_STYLE, format!(") start {}", id)); 15 let overview = add_pb(&send_start, OVERVIEW_STYLE, format!(") start {id}"));
16 16
17 // TODO: calculate average start-time on server 17 let url = format_url(config, "start", &Protocols::Http);
18 let url = format_url(config, "start", Protocols::Http)?; 18 let connect = add_pb(&send_start, DEFAULT_STYLE, format!("connect to {url}"));
19 let connect = add_pb(&send_start, DEFAULT_STYLE, format!("connect to {}", url));
20 let res = reqwest::Client::new() 19 let res = reqwest::Client::new()
21 .post(url) 20 .post(url)
22 .headers(default_headers(config)?) 21 .headers(default_headers(config)?)
23 .body(format!(r#"{{"id": "{}", "ping": {}}}"#, id, ping)) 22 .body(format!(r#"{{"id": "{id}", "ping": {ping}}}"#))
24 .send() 23 .send()
25 .await 24 .await?;
26 .map_err(CliError::Reqwest)?; 25 finish_pb(&connect, "connected, got response".to_string(), DONE_STYLE);
27 finish_pb(connect, "connected, got response".to_string(), DONE_STYLE);
28 26
29 let res_pb = add_pb(&send_start, DEFAULT_STYLE, "analyzing response".to_string()); 27 let res_pb = add_pb(&send_start, DEFAULT_STYLE, "analyzing response".to_string());
30 match res.status() {
31 StatusCode::OK => {
32 let body = serde_json::from_str::<StartResponse>(
33 &res.text().await.map_err(CliError::Reqwest)?,
34 )
35 .map_err(CliError::Serde)?;
36
37 if body.boot {
38 finish_pb(res_pb, "sent start packet".to_string(), DONE_STYLE);
39 }
40 28
41 if ping { 29 if res.status() == StatusCode::OK {
42 let status = status_socket(config, body.uuid, &send_start, &overview, id).await?; 30 let body = serde_json::from_str::<StartResponse>(&res.text().await?)?;
43 if status { 31
44 finish_pb( 32 if body.boot {
45 overview, 33 finish_pb(&res_pb, "sent start packet".to_string(), DONE_STYLE);
46 format!("successfully started {}", body.id),
47 OVERVIEW_DONE,
48 );
49 } else {
50 finish_pb(
51 overview,
52 format!("error while starting {}", body.id),
53 OVERVIEW_ERROR,
54 );
55 }
56 }
57 } 34 }
58 _ => {
59 let body = serde_json::from_str::<ErrorResponse>(
60 &res.text().await.map_err(CliError::Reqwest)?,
61 )
62 .map_err(CliError::Serde)?;
63 35
64 res_pb.finish_with_message(format!("✗ got error: {}", body.error)); 36 if ping {
37 let status = status_socket(config, body.uuid, &send_start, &overview, id).await?;
38 if status {
39 finish_pb(
40 &overview,
41 format!("successfully started {}", body.id),
42 OVERVIEW_DONE,
43 );
44 } else {
45 finish_pb(
46 &overview,
47 format!("error while starting {}", body.id),
48 OVERVIEW_ERROR,
49 );
50 }
65 } 51 }
52 } else {
53 let body = serde_json::from_str::<ErrorResponse>(&res.text().await?)?;
54
55 res_pb.finish_with_message(format!("✗ got error: {}", body.error));
66 } 56 }
67 57
68 Ok(()) 58 Ok(())
@@ -74,60 +64,60 @@ async fn status_socket(
74 pb: &MultiProgress, 64 pb: &MultiProgress,
75 overview: &ProgressBar, 65 overview: &ProgressBar,
76 id: String, 66 id: String,
77) -> Result<bool, CliError> { 67) -> Result<bool, Error> {
78 let ws_pb = add_pb(pb, DEFAULT_STYLE, "connect to websocket".to_string()); 68 let ws_pb = add_pb(pb, DEFAULT_STYLE, "connect to websocket".to_string());
79 let (mut ws_stream, _response) = 69 let (mut ws_stream, _response) =
80 connect_async(format_url(config, "status", Protocols::Websocket)?) 70 connect_async(format_url(config, "status", &Protocols::Websocket))
81 .await 71 .await
82 .expect("Failed to connect"); 72 .expect("Failed to connect");
83 finish_pb(ws_pb, "connected to websocket".to_string(), DONE_STYLE); 73 finish_pb(&ws_pb, "connected to websocket".to_string(), DONE_STYLE);
84 74
85 ws_stream.send(Message::Text(uuid.clone())).await.unwrap(); 75 ws_stream.send(Message::Text(uuid.clone())).await.unwrap();
86 76
87 // Get ETA 77 // Get ETA
88 let eta_msg = ws_stream.next().await.unwrap().unwrap(); 78 let eta_msg = ws_stream.next().await.unwrap().unwrap();
89 let eta = get_eta(eta_msg.into_text().unwrap(), uuid.clone())? + overview.elapsed().as_secs(); 79 let eta = get_eta(&eta_msg.into_text().unwrap(), &uuid)? + overview.elapsed().as_secs();
90 overview.set_message(format!("/{}) start {}", eta, id)); 80 overview.set_message(format!("/{eta}) start {id}"));
91 81
92 let msg_pb = add_pb(pb, DEFAULT_STYLE, "await message".to_string()); 82 let msg_pb = add_pb(pb, DEFAULT_STYLE, "await message".to_string());
93 let msg = ws_stream.next().await.unwrap(); 83 let msg = ws_stream.next().await.unwrap();
94 finish_pb(msg_pb, "received message".to_string(), DONE_STYLE); 84 finish_pb(&msg_pb, "received message".to_string(), DONE_STYLE);
95 85
96 ws_stream.close(None).await.unwrap(); 86 ws_stream.close(None).await.unwrap();
97 87
98 let v_pb = add_pb(pb, DEFAULT_STYLE, "verify response".to_string()); 88 let v_pb = add_pb(pb, DEFAULT_STYLE, "verify response".to_string());
99 let res = verify_response(msg.unwrap().to_string(), uuid)?; 89 let res = verify_response(&msg.unwrap().to_string(), &uuid)?;
100 match res { 90 match res {
101 Verified::WrongUuid => { 91 Verified::WrongUuid => {
102 finish_pb(v_pb, "returned wrong uuid".to_string(), ERROR_STYLE); 92 finish_pb(&v_pb, "returned wrong uuid".to_string(), ERROR_STYLE);
103 Ok(false) 93 Ok(false)
104 } 94 }
105 Verified::ResponseType(res_type) => match res_type { 95 Verified::ResponseType(res_type) => match res_type {
106 ResponseType::Start => { 96 ResponseType::Start => {
107 finish_pb(v_pb, "device started".to_string(), DONE_STYLE); 97 finish_pb(&v_pb, "device started".to_string(), DONE_STYLE);
108 Ok(true) 98 Ok(true)
109 } 99 }
110 ResponseType::Timeout => { 100 ResponseType::Timeout => {
111 finish_pb(v_pb, "ping timed out".to_string(), ERROR_STYLE); 101 finish_pb(&v_pb, "ping timed out".to_string(), ERROR_STYLE);
112 Ok(false) 102 Ok(false)
113 } 103 }
114 ResponseType::NotFound => { 104 ResponseType::NotFound => {
115 finish_pb(v_pb, "unknown uuid".to_string(), ERROR_STYLE); 105 finish_pb(&v_pb, "unknown uuid".to_string(), ERROR_STYLE);
116 Ok(false) 106 Ok(false)
117 } 107 }
118 }, 108 },
119 } 109 }
120} 110}
121 111
122fn get_eta(msg: String, uuid: String) -> Result<u64, CliError> { 112fn get_eta(msg: &str, uuid: &str) -> Result<u64, Error> {
123 let spl: Vec<&str> = msg.split('_').collect(); 113 let spl: Vec<&str> = msg.split('_').collect();
124 if (spl[0] != "eta") || (spl[2] != uuid) { 114 if (spl[0] != "eta") || (spl[2] != uuid) {
125 return Err(CliError::WsResponse); 115 return Err(Error::WsResponse);
126 }; 116 };
127 Ok(u64::from_str_radix(spl[1], 10).map_err(CliError::Parse)?) 117 Ok(spl[1].parse()?)
128} 118}
129 119
130fn verify_response(res: String, org_uuid: String) -> Result<Verified, CliError> { 120fn verify_response(res: &str, org_uuid: &str) -> Result<Verified, Error> {
131 let spl: Vec<&str> = res.split('_').collect(); 121 let spl: Vec<&str> = res.split('_').collect();
132 let res_type = spl[0]; 122 let res_type = spl[0];
133 let uuid = spl[1]; 123 let uuid = spl[1];
@@ -158,12 +148,12 @@ enum ResponseType {
158} 148}
159 149
160impl ResponseType { 150impl ResponseType {
161 fn from(value: &str) -> Result<Self, CliError> { 151 fn from(value: &str) -> Result<Self, Error> {
162 match value { 152 match value {
163 "start" => Ok(ResponseType::Start), 153 "start" => Ok(ResponseType::Start),
164 "timeout" => Ok(ResponseType::Timeout), 154 "timeout" => Ok(ResponseType::Timeout),
165 "notfound" => Ok(ResponseType::NotFound), 155 "notfound" => Ok(ResponseType::NotFound),
166 _ => Err(CliError::WsResponse), 156 _ => Err(Error::WsResponse),
167 } 157 }
168 } 158 }
169} 159}