Skip to content

Commit 819d5ae

Browse files
authored
Merge pull request #33 from Kroisse/fix/error
fix: migrate to error type
2 parents c564909 + 162e127 commit 819d5ae

File tree

1 file changed

+58
-26
lines changed

1 file changed

+58
-26
lines changed

rust/src/lib.rs

Lines changed: 58 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
pub mod http {
22
use std::collections::HashMap;
33
use std::env;
4+
use std::fmt;
45
use std::io::{self, BufRead, BufReader, Read, Write};
56
use std::net::TcpStream;
67
use std::sync::Arc;
@@ -10,12 +11,6 @@ pub mod http {
1011
use rustls::{ClientConfig, ClientSession, StreamOwned};
1112
use webpki::DNSNameRef;
1213

13-
const UNREACHABLE: &str = "Unreachable";
14-
const MALFORMED_URL: &str = "Malformed URL";
15-
const CONNECTION_ERROR: &str = "Connection error";
16-
const MALFORMED_RESPONSE: &str = "Malformed response";
17-
const UNSUPPORTED_ENCODING: &str = "Unsupported encoding";
18-
1914
enum Stream {
2015
Tcp(TcpStream),
2116
Tls(StreamOwned<ClientSession, TcpStream>),
@@ -105,46 +100,77 @@ pub mod http {
105100
}
106101
}
107102

108-
pub fn request(url: &str) -> Result<(HashMap<String, String>, Vec<u8>), String> {
103+
#[derive(Debug)]
104+
pub enum RequestError {
105+
Unreachable,
106+
MalformedUrl,
107+
UnknownScheme(String),
108+
ConnectionError,
109+
StatusError(String, String),
110+
MalformedResponse,
111+
UnsupportedEncoding,
112+
}
113+
114+
impl fmt::Display for RequestError {
115+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
116+
match self {
117+
RequestError::Unreachable => f.write_str("Unreachable"),
118+
RequestError::MalformedUrl => f.write_str("Malformed URL"),
119+
RequestError::UnknownScheme(scheme) => {
120+
write!(f, "Unknown scheme: {}", scheme)
121+
}
122+
RequestError::ConnectionError => f.write_str("Connection error"),
123+
RequestError::StatusError(status, reason) => {
124+
write!(f, "Status error: {} {}", status, reason)
125+
}
126+
RequestError::MalformedResponse => f.write_str("Malformed response"),
127+
RequestError::UnsupportedEncoding => f.write_str("Unsupported encoding"),
128+
}
129+
}
130+
}
131+
132+
impl std::error::Error for RequestError {}
133+
134+
pub fn request(url: &str) -> Result<(HashMap<String, String>, Vec<u8>), RequestError> {
109135
// 1. Parse scheme
110136
let (scheme, url) = split2(url, ":").unwrap_or(("https", url));
111137
let default_port = match scheme {
112138
"http" => 80,
113139
"https" => 443,
114140
"data" => {
115141
// Exercise data scheme
116-
let (content_type, body) = split2(url, ",").ok_or(MALFORMED_URL)?;
142+
let (content_type, body) = split2(url, ",").ok_or(RequestError::MalformedUrl)?;
117143
let mut headers = HashMap::new();
118144
headers.insert("content-type".to_owned(), content_type.to_owned());
119145
return Ok((headers, body.as_bytes().to_vec()));
120146
}
121-
_ => panic!("Unknown scheme {}", scheme),
147+
_ => return Err(RequestError::UnknownScheme(scheme.to_string())),
122148
};
123149
let url = url.strip_prefix("//").unwrap_or(url);
124150

125151
// 2. Parse host
126-
let (host, path) = split2(url, "/").ok_or(MALFORMED_URL)?;
152+
let (host, path) = split2(url, "/").ok_or(RequestError::MalformedUrl)?;
127153
let path = format!("/{}", path);
128154

129155
// 3. Parse port
130156
let (host, port) = if host.contains(':') {
131-
let (host, port) = split2(host, ":").ok_or(UNREACHABLE)?;
132-
let port = port.parse().map_err(|_| MALFORMED_URL)?;
157+
let (host, port) = split2(host, ":").ok_or(RequestError::Unreachable)?;
158+
let port = port.parse().or(Err(RequestError::MalformedUrl))?;
133159
(host, port)
134160
} else {
135161
(host, default_port)
136162
};
137163

138164
// 4. Connect
139-
let stream = TcpStream::connect((host, port)).map_err(|_| CONNECTION_ERROR)?;
165+
let stream = TcpStream::connect((host, port)).or(Err(RequestError::ConnectionError))?;
140166
let mut stream = if scheme != "https" {
141167
Stream::Tcp(stream)
142168
} else {
143169
let mut config = ClientConfig::new();
144170
config
145171
.root_store
146172
.add_server_trust_anchors(&webpki_roots::TLS_SERVER_ROOTS);
147-
let host = DNSNameRef::try_from_ascii_str(host).map_err(|_| MALFORMED_URL)?;
173+
let host = DNSNameRef::try_from_ascii_str(host).or(Err(RequestError::MalformedUrl))?;
148174
let client = ClientSession::new(&Arc::new(config), host);
149175
let stream = StreamOwned::new(client, stream);
150176
Stream::Tls(stream)
@@ -163,7 +189,7 @@ pub mod http {
163189
host,
164190
env::consts::OS
165191
)
166-
.map_err(|_| CONNECTION_ERROR)?;
192+
.or(Err(RequestError::ConnectionError))?;
167193

168194
// 6. Receive response
169195
let mut reader = BufReader::new(stream);
@@ -172,16 +198,21 @@ pub mod http {
172198
let mut line = String::new();
173199
reader
174200
.read_line(&mut line)
175-
.map_err(|_| MALFORMED_RESPONSE)?;
201+
.or(Err(RequestError::MalformedResponse))?;
176202

177203
// 8. Parse status line
178-
let (_version, status) = split2(&line, " ").ok_or(MALFORMED_RESPONSE)?;
179-
let (status, explanation) = split2(status, " ").ok_or(MALFORMED_RESPONSE)?;
204+
let (_version, status) = split2(&line, " ").ok_or(RequestError::MalformedResponse)?;
205+
let (status, explanation) = split2(status, " ").ok_or(RequestError::MalformedResponse)?;
180206

181207
// 9. Check status
182208
match status {
183209
"200" | "301" | "302" => (),
184-
_ => panic!("{}: {}", status, explanation),
210+
_ => {
211+
return Err(RequestError::StatusError(
212+
status.to_string(),
213+
explanation.to_string(),
214+
))
215+
}
185216
};
186217

187218
// 10. Parse headers
@@ -190,11 +221,11 @@ pub mod http {
190221
line.clear();
191222
reader
192223
.read_line(&mut line)
193-
.map_err(|_| MALFORMED_RESPONSE)?;
224+
.or(Err(RequestError::MalformedResponse))?;
194225
if line == "\r\n" {
195226
break;
196227
}
197-
let (header, value) = split2(&line, ":").ok_or(MALFORMED_RESPONSE)?;
228+
let (header, value) = split2(&line, ":").ok_or(RequestError::MalformedResponse)?;
198229
let header = header.to_ascii_lowercase();
199230
let value = value.trim();
200231
headers.insert(header, value.to_string());
@@ -205,7 +236,9 @@ pub mod http {
205236
}
206237

207238
let content_encoding: ContentEncoding = match headers.get("content-encoding") {
208-
Some(encoding) => encoding.parse().map_err(|_| UNSUPPORTED_ENCODING)?,
239+
Some(encoding) => encoding
240+
.parse()
241+
.or(Err(RequestError::UnsupportedEncoding))?,
209242
None => ContentEncoding::Identity,
210243
};
211244

@@ -220,15 +253,15 @@ pub mod http {
220253
let mut line = String::new();
221254
reader
222255
.read_line(&mut line)
223-
.map_err(|_| MALFORMED_RESPONSE)?;
256+
.or(Err(RequestError::MalformedResponse))?;
224257
let n_bytes = u64::from_str_radix(line.trim_end(), 16).unwrap_or(0);
225258
if n_bytes == 0 {
226259
break;
227260
}
228261
let mut chunk = vec![0u8; n_bytes as usize];
229262
reader
230263
.read_exact(&mut chunk)
231-
.map_err(|_| MALFORMED_RESPONSE)?;
264+
.or(Err(RequestError::MalformedResponse))?;
232265
reader.read_exact(&mut vec![0u8; 2]).unwrap();
233266
unchunked.write_all(&chunk).unwrap();
234267
}
@@ -243,8 +276,7 @@ pub mod http {
243276
let mut body = Vec::new();
244277
reader
245278
.read_to_end(&mut body)
246-
.map_err(|_| MALFORMED_RESPONSE)
247-
.unwrap();
279+
.or(Err(RequestError::MalformedResponse))?;
248280
body
249281
};
250282

0 commit comments

Comments
 (0)