Skip to content

Commit c7a37b3

Browse files
committed
Auto merge of #2065 - jtgeibel:prod/make-2-errors-user-facing, r=smarnach
Convert pagination ISE into a Bad Request response Errors in parsing the pagination query string parameters can be safely returned to the client and do not need to propagate up to the logging middleware. The following have been observed in production logs: * error="cannot parse integer from empty string" for `/api/v1/crates?page=&per_page=50&sort=downloads` (empty `page=`) * error="invalid digit found in string" for `/api/v1/crates?page=1&per_page=100%22%EF%BC%8Cexception"` (very invalid `per_page`) Additionally, 2 existing pagination errors are converted to use `bad_request()` in place of `cargo_err()`. It is not necessary to send a status=200 response to support old versions of cargo for pagination errors. Fixes: #1928 r? @smarnach
2 parents 9deef3e + 741093f commit c7a37b3

File tree

3 files changed

+61
-6
lines changed

3 files changed

+61
-6
lines changed

src/controllers/helpers/pagination.rs

Lines changed: 32 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
use crate::models::helpers::with_count::*;
2-
use crate::util::errors::{cargo_err, AppResult};
2+
use crate::util::errors::{bad_request, AppResult};
33
use diesel::pg::Pg;
44
use diesel::prelude::*;
55
use diesel::query_builder::*;
@@ -16,9 +16,9 @@ pub(crate) enum Page {
1616
impl Page {
1717
fn new(params: &IndexMap<String, String>) -> AppResult<Self> {
1818
if let Some(s) = params.get("page") {
19-
let numeric_page = s.parse()?;
19+
let numeric_page = s.parse().map_err(|e| bad_request(&e))?;
2020
if numeric_page < 1 {
21-
return Err(cargo_err(&format_args!(
21+
return Err(bad_request(&format_args!(
2222
"page indexing starts from 1, page {} is invalid",
2323
numeric_page,
2424
)));
@@ -44,11 +44,11 @@ impl PaginationOptions {
4444

4545
let per_page = params
4646
.get("per_page")
47-
.map(|s| s.parse())
47+
.map(|s| s.parse().map_err(|e| bad_request(&e)))
4848
.unwrap_or(Ok(DEFAULT_PER_PAGE))?;
4949

5050
if per_page > MAX_PER_PAGE {
51-
return Err(cargo_err(&format_args!(
51+
return Err(bad_request(&format_args!(
5252
"cannot request more than {} items",
5353
MAX_PER_PAGE,
5454
)));
@@ -182,3 +182,30 @@ where
182182
Ok(())
183183
}
184184
}
185+
186+
#[cfg(test)]
187+
mod tests {
188+
use super::{Page, PaginationOptions};
189+
use indexmap::IndexMap;
190+
191+
#[test]
192+
fn page_must_be_a_number() {
193+
let mut params = IndexMap::new();
194+
params.insert(String::from("page"), String::from("not a number"));
195+
let page_error = Page::new(&params).unwrap_err().response().unwrap();
196+
197+
assert_eq!(page_error.status, (400, "Bad Request"));
198+
}
199+
200+
#[test]
201+
fn per_page_must_be_a_number() {
202+
let mut params = IndexMap::new();
203+
params.insert(String::from("per_page"), String::from("not a number"));
204+
let per_page_error = PaginationOptions::new(&params)
205+
.unwrap_err()
206+
.response()
207+
.unwrap();
208+
209+
assert_eq!(per_page_error.status, (400, "Bad Request"));
210+
}
211+
}

src/tests/krate.rs

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2364,3 +2364,31 @@ fn pagination_links_included_if_applicable() {
23642364
assert_eq!(None, page4.meta.next_page);
23652365
assert_eq!(Some("?page=2&per_page=1".to_string()), page3.meta.prev_page);
23662366
}
2367+
2368+
#[test]
2369+
fn pagination_parameters_only_accept_integers() {
2370+
let (app, anon, user) = TestApp::init().with_user();
2371+
let user = user.as_model();
2372+
2373+
app.db(|conn| {
2374+
CrateBuilder::new("pagination_links_1", user.id).expect_build(conn);
2375+
CrateBuilder::new("pagination_links_2", user.id).expect_build(conn);
2376+
CrateBuilder::new("pagination_links_3", user.id).expect_build(conn);
2377+
});
2378+
2379+
let invalid_per_page_json = anon
2380+
.get_with_query::<()>("/api/v1/crates", "page=1&per_page=100%22%EF%BC%8Cexception")
2381+
.bad_with_status(400);
2382+
assert_eq!(
2383+
invalid_per_page_json.errors[0].detail,
2384+
"invalid digit found in string"
2385+
);
2386+
2387+
let invalid_page_json = anon
2388+
.get_with_query::<()>("/api/v1/crates", "page=100%22%EF%BC%8Cexception&per_page=1")
2389+
.bad_with_status(400);
2390+
assert_eq!(
2391+
invalid_page_json.errors[0].detail,
2392+
"invalid digit found in string"
2393+
);
2394+
}

src/tests/user.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -296,7 +296,7 @@ fn following() {
296296
assert_eq!(r.meta.more, false);
297297

298298
user.get_with_query::<()>("/api/v1/me/updates", "page=0")
299-
.bad_with_status(200); // TODO: Should be 500
299+
.bad_with_status(400);
300300
}
301301

302302
#[test]

0 commit comments

Comments
 (0)