4 files changed
+125
-1
Cargo.lock
+10
@@ -1060,6 +1060,7 @@ dependencies = [
1060
"reqwest",
1061
"serde",
1062
"serde_json",
1063
+ "smbpndk-networking",
1064
"spinners",
1065
"tokio",
1066
"tracing",
@@ -1068,6 +1069,15 @@ dependencies = [
1069
"url-builder",
1070
]
1071
1072
+[[package]]
1073
+name = "smbpndk-networking"
1074
+version = "0.0.1"
1075
+dependencies = [
1076
+ "reqwest",
1077
+ "serde",
1078
+ "serde_json",
1079
+]
1080
+
1081
[[package]]
1082
name = "socket2"
1083
version = "0.4.7"
Cargo.toml
+7
-1
@@ -1,3 +1,8 @@
1
+[workspace]
2
+members = [
3
+ "smbpndk-networking"
4
+]
5
+
6
[package]
7
name = "smbpndk-cli"
8
version = "0.2.1"
@@ -16,17 +21,18 @@ path = "src/main.rs"
21
[dependencies]
22
anyhow = "1.0.58"
23
clap = { version = "4.1.6", features = ["derive"] }
24
+clap_mangen = "0.2.10"
25
console = "0.15.0"
26
dialoguer = "0.10.4"
27
dirs = "4.0.0"
28
dotenv = "0.15.0"
23
-clap_mangen = "0.2.10"
29
home = "0.5.4"
30
open = "4.0.1"
31
regex = "1.3.1"
32
reqwest = { version = "0.11", features = ["json"] }
33
serde = { version = "1", features = ["derive"] }
34
serde_json = "1.0.82"
35
+smbpndk-networking = { path = "smbpndk-networking" }
36
spinners = "4.1.0"
37
tokio = { version = "1.14.1", features = ["full"] }
38
tracing = { version = "0.1.35", features = ["log"] }
smbpndk-networking/Cargo.toml
new
+9
@@ -0,0 +1,9 @@
1
+[package]
2
+name = "smbpndk-networking"
3
+version = "0.0.1"
4
+edition = "2021"
5
+
6
+[dependencies]
7
+reqwest = { version = "0.11", features = ["json"] }
8
+serde = { version = "1", features = ["derive"] }
9
+serde_json = "1.0.82"
\ No newline at end of file
smbpndk-networking/src/lib.rs
new
+99
@@ -0,0 +1,99 @@
1
+use serde_json::Value;
2
+use std::collections::HashMap;
3
+use std::time::Duration;
4
+use reqwest::Method;
5
+use reqwest::RequestBuilder;
6
+
7
+pub type Headers = HashMap<String, String>;
8
+pub type Query<'a> = HashMap<&'a str, &'a str>;
9
+
10
+pub trait HttpClient: Send + Default {
11
+ type Error;
12
+
13
+ fn get(
14
+ &self,
15
+ url: &str,
16
+ headers: Option<&Headers>,
17
+ payload: &Query,
18
+ ) -> Result<String, Self::Error>;
19
+
20
+ fn post(
21
+ &self,
22
+ url: &str,
23
+ headers: Option<&Headers>,
24
+ payload: &Value,
25
+ ) -> Result<String, Self::Error>;
26
+}
27
+
28
+#[derive(thiserror::Error, Debug)]
29
+pub enum ReqwestError {
30
+ /// The request couldn't be completed because there was an error when trying
31
+ /// to do so
32
+ #[error("request: {0}")]
33
+ Client(#[from] reqwest::Error),
34
+
35
+ /// The request was made, but the server returned an unsuccessful status
36
+ /// code, such as 404 or 503. In some cases, the response may contain a
37
+ /// custom message from Spotify with more information, which can be
38
+ /// serialized into `rspotify_model::ApiError`.
39
+ #[error("status code {}", reqwest::Response::status(.0))]
40
+ StatusCode(reqwest::Response),
41
+}
42
+
43
+pub struct ReqwestClient {
44
+ // An instance of reqwest::Client.
45
+ client: reqwest::Client,
46
+}
47
+
48
+impl Default for ReqwestClient {
49
+ fn default() -> Self {
50
+ let client = reqwest::ClientBuilder::new()
51
+ .user_agent("smbpndk-cli")
52
+ .timeout(Duration::from_secs(10))
53
+ .build()
54
+ .unwrap();
55
+ Self { client }
56
+ }
57
+}
58
+
59
+impl ReqwestClient {
60
+ async fn request<D>(
61
+ &self,
62
+ method: Method,
63
+ url: &str,
64
+ headers: Option<&Headers>,
65
+ add_data: D,
66
+ ) -> Result<String, ReqwestError>
67
+ where
68
+ D: Fn(RequestBuilder) -> RequestBuilder,
69
+ {
70
+ let mut request = self.client.request(method.clone(), url);
71
+
72
+ // Setting the headers, if any
73
+ if let Some(headers) = headers {
74
+ // The headers need to be converted into a `reqwest::HeaderMap`,
75
+ // which won't fail as long as its contents are ASCII. This is an
76
+ // internal function, so the condition cannot be broken by the user
77
+ // and will always be true.
78
+ //
79
+ // The content-type header will be set automatically.
80
+ let headers = headers.try_into().unwrap();
81
+
82
+ request = request.headers(headers);
83
+ }
84
+
85
+ // Configuring the request for the specific type (get/post/put/delete)
86
+ request = add_data(request);
87
+
88
+ // Finally performing the request and handling the response
89
+ log::info!("Making request {:?}", request);
90
+ let response = request.send().await?;
91
+
92
+ // Making sure that the status code is OK
93
+ if response.status().is_success() {
94
+ response.text().await.map_err(Into::into)
95
+ } else {
96
+ Err(ReqwestError::StatusCode(response))
97
+ }
98
+ }
99
+}
\ No newline at end of file