Skip to content

Commit e0a2a12

Browse files
authored
[refact] Introduce NetworkResult and NetworkError types (#64)
Extracted from #60 Working on #63 In this PR we are just introducing new types `NetworkResult` and `NetworkError` that will be used inside the `network` module. Our purpose is to isolate inside this `network` module everything related to outside-world communications and provide a clear (and small) interface that can be reused from the other modules in the crate. Signed-off-by: Javier G. Sogo <[email protected]>
1 parent 6a653cf commit e0a2a12

File tree

9 files changed

+81
-35
lines changed

9 files changed

+81
-35
lines changed

src/errors.rs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ use std::sync::PoisonError;
22

33
use thiserror::Error;
44

5+
use crate::network::errors::NetworkError;
56
use crate::segment_evaluation::errors::SegmentEvaluationError;
67

78
pub type Result<T> = std::result::Result<T, Error>;
@@ -28,9 +29,6 @@ pub enum Error {
2829
#[error("Inner type cannot be converted to requested type")]
2930
MismatchType,
3031

31-
#[error(transparent)]
32-
ReqwestError(#[from] reqwest::Error),
33-
3432
#[error(transparent)]
3533
TungsteniteError(#[from] tungstenite::Error),
3634

@@ -49,6 +47,9 @@ pub enum Error {
4947
#[error("Failed to evaluate entity: {0}")]
5048
EntityEvaluationError(EntityEvaluationError),
5149

50+
#[error(transparent)]
51+
NetworkError(#[from] NetworkError),
52+
5253
#[error("{0}")]
5354
Other(String),
5455
}

src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,6 @@ pub use value::Value;
105105
#[cfg(feature = "http_client")]
106106
pub use client::AppConfigurationClientHttp;
107107
#[cfg(feature = "http_client")]
108-
pub use network::{ServiceAddress, TokenProvider};
108+
pub use network::{NetworkError, NetworkResult, ServiceAddress, TokenProvider};
109109
#[cfg(test)]
110110
mod tests;

src/network/errors.rs

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
// (C) Copyright IBM Corp. 2024.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
use thiserror::Error;
16+
17+
#[derive(Debug, Error)]
18+
pub enum NetworkError {
19+
#[error(transparent)]
20+
ReqwestError(#[from] reqwest::Error),
21+
22+
#[error(transparent)]
23+
TungsteniteError(#[from] tungstenite::Error),
24+
25+
#[error("Protocol error. Unexpected data received from server")]
26+
ProtocolError,
27+
28+
#[error("Cannot parse '{0}' as URL")]
29+
UrlParseError(String),
30+
31+
#[error("Invalid header value for '{0}'")]
32+
InvalidHeaderValue(String),
33+
}

src/network/http_client.rs

Lines changed: 17 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,9 @@
1212
// See the License for the specific language governing permissions and
1313
// limitations under the License.
1414

15-
use super::TokenProvider;
15+
use super::{NetworkError, NetworkResult, TokenProvider};
1616
use crate::models::ConfigurationJson;
17-
use crate::{ConfigurationId, Error, Result};
17+
use crate::ConfigurationId;
1818
use reqwest::blocking::Client;
1919
use std::cell::RefCell;
2020
use tungstenite::stream::MaybeTlsStream;
@@ -101,7 +101,7 @@ impl ServerClientImpl {
101101
pub fn new(
102102
service_address: ServiceAddress,
103103
token_provider: Box<dyn TokenProvider>,
104-
) -> Result<Self> {
104+
) -> NetworkResult<Self> {
105105
let access_token = RefCell::new(token_provider.get_access_token()?);
106106
Ok(Self {
107107
service_address,
@@ -110,29 +110,31 @@ impl ServerClientImpl {
110110
})
111111
}
112112

113-
pub fn get_configuration(&self, collection: &ConfigurationId) -> Result<ConfigurationJson> {
113+
pub fn get_configuration(
114+
&self,
115+
configuration_id: &ConfigurationId,
116+
) -> NetworkResult<ConfigurationJson> {
114117
let url = format!(
115118
"{}/feature/v1/instances/{}/config",
116119
self.service_address.base_url(ServiceAddressProtocol::Http),
117-
collection.guid
120+
configuration_id.guid
118121
);
122+
let url = Url::parse(&url).map_err(|_| NetworkError::UrlParseError(url))?;
119123
let client = Client::new();
120124
let r = client
121125
.get(url)
122126
.query(&[
123127
("action", "sdkConfig"),
124-
("environment_id", &collection.environment_id),
125-
("collection_id", &collection.collection_id),
128+
("environment_id", &configuration_id.environment_id),
129+
("collection_id", &configuration_id.collection_id),
126130
])
127131
.header("Accept", "application/json")
128132
.header("User-Agent", "appconfiguration-rust-sdk/0.0.1")
129133
.bearer_auth(self.access_token.borrow())
130134
.send();
131135

132136
match r {
133-
Ok(response) => response.json().map_err(|_| {
134-
Error::ProtocolError("Failed to deserialize JSON from server response".to_string())
135-
}),
137+
Ok(response) => response.json().map_err(|_| NetworkError::ProtocolError),
136138
Err(e) => {
137139
// TODO: Identify if token expired, get new one and retry
138140
if false {
@@ -147,13 +149,12 @@ impl ServerClientImpl {
147149
pub fn get_configuration_monitoring_websocket(
148150
&self,
149151
collection: &ConfigurationId,
150-
) -> Result<(WebSocket<MaybeTlsStream<TcpStream>>, Response)> {
152+
) -> NetworkResult<(WebSocket<MaybeTlsStream<TcpStream>>, Response)> {
151153
let ws_url = format!(
152154
"{}/wsfeature",
153155
self.service_address.base_url(ServiceAddressProtocol::Ws)
154156
);
155-
let mut ws_url = Url::parse(&ws_url)
156-
.map_err(|e| Error::Other(format!("Cannot parse '{}' as URL: {}", ws_url, e)))?;
157+
let mut ws_url = Url::parse(&ws_url).map_err(|_| NetworkError::UrlParseError(ws_url))?;
157158

158159
ws_url
159160
.query_pairs_mut()
@@ -164,21 +165,19 @@ impl ServerClientImpl {
164165
let mut request = ws_url
165166
.as_str()
166167
.into_client_request()
167-
.map_err(Error::TungsteniteError)?;
168+
.map_err(NetworkError::TungsteniteError)?;
168169
let headers = request.headers_mut();
169170
headers.insert(
170171
"User-Agent",
171172
"appconfiguration-rust-sdk/0.0.1"
172173
.parse()
173-
.map_err(|_| Error::Other("Invalid header value for 'User-Agent'".to_string()))?,
174+
.map_err(|_| NetworkError::InvalidHeaderValue("User-Agent".to_string()))?,
174175
);
175176
headers.insert(
176177
"Authorization",
177178
format!("Bearer {}", self.access_token.borrow())
178179
.parse()
179-
.map_err(|_| {
180-
Error::Other("Invalid header value for 'Authorization'".to_string())
181-
})?,
180+
.map_err(|_| NetworkError::InvalidHeaderValue("Authorization".to_string()))?,
182181
);
183182

184183
Ok(connect(request)?)

src/network/mod.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,14 @@
1212
// See the License for the specific language governing permissions and
1313
// limitations under the License.
1414

15+
pub mod errors;
1516
pub(crate) mod http_client;
1617
mod token_provider;
1718

1819
pub(crate) use http_client::ServerClientImpl;
1920
pub use http_client::ServiceAddress;
2021
pub(crate) use token_provider::IBMCloudTokenProvider;
2122
pub use token_provider::TokenProvider;
23+
24+
pub use errors::NetworkError;
25+
pub type NetworkResult<T> = std::result::Result<T, NetworkError>;

src/network/token_provider.rs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,12 @@
1414

1515
use std::collections::HashMap;
1616

17-
use crate::{Error, Result};
17+
use super::{NetworkError, NetworkResult};
1818
use reqwest::blocking::Client;
1919
use serde::Deserialize;
2020

2121
pub trait TokenProvider: std::fmt::Debug + Send + Sync {
22-
fn get_access_token(&self) -> Result<String>;
22+
fn get_access_token(&self) -> NetworkResult<String>;
2323
}
2424

2525
#[derive(Debug)]
@@ -41,7 +41,7 @@ struct AccessTokenResponse {
4141
}
4242

4343
impl TokenProvider for IBMCloudTokenProvider {
44-
fn get_access_token(&self) -> Result<String> {
44+
fn get_access_token(&self) -> NetworkResult<String> {
4545
let mut form_data = HashMap::new();
4646
form_data.insert("reponse_type".to_string(), "cloud_iam".to_string());
4747
form_data.insert(
@@ -56,9 +56,9 @@ impl TokenProvider for IBMCloudTokenProvider {
5656
.header("Accept", "application/json")
5757
.form(&form_data)
5858
.send()
59-
.map_err(Error::ReqwestError)?
59+
.map_err(NetworkError::ReqwestError)?
6060
.json::<AccessTokenResponse>()
61-
.map_err(Error::ReqwestError)? // FIXME: This is a deserialization error (extract it from Reqwest)
61+
.map_err(NetworkError::ReqwestError)? // FIXME: This is a deserialization error (extract it from Reqwest)
6262
.access_token)
6363
}
6464
}

tests/test_initial_configuration_fails.rs

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ use std::sync::mpsc::channel;
44
use std::thread::spawn;
55

66
use appconfiguration::{
7-
AppConfigurationClientHttp, ConfigurationId, Error, ServiceAddress, TokenProvider,
7+
AppConfigurationClientHttp, ConfigurationId, NetworkError, ServiceAddress, TokenProvider,
88
};
99

1010
fn handle_config_request_error(server: &TcpListener) {
@@ -83,7 +83,7 @@ fn server_thread() -> ServerHandle {
8383
struct MockTokenProvider {}
8484

8585
impl TokenProvider for MockTokenProvider {
86-
fn get_access_token(&self) -> appconfiguration::Result<String> {
86+
fn get_access_token(&self) -> appconfiguration::NetworkResult<String> {
8787
Ok("mock_token".into())
8888
}
8989
}
@@ -111,12 +111,18 @@ fn main() {
111111
);
112112

113113
assert!(client.is_err());
114-
assert!(matches!(client.unwrap_err(), Error::ReqwestError(_)));
114+
assert!(matches!(
115+
client.unwrap_err(),
116+
appconfiguration::Error::NetworkError(NetworkError::ReqwestError(_))
117+
));
115118

116119
// Test response is successful (200) but configuration JSON is invalid
117120
let client =
118121
AppConfigurationClientHttp::new(address, Box::new(MockTokenProvider {}), config_id);
119122

120123
assert!(client.is_err());
121-
assert!(matches!(client.unwrap_err(), Error::ProtocolError(_)));
124+
assert!(matches!(
125+
client.unwrap_err(),
126+
appconfiguration::Error::NetworkError(NetworkError::ProtocolError)
127+
));
122128
}

tests/test_initial_configuration_from_server.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,7 @@ fn server_thread() -> ServerHandle {
101101
struct MockTokenProvider {}
102102

103103
impl TokenProvider for MockTokenProvider {
104-
fn get_access_token(&self) -> appconfiguration::Result<String> {
104+
fn get_access_token(&self) -> appconfiguration::NetworkResult<String> {
105105
Ok("mock_token".into())
106106
}
107107
}

tests/test_ws_connection_fails.rs

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
use appconfiguration::{
2-
AppConfigurationClientHttp, ConfigurationId, Error, ServiceAddress, TokenProvider,
2+
AppConfigurationClientHttp, ConfigurationId, NetworkError, ServiceAddress, TokenProvider,
33
};
44

55
use std::io::{BufRead, BufReader, Write};
@@ -67,7 +67,7 @@ fn server_thread() -> ServerHandle {
6767
struct MockTokenProvider {}
6868

6969
impl TokenProvider for MockTokenProvider {
70-
fn get_access_token(&self) -> appconfiguration::Result<String> {
70+
fn get_access_token(&self) -> appconfiguration::NetworkResult<String> {
7171
Ok("mock_token".into())
7272
}
7373
}
@@ -90,5 +90,8 @@ fn main() {
9090
AppConfigurationClientHttp::new(address, Box::new(MockTokenProvider {}), config_id);
9191

9292
assert!(client.is_err());
93-
assert!(matches!(client.unwrap_err(), Error::TungsteniteError(_)));
93+
assert!(matches!(
94+
client.unwrap_err(),
95+
appconfiguration::Error::NetworkError(NetworkError::TungsteniteError(_))
96+
));
9497
}

0 commit comments

Comments
 (0)