Skip to content

Commit 5a08887

Browse files
committed
Auto merge of #2072 - jtgeibel:fix/remove-some-unwraps, r=carols10cents
Remove some usage of `unwrap()` This commit reduces the usage of unwrap within the request/response lifecycle, and documents a few remaining cases. Usage of unwrap in tests, binaries, or server boot code is generally okay and was not reviewed. Remaining work: * Some usage of unwrap in `git.rs` and `src/tasks/` background jobs. This code is not directly in the request/response lifecycle, but should be reviewed. * Current usage in middleware seems okay, but could be documented. In most cases, such as with the handler in `AroundMiddleware`, the conduit API ensures a handler is set.
2 parents a34f6c4 + a318cb3 commit 5a08887

File tree

12 files changed

+101
-38
lines changed

12 files changed

+101
-38
lines changed

src/controllers/krate/search.rs

+2-1
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ use crate::controllers::cargo_prelude::*;
77
use crate::controllers::helpers::Paginate;
88
use crate::models::{Crate, CrateBadge, CrateOwner, CrateVersions, OwnerKind, Version};
99
use crate::schema::*;
10+
use crate::util::errors::{bad_request, ChainError};
1011
use crate::views::EncodableCrate;
1112

1213
use crate::models::krate::{canon_crate_name, ALL_COLUMNS};
@@ -110,7 +111,7 @@ pub fn search(req: &mut dyn Request) -> AppResult<Response> {
110111
letter
111112
.chars()
112113
.next()
113-
.unwrap()
114+
.chain_error(|| bad_request("letter value must contain 1 character"))?
114115
.to_lowercase()
115116
.collect::<String>()
116117
);

src/controllers/user/me.rs

+8-8
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ use crate::controllers::frontend_prelude::*;
44

55
use crate::controllers::helpers::*;
66
use crate::email;
7-
use crate::util::errors::AppError;
7+
use crate::util::errors::{AppError, ChainError};
88

