15 files changed
+234
-48
Cargo.lock
+23
-2
@@ -1691,6 +1691,8 @@ dependencies = [
1691
"serde_json",
1692
"serde_repr",
1693
"spinners",
1694
+ "strum 0.27.1",
1695
+ "strum_macros 0.27.1",
1696
]
1697
1698
[[package]]
@@ -1762,7 +1764,7 @@ checksum = "a0ef947f358b9c238923f764c72a4a9d42f2d637c46e059dbd319d6e7cfb4f82"
1764
dependencies = [
1765
"lazy_static",
1766
"maplit",
1765
- "strum",
1767
+ "strum 0.24.1",
1768
]
1769
1770
[[package]]
@@ -1783,9 +1785,15 @@ version = "0.24.1"
1785
source = "registry+https://github.com/rust-lang/crates.io-index"
1786
checksum = "063e6045c0e62079840579a7e47a355ae92f60eb74daaf156fb1e84ba164e63f"
1787
dependencies = [
1786
- "strum_macros",
1788
+ "strum_macros 0.24.3",
1789
]
1790
1791
+[[package]]
1792
+name = "strum"
1793
+version = "0.27.1"
1794
+source = "registry+https://github.com/rust-lang/crates.io-index"
1795
+checksum = "f64def088c51c9510a8579e3c5d67c65349dcf755e5479ad3d010aa6454e2c32"
1796
+
1797
[[package]]
1798
name = "strum_macros"
1799
version = "0.24.3"
@@ -1799,6 +1807,19 @@ dependencies = [
1807
"syn 1.0.109",
1808
]
1809
1810
+[[package]]
1811
+name = "strum_macros"
1812
+version = "0.27.1"
1813
+source = "registry+https://github.com/rust-lang/crates.io-index"
1814
+checksum = "c77a8c5abcaf0f9ce05d62342b7d298c346515365c36b673df4ebe3ced01fde8"
1815
+dependencies = [
1816
+ "heck 0.5.0",
1817
+ "proc-macro2",
1818
+ "quote",
1819
+ "rustversion",
1820
+ "syn 2.0.101",
1821
+]
1822
+
1823
[[package]]
1824
name = "subtle"
1825
version = "2.6.1"
Cargo.toml
+2
@@ -42,3 +42,5 @@ tracing-bunyan-formatter = "0.3.3"
42
tracing-subscriber = "0.3.14"
43
url = "2"
44
url-builder = "0.1.1"
45
+strum = "0.27"
46
+strum_macros = "0.27"
crates/cli/src/deploy/config.rs
+6
-2
@@ -53,12 +53,16 @@ fn handle_config_error() -> Result<Config, ConfigError> {
53
todo!()
54
}
55
56
-pub(crate) async fn check_project(env: Environment, id: i32) -> Result<(), ConfigError> {
56
+pub(crate) async fn check_project(
57
+ env: Environment,
58
+ access_token: String,
59
+ id: i32
60
+) -> Result<(), ConfigError> {
61
let mut spinner: Spinner = Spinner::new(
62
spinners::Spinners::Hamburger,
63
succeed_message("Validate project"),
64
);
61
- match get_project(env, id.to_string()).await {
65
+ match get_project(env, access_token, id.to_string()).await {
66
Ok(_) => {
67
spinner.stop_and_persist(&succeed_symbol(), succeed_message("Valid project"));
68
Ok(())
crates/cli/src/deploy/mod.rs
+5
-2
@@ -16,7 +16,7 @@ use git2::{PushOptions, RemoteCallbacks, Repository};
16
use remote_messages::{build_next_app, start_server};
17
use smbcloud_model::project::DeploymentPayload;
18
use smbcloud_model::project::DeploymentStatus;
19
-use smbcloud_networking::environment::Environment;
19
+use smbcloud_networking::{environment::Environment, get_smb_token};
20
use smbcloud_networking_project::crud_project_deployment_create::create;
21
use spinners::Spinner;
22
@@ -26,11 +26,14 @@ pub async fn process_deploy(env: Environment) -> Result<CommandResult> {
26
let _ = process_login(env).await;
27
}
28
29
+ // Get current token
30
+ let access_token = get_smb_token(env).await?;
31
+
32
// Check config.
33
let config = check_config(env).await?;
34
35
// Validate config with project.
33
- check_project(env, config.project.id).await?;
36
+ check_project(env, access_token, config.project.id).await?;
37
38
// Check remote repository setup.
39
let repo = match Repository::open(".") {
crates/cli/src/project/crud_read.rs
+3
-2
@@ -4,7 +4,7 @@ use crate::{
4
};
5
use anyhow::{anyhow, Result};
6
use smbcloud_model::project::Project;
7
-use smbcloud_networking::environment::Environment;
7
+use smbcloud_networking::{environment::Environment, get_smb_token};
8
use smbcloud_networking_project::{get_all, get_project};
9
use spinners::Spinner;
10
use tabled::{Table, Tabled};
@@ -65,7 +65,8 @@ pub async fn process_project_show(env: Environment, id: String) -> Result<Comman
65
spinners::Spinners::SimpleDotsScrolling,
66
succeed_message("Loading"),
67
);
68
- match get_project(env, id).await {
68
+ let access_token = get_smb_token(env).await?;
69
+ match get_project(env, access_token, id).await {
70
Ok(project) => {
71
spinner.stop_and_persist(&succeed_symbol(), succeed_message("Loaded."));
72
let message = succeed_message(&format!("Showing project {}.", &project.name));
crates/cli/src/project/deployment.rs
+6
-3
@@ -5,7 +5,7 @@ use crate::{
5
};
6
use anyhow::Result;
7
use smbcloud_model::project::Deployment;
8
-use smbcloud_networking::environment::Environment;
8
+use smbcloud_networking::{environment::Environment, get_smb_token};
9
use smbcloud_networking_project::crud_project_deployment_read::{
10
get_deployment_detail, list_deployments,
11
};
@@ -21,7 +21,9 @@ pub(crate) async fn process_deployment(
21
// Load project id from .smb/config.toml
22
let config = check_config(env).await?;
23
24
- check_project(env, config.project.id).await?;
24
+ let access_token = get_smb_token(env).await?;
25
+
26
+ check_project(env,access_token, config.project.id).await?;
27
28
if let Some(deployment_id) = id {
29
// Show detail for a specific deployment
@@ -31,7 +33,8 @@ pub(crate) async fn process_deployment(
33
show_deployment_detail(&deployment);
34
} else {
35
// List all deployments for the project
34
- let deployments = list_deployments(env, config.project.id).await?;
36
+ let access_token = get_smb_token(env).await?;
37
+ let deployments = list_deployments(env, access_token, config.project.id).await?;
38
spinner.stop_and_persist(&succeed_symbol(), succeed_message("Load all deployments"));
39
show_project_deployments(&deployments);
40
};
crates/cli/src/project/mod.rs
+1
-1
@@ -30,7 +30,7 @@ pub async fn process_project(env: Environment, commands: Commands) -> Result<Com
30
Commands::Show { id } => process_project_show(env, id).await,
31
Commands::Delete { id } => process_project_delete(env, id).await,
32
Commands::Use { id } => {
33
- let project = get_project(env, id).await?;
33
+ let project = get_project(env,"access token".to_owned(), id).await?;
34
35
let config = Config {
36
current_project: Some(project),
crates/smbcloud-model/Cargo.toml
+2
@@ -23,3 +23,5 @@ serde = { workspace = true, features = ["derive"] }
23
serde_json = { workspace = true }
24
serde_repr = { workspace = true }
25
spinners = { workspace = true }
26
+strum = { workspace = true }
27
+strum_macros = { workspace = true }
crates/smbcloud-model/src/error_codes.rs
new
+71
@@ -0,0 +1,71 @@
1
+use core::fmt;
2
+use std::{
3
+ error::Error,
4
+ fmt::{Display, Formatter},
5
+};
6
+
7
+use serde::{Deserialize, Serialize};
8
+use serde_repr::{Deserialize_repr, Serialize_repr};
9
+use strum_macros::EnumIter;
10
+
11
+#[derive(Serialize, Deserialize, Debug)]
12
+#[serde(untagged)]
13
+pub enum ErrorResponse {
14
+ Error {
15
+ error_code: ErrorCode,
16
+ message: String,
17
+ },
18
+}
19
+
20
+impl Error for ErrorResponse {}
21
+
22
+impl Display for ErrorResponse {
23
+ fn fmt(&self, f: &mut Formatter) -> fmt::Result {
24
+ write!(f, "{:?}", self)
25
+ }
26
+}
27
+
28
+#[derive(Serialize_repr, Deserialize_repr, Debug, EnumIter)]
29
+#[repr(i32)]
30
+pub enum ErrorCode {
31
+ Unknown = 0,
32
+ ParseError = 1,
33
+ NetworkError = 2,
34
+ // Projects
35
+ ProjectNotFound = 1000
36
+}
37
+
38
+impl Display for ErrorCode {
39
+ fn fmt(&self, f: &mut Formatter) -> fmt::Result {
40
+ write!(f, "{:?}", self)
41
+ }
42
+}
43
+
44
+impl ErrorCode {
45
+ /// Cannot expose the ErrorCode enum directly to Ruby,
46
+ /// so we need to get it from i32.
47
+ pub fn from_i32(value: i32) -> Self {
48
+ match value {
49
+ // Projects
50
+ 1000 => ErrorCode::ProjectNotFound,
51
+ // Fallback
52
+ 2 => ErrorCode::ParseError,
53
+ 1 => ErrorCode::NetworkError,
54
+ _ => ErrorCode::Unknown,
55
+ }
56
+ }
57
+
58
+ pub fn message(&self, l: Option<String>) -> &str {
59
+ print!("Language code: {:?}, {}", l, self);
60
+ match self {
61
+ ErrorCode::Unknown => "Unknown error.",
62
+ ErrorCode::ProjectNotFound => "Project not found.",
63
+ ErrorCode::ParseError => "Parse error.",
64
+ ErrorCode::NetworkError => "Network error.",
65
+ }
66
+ }
67
+
68
+ pub fn rb_constant_name(&self) -> String {
69
+ self.to_string()
70
+ }
71
+}
crates/smbcloud-model/src/lib.rs
+1
@@ -4,6 +4,7 @@ pub mod forgot;
4
pub mod login;
5
pub mod project;
6
pub mod signup;
7
+pub mod error_codes;
8
9
pub mod ar_date_format {
10
use chrono::{DateTime, Utc};
crates/smbcloud-networking-project/src/crud_project_deployment_read.rs
+11
-17
@@ -1,26 +1,20 @@
1
use crate::{build_project_deployment, build_project_deployment_index};
2
use anyhow::{anyhow, Result};
3
use reqwest::Client;
4
-use smbcloud_model::project::Deployment;
5
-use smbcloud_networking::{environment::Environment, get_smb_token};
4
+use smbcloud_model::{error_codes::ErrorResponse, project::Deployment};
5
+use smbcloud_networking::{constants::SMB_USER_AGENT, environment::Environment, get_smb_token, network::request};
6
7
-pub async fn list_deployments(env: Environment, project_id: i32) -> Result<Vec<Deployment>> {
8
- let token = get_smb_token(env).await?;
9
-
10
- let response = Client::new()
7
+pub async fn list_deployments(
8
+ env: Environment,
9
+ access_token: String,
10
+ project_id: i32
11
+) -> Result<Vec<Deployment>, ErrorResponse> {
12
+ let builder = Client::new()
13
.get(build_project_deployment_index(env, project_id.to_string()))
12
- .header("Authorization", token)
13
- .header("User-agent", "smbcloud-cli")
14
- .send()
15
- .await?;
14
+ .header("Authorization", access_token)
15
+ .header("User-agent", SMB_USER_AGENT);
16
17
- match response.status() {
18
- reqwest::StatusCode::OK => {
19
- let deployments: Vec<Deployment> = response.json().await?;
20
- Ok(deployments)
21
- }
22
- _ => Err(anyhow!("Failed to list deployments.")),
23
- }
17
+ request(builder).await
18
}
19
20
pub async fn get_deployment_detail(
crates/smbcloud-networking-project/src/lib.rs
+11
-19
@@ -5,10 +5,9 @@ use anyhow::{anyhow, Result};
5
use log::debug;
6
use reqwest::{Client, StatusCode};
7
use smbcloud_model::{
8
- self,
9
- project::{Project, ProjectCreate},
8
+ self, error_codes::ErrorResponse, project::{Project, ProjectCreate}
9
};
11
-use smbcloud_networking::{environment::Environment, get_smb_token, smb_base_url_builder};
10
+use smbcloud_networking::{constants::SMB_USER_AGENT, environment::Environment, get_smb_token, network::request, smb_base_url_builder};
11
12
pub async fn get_all(env: Environment) -> Result<Vec<Project>> {
13
// Get current token
@@ -53,24 +52,17 @@ pub async fn create_project(env: Environment, project: ProjectCreate) -> Result<
52
}
53
}
54
56
-pub async fn get_project(env: Environment, id: String) -> Result<Project> {
57
- // Get current token
58
- let token = get_smb_token(env).await?;
59
-
60
- let response = Client::new()
55
+pub async fn get_project(
56
+ env: Environment,
57
+ access_token: String,
58
+ id: String
59
+) -> Result<Project, ErrorResponse> {
60
+ let builder = Client::new()
61
.get(build_project_url_with_id(env, id))
62
- .header("Authorization", token)
63
- .send()
64
- .await?;
62
+ .header("Authorization", access_token)
63
+ .header("User-agent", SMB_USER_AGENT);
64
66
- match response.status() {
67
- reqwest::StatusCode::OK => {
68
- let project: Project = response.json().await?;
69
- //println!("Project requested: {project:#?}");
70
- Ok(project)
71
- }
72
- _ => Err(anyhow!("Failed to request a project.")),
73
- }
65
+ request(builder).await?
66
}
67
68
pub async fn delete_project(env: Environment, id: String) -> Result<()> {
crates/smbcloud-networking/src/constants.rs
+1
@@ -8,6 +8,7 @@ pub const GH_OAUTH_REDIRECT_PORT: &str = "8808";
8
9
pub const SMB_CLIENT_ID: &str = "cli";
10
pub const SMB_CLIENT_SECRET: &str = "secretttttttt";
11
+pub const SMB_USER_AGENT: &str = "smbcloud-cli";
12
13
// Paths
14
pub const PATH_USERS_ME: &str = "v1/me";
crates/smbcloud-networking/src/lib.rs
+1
@@ -1,5 +1,6 @@
1
pub mod constants;
2
pub mod environment;
3
+pub mod network;
4
5
use anyhow::{anyhow, Result};
6
use constants::{SMB_CLIENT_ID, SMB_CLIENT_SECRET};
crates/smbcloud-networking/src/network.rs
new
+90
@@ -0,0 +1,90 @@
1
+
2
+use log::{debug, error};
3
+use reqwest::{RequestBuilder, Response};
4
+use serde::de::DeserializeOwned;
5
+use smbcloud_model::error_codes::{ErrorCode, ErrorResponse};
6
+#[cfg(debug_assertions)]
7
+const LOG_RESPONSE_BODY: bool = true; // You know what to do here.
8
+#[cfg(not(debug_assertions))]
9
+const LOG_RESPONSE_BODY: bool = false;
10
+
11
+pub async fn parse_error_response<T: DeserializeOwned>(
12
+ response: Response,
13
+) -> Result<T, ErrorResponse> {
14
+ let response_body = match response.text().await {
15
+ Ok(body) => body,
16
+ Err(e) => {
17
+ error!("Failed to get response body: {:?}", e);
18
+ return Err(ErrorResponse::Error {
19
+ error_code: ErrorCode::NetworkError,
20
+ message: ErrorCode::NetworkError.message(None).to_string(),
21
+ });
22
+ }
23
+ };
24
+
25
+ if LOG_RESPONSE_BODY {
26
+ println!("{:?}", serde_json::to_string_pretty(&response_body));
27
+ }
28
+
29
+ let e = match serde_json::from_str::<ErrorResponse>(&response_body) {
30
+ Ok(json) => json,
31
+ Err(e) => {
32
+ error!("Failed to parse error response: {:?}", e);
33
+ return Err(ErrorResponse::Error {
34
+ error_code: ErrorCode::ParseError,
35
+ message: ErrorCode::ParseError.message(None).to_string(),
36
+ });
37
+ }
38
+ };
39
+ error!("Error response: {:?}", e);
40
+ return Err(e);
41
+}
42
+
43
+pub async fn request<R: DeserializeOwned>(builder: RequestBuilder) -> Result<R, ErrorResponse> {
44
+ let response = builder.send().await;
45
+ let response = match response {
46
+ Ok(response) => response,
47
+ Err(e) => {
48
+ error!("Failed to get response: {:?}", e);
49
+ return Err(ErrorResponse::Error {
50
+ error_code: ErrorCode::NetworkError,
51
+ message: ErrorCode::NetworkError.message(None).to_string(),
52
+ });
53
+ }
54
+ };
55
+ let response = match response.status() {
56
+ reqwest::StatusCode::OK => response,
57
+ status => {
58
+ error!("Failed to get response: {:?}", status);
59
+ return parse_error_response(response).await;
60
+ }
61
+ };
62
+
63
+ let response_body = match response.text().await {
64
+ Ok(body) => body,
65
+ Err(e) => {
66
+ error!("Failed to get response body: {:?}", e);
67
+ return Err(ErrorResponse::Error {
68
+ error_code: ErrorCode::NetworkError,
69
+ message: ErrorCode::NetworkError.message(None).to_string(),
70
+ });
71
+ }
72
+ };
73
+
74
+ if LOG_RESPONSE_BODY {
75
+ debug!("{:?}", serde_json::to_string_pretty(&response_body));
76
+ }
77
+
78
+ let response = match serde_json::from_str::<R>(&response_body) {
79
+ Ok(response) => response,
80
+ Err(e) => {
81
+ error!("Failed to parse response: {:?}", e);
82
+ return Err(ErrorResponse::Error {
83
+ error_code: ErrorCode::ParseError,
84
+ message: e.to_string(),
85
+ });
86
+ }
87
+ };
88
+
89
+ Ok(response)
90
+}
\ No newline at end of file