Real error handling for paginated iterator example

This commit is contained in:
David Tolnay 2017-05-23 12:05:22 -07:00
parent 5974bee3c2
commit de913078f3
No known key found for this signature in database
GPG key ID: F9BA143B95FF6D82

View file

@ -535,16 +535,19 @@ fn run() -> Result<()> {
[![reqwest-badge]][reqwest] [![serde-badge]][serde] [![cat-net-badge]][cat-net] [![cat-encoding-badge]][cat-encoding] [![reqwest-badge]][reqwest] [![serde-badge]][serde] [![cat-net-badge]][cat-net] [![cat-encoding-badge]][cat-encoding]
In this example, A Paginated JSON API is exposed as a Rust Iterator, and will create further requests until the iteration is finished. Wraps a paginated web API in a convenient Rust iterator. The iterator lazily
fetches the next page of results from the remote server as it arrives at the end
of each page.
An initial request is made with [`reqwest::get`] and that is used to create a `DependencyList`. The `DependencyList` implements the [`std::iter::Iterator`] trait so it can be consumed in a rust fashion. ```rust
```rust, no_run
#[macro_use] #[macro_use]
extern crate serde_derive; extern crate serde_derive;
extern crate serde;
extern crate reqwest; extern crate reqwest;
use std::vec::IntoIter; #[macro_use]
extern crate error_chain;
#[derive(Deserialize)] #[derive(Deserialize)]
struct ApiResponse { struct ApiResponse {
@ -552,7 +555,7 @@ struct ApiResponse {
meta: Meta, meta: Meta,
} }
//Could include more fields here if needed // Could capture more fields here if needed
#[derive(Deserialize)] #[derive(Deserialize)]
struct Dependency { struct Dependency {
crate_id: String, crate_id: String,
@ -563,82 +566,76 @@ struct Meta {
total: u32 total: u32
} }
struct DependencyList { struct ReverseDependencies {
crate_id: String, crate_id: String,
dependencies: IntoIter<Dependency>, dependencies: <Vec<Dependency> as IntoIterator>::IntoIter,
page: u32, page: u32,
per_page: u32, per_page: u32,
total_amount: u32 total: u32,
} }
impl DependencyList { error_chain! {
foreign_links {
Reqwest(reqwest::Error);
}
}
fn new(crate_id: &str) -> DependencyList { impl ReverseDependencies {
fn of(crate_id: &str) -> Self {
let page = 1; ReverseDependencies {
let per_page = 100; crate_id: crate_id.to_owned(),
let api_response = DependencyList::get_api_response(crate_id, page, per_page).expect("Could not get API response!"); dependencies: vec![].into_iter(),
let total_amount = api_response.meta.total; page: 0,
per_page: 100,
DependencyList { total: 0,
crate_id: String::from(crate_id),
dependencies: api_response.dependencies.into_iter(),
page: page,
per_page: per_page,
total_amount: total_amount
} }
} }
fn get_api_response(crate_id: &str, page: u32, per_page: u32) -> Result<ApiResponse, reqwest::Error> { fn try_next(&mut self) -> Result<Option<Dependency>> {
let request_url = format!("https://crates.io/api/v1/crates/{}/reverse_dependencies?page={}&per_page={}", crate_id, page, per_page); // If the previous page has a dependency that hasn't been looked at.
reqwest::get(&request_url)?.json::<ApiResponse>() if let Some(dep) = self.dependencies.next() {
} return Ok(Some(dep));
}
// If there are no more reverse dependencies.
if self.page > 0 && self.page * self.per_page >= self.total {
return Ok(None);
}
// Fetch the next page.
self.page += 1;
let url = format!("https://crates.io/api/v1/crates/{}/reverse_dependencies?page={}&per_page={}",
self.crate_id, self.page, self.per_page);
let response = reqwest::get(&url)?.json::<ApiResponse>()?;
self.dependencies = response.dependencies.into_iter();
self.total = response.meta.total;
Ok(self.dependencies.next())
}
} }
impl Iterator for DependencyList { impl Iterator for ReverseDependencies {
type Item = Dependency; type Item = Result<Dependency>;
fn next(&mut self) -> Option<Dependency> { fn next(&mut self) -> Option<Self::Item> {
match self.dependencies.next() { // Some juggling required here because `try_next` returns a result
Some(dependency) => { // containing an option, while `next` is supposed to return an option
Some(dependency) // containing a result.
}, match self.try_next() {
None => { Ok(Some(dep)) => Some(Ok(dep)),
Ok(None) => None,
//If we're not at the end we request the next page Err(err) => Some(Err(err)),
if self.total_amount > self.page * self.per_page {
self.page += 1;
let api_response = DependencyList::get_api_response(&self.crate_id, self.page, self.per_page).expect("Could not get API response!");
self.dependencies = api_response.dependencies.into_iter();
//Send the next dependency
self.dependencies.next()
} else {
None
}
}
} }
} }
} }
fn run() -> Result<()> {
fn main() { for dep in ReverseDependencies::of("serde") {
let dependency_list = DependencyList::new("serde"); println!("reverse dependency: {}", dep?.crate_id);
for dependency in dependency_list {
println!("Dependency: {}", dependency.crate_id);
} }
Ok(())
} }
quick_main!(run);
``` ```
[ex-file-post]: #ex-file-post [ex-file-post]: #ex-file-post