99
use crate::models::{
1010
CrateOwner, Email, Follow, NewEmail, OwnerKind, User, Version, VersionOwnerAction,
@@ -141,12 +141,10 @@ pub fn update_user(req: &mut dyn Request) -> AppResult<Response> {
141141
let user_update: UserUpdate =
142142
serde_json::from_str(&body).map_err(|_| bad_request("invalid json request"))?;
143143

144-
if user_update.user.email.is_none() {
145-
return Err(bad_request("empty email rejected"));
146-
}
147-
148-
let user_email = user_update.user.email.unwrap();
149-
let user_email = user_email.trim();
144+
let user_email = match &user_update.user.email {
145+
Some(email) => email.trim(),
146+
None => return Err(bad_request("empty email rejected")),
147+
};
150148

151149
if user_email == "" {
152150
return Err(bad_request("empty email rejected"));
@@ -199,7 +197,9 @@ pub fn regenerate_token_and_send(req: &mut dyn Request) -> AppResult<Response> {
199197
use diesel::update;
200198

201199
let user = req.user()?;
202-
let name = &req.params()["user_id"].parse::<i32>().ok().unwrap();
200+
let name = &req.params()["user_id"]
201+
.parse::<i32>()
202+
.chain_error(|| bad_request("invalid user_id"))?;
203203
let conn = req.db_conn()?;
204204

205205
// need to check if current user matches user to be updated

src/controllers/user/other.rs

+4-1
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ use crate::controllers::frontend_prelude::*;
22

33
use crate::models::{CrateOwner, OwnerKind, User};
44
use crate::schema::{crate_owners, crates, users};
5+
use crate::util::errors::ChainError;
56
use crate::views::EncodablePublicUser;
67

78
/// Handles the `GET /users/:user_id` route.
@@ -28,7 +29,9 @@ pub fn show(req: &mut dyn Request) -> AppResult<Response> {
2829
pub fn stats(req: &mut dyn Request) -> AppResult<Response> {
2930
use diesel::dsl::sum;
3031

31-
let user_id = &req.params()["user_id"].parse::<i32>().ok().unwrap();
32+
let user_id = &req.params()["user_id"]
33+
.parse::<i32>()
34+
.chain_error(|| bad_request("invalid user_id"))?;
3235
let conn = req.db_conn()?;
3336

3437
let data = CrateOwner::by_owner_kind(OwnerKind::User)

src/middleware/block_traffic.rs

+1
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ impl Handler for BlockTraffic {
5454
Please open an issue at https://github.com/rust-lang/crates.io \
5555
or email [email protected] \
5656
and provide the request id {}",
57+
// Heroku should always set this header
5758
req.headers().find("X-Request-Id").unwrap()[0]
5859
);
5960
let mut headers = HashMap::new();

src/models/badge.rs

+1
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,7 @@ impl Queryable<badges::SqlType, Pg> for Badge {
109109

110110
impl Badge {
111111
pub fn encodable(self) -> EncodableBadge {
112+
// The serde attributes on Badge ensure it can be deserialized to EncodableBadge
112113
serde_json::from_value(serde_json::to_value(self).unwrap()).unwrap()
113114
}
114115

src/models/keyword.rs

+1
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ impl Keyword {
5353
if name.is_empty() {
5454
return false;
5555
}
56+
// unwrap is okay because name is non-empty
5657
name.chars().next().unwrap().is_alphanumeric()
5758
&& name
5859
.chars()

src/models/team.rs

+6-1
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,10 @@ impl<'a> NewTeam<'a> {
6868

6969
impl Team {
7070
/// Tries to create the Team in the DB (assumes a `:` has already been found).
71+
///
72+
/// # Panics
73+
///
74+
/// This function will panic if login contains less than 2 `:` characters.
7175
pub fn create_or_update(
7276
app: &App,
7377
conn: &PgConnection,
@@ -76,10 +80,11 @@ impl Team {
7680
) -> AppResult<Self> {
7781
// must look like system:xxxxxxx
7882
let mut chunks = login.split(':');
83+
// unwrap is okay, split on an empty string still has 1 chunk
7984
match chunks.next().unwrap() {
8085
// github:rust-lang:owners
8186
"github" => {
82-
// Ok to unwrap since we know one ":" is contained
87+
// unwrap is documented above as part of the calling contract
8388
let org = chunks.next().unwrap();
8489
let team = chunks.next().ok_or_else(|| {
8590
cargo_err(

src/s3/Cargo.toml

+1
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ authors = ["Alex Crichton <[email protected]>"]
66
license = "MIT OR Apache-2.0"
77
repository = "https://github.com/rust-lang/crates.io"
88
description = "Interaction between crates.io and S3 for storing crate files"
9+
edition = "2018"
910

1011
[lib]
1112

src/s3/error.rs

+33
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
use std::fmt;
2+
3+
use openssl::error::ErrorStack;
4+
use reqwest::Error as ReqwestError;
5+
6+
#[derive(Debug)]
7+
pub enum Error {
8+
Openssl(ErrorStack),
9+
Reqwest(ReqwestError),
10+
}
11+
12+
impl From<ErrorStack> for Error {
13+
fn from(stack: ErrorStack) -> Self {
14+
Self::Openssl(stack)
15+
}
16+
}
17+
18+
impl From<ReqwestError> for Error {
19+
fn from(error: ReqwestError) -> Self {
20+
Self::Reqwest(error)
21+
}
22+
}
23+
24+
impl fmt::Display for Error {
25+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
26+
match self {
27+
Self::Openssl(stack) => stack.fmt(f),
28+
Self::Reqwest(error) => error.fmt(f),
29+
}
30+
}
31+
}
32+
33+
impl std::error::Error for Error {}

src/s3/lib.rs

+26-22
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,15 @@
11
#![warn(clippy::all, rust_2018_idioms)]
22

3-
extern crate base64;
4-
extern crate chrono;
5-
extern crate openssl;
6-
extern crate reqwest;
7-
83
use base64::encode;
94
use chrono::prelude::Utc;
5+
use openssl::error::ErrorStack;
106
use openssl::hash::MessageDigest;
117
use openssl::pkey::PKey;
128
use openssl::sign::Signer;
13-
use reqwest::header;
9+
use reqwest::{header, Body, Client, Response};
10+
11+
mod error;
12+
pub use error::Error;
1413

1514
#[derive(Clone, Debug)]
1615
pub struct Bucket {
@@ -40,20 +39,20 @@ impl Bucket {
4039

4140
pub fn put<R: std::io::Read + Send + 'static>(
4241
&self,
43-
client: &reqwest::Client,
42+
client: &Client,
4443
path: &str,
4544
content: R,
4645
content_length: u64,
4746
content_type: &str,
4847
extra_headers: header::HeaderMap,
49-
) -> reqwest::Result<reqwest::Response> {
48+
) -> Result<Response, Error> {
5049
let path = if path.starts_with('/') {
5150
&path[1..]
5251
} else {
5352
path
5453
};
5554
let date = Utc::now().to_rfc2822();
56-
let auth = self.auth("PUT", &date, path, "", content_type);
55+
let auth = self.auth("PUT", &date, path, "", content_type)?;
5756
let url = self.url(path);
5857

5958
client
@@ -62,23 +61,20 @@ impl Bucket {
6261
.header(header::CONTENT_TYPE, content_type)
6362
.header(header::DATE, date)
6463
.headers(extra_headers)
65-
.body(reqwest::Body::sized(content, content_length))
64+
.body(Body::sized(content, content_length))
6665
.send()?
6766
.error_for_status()
67+
.map_err(Into::into)
6868
}
6969

70-
pub fn delete(
71-
&self,
72-
client: &reqwest::Client,
73-
path: &str,
74-
) -> reqwest::Result<reqwest::Response> {
70+
pub fn delete(&self, client: &Client, path: &str) -> Result<Response, Error> {
7571
let path = if path.starts_with('/') {
7672
&path[1..]
7773
} else {
7874
path
7975
};
8076
let date = Utc::now().to_rfc2822();
81-
let auth = self.auth("DELETE", &date, path, "", "");
77+
let auth = self.auth("DELETE", &date, path, "", "")?;
8278
let url = self.url(path);
8379

8480
client
@@ -87,6 +83,7 @@ impl Bucket {
8783
.header(header::AUTHORIZATION, auth)
8884
.send()?
8985
.error_for_status()
86+
.map_err(Into::into)
9087
}
9188

9289
pub fn host(&self) -> String {
@@ -101,7 +98,14 @@ impl Bucket {
10198
)
10299
}
103100

104-
fn auth(&self, verb: &str, date: &str, path: &str, md5: &str, content_type: &str) -> String {
101+
fn auth(
102+
&self,
103+
verb: &str,
104+
date: &str,
105+
path: &str,
106+
md5: &str,
107+
content_type: &str,
108+
) -> Result<String, ErrorStack> {
105109
let string = format!(
106110
"{verb}\n{md5}\n{ty}\n{date}\n{headers}{resource}",
107111
verb = verb,
@@ -112,12 +116,12 @@ impl Bucket {
112116
resource = format!("/{}/{}", self.name, path)
113117
);
114118
let signature = {
115-
let key = PKey::hmac(self.secret_key.as_bytes()).unwrap();
116-
let mut signer = Signer::new(MessageDigest::sha1(), &key).unwrap();
117-
signer.update(string.as_bytes()).unwrap();
118-
encode(&signer.sign_to_vec().unwrap()[..])
119+
let key = PKey::hmac(self.secret_key.as_bytes())?;
120+
let mut signer = Signer::new(MessageDigest::sha1(), &key)?;
121+
signer.update(string.as_bytes())?;
122+
encode(&signer.sign_to_vec()?[..])
119123
};
120-
format!("AWS {}:{}", self.access_key, signature)
124+
Ok(format!("AWS {}:{}", self.access_key, signature))
121125
}
122126

123127
fn url(&self, path: &str) -> String {

src/uploaders.rs

+11-5
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
use conduit::Request;
22
use flate2::read::GzDecoder;
3+
use openssl::error::ErrorStack;
34
use openssl::hash::{Hasher, MessageDigest};
45
use reqwest::header;
56

@@ -88,6 +89,11 @@ impl Uploader {
8889
/// Uploads a file using the configured uploader (either `S3`, `Local`).
8990
///
9091
/// It returns the path of the uploaded file.
92+
///
93+
/// # Panics
94+
///
95+
/// This function can panic on an `Self::Local` during development.
96+
/// Production and tests use `Self::S3` which should not panic.
9197
pub fn upload<R: std::io::Read + Send + 'static>(
9298
&self,
9399
client: &reqwest::Client,
@@ -135,7 +141,7 @@ impl Uploader {
135141
let mut body = Vec::new();
136142
LimitErrorReader::new(req.body(), maximums.max_upload_size).read_to_end(&mut body)?;
137143
verify_tarball(krate, vers, &body, maximums.max_unpack_size)?;
138-
let checksum = hash(&body);
144+
let checksum = hash(&body)?;
139145
let content_length = body.len() as u64;
140146
let content = Cursor::new(body);
141147
let mut extra_headers = header::HeaderMap::new();
@@ -221,8 +227,8 @@ fn verify_tarball(
221227
Ok(())
222228
}
223229

224-
fn hash(data: &[u8]) -> Vec<u8> {
225-
let mut hasher = Hasher::new(MessageDigest::sha256()).unwrap();
226-
hasher.update(data).unwrap();
227-
hasher.finish().unwrap().to_vec()
230+
fn hash(data: &[u8]) -> Result<Vec<u8>, ErrorStack> {
231+
let mut hasher = Hasher::new(MessageDigest::sha256())?;
232+
hasher.update(data)?;
233+
Ok(hasher.finish()?.to_vec())
228234
}

src/util.rs

+7
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,13 @@ mod request_helpers;
1717
mod request_proxy;
1818
pub mod rfc3339;
1919

20+
/// Serialize a value to JSON and build a status 200 Response
21+
///
22+
/// This helper sets appropriate values for `Content-Type` and `Content-Length`.
23+
///
24+
/// # Panics
25+
///
26+
/// This function will panic if serialization fails.
2027
pub fn json_response<T: Serialize>(t: &T) -> Response {
2128
let json = serde_json::to_string(t).unwrap();
2229
let mut headers = HashMap::new();

0 commit comments

Comments
 (0)