Calling a Web API
Query the GitHub API
[![reqwest-badge]][reqwest] [![serde-badge]][serde] [![cat-net-badge]][cat-net] [![cat-encoding-badge]][cat-encoding]
Queries [GitHub stargazers API v3][github-api-stargazers] with reqwest::get
to get list of all users who have marked a GitHub repository with a star.
reqwest::Response
is deserialized into User
objects implementing serde::Deserialize
.
The program expects the GitHub personal access token to be specified in the
environment variable GITHUB_TOKEN
. Request setup includes the [reqwest::header::USER_AGENT
]
header as required by the [GitHub API][github-api]. The program deserializes
the response body with [serde_json::from_str
] into a vector of User
objects and
processing the response into User instances.
use serde::Deserialize; use reqwest::Error; use reqwest::header::USER_AGENT; #[derive(Deserialize, Debug)] struct User { login: String, id: u32, } fn main() -> Result<(), Box<dyn std::error::Error>> { let request_url = format!("https://api.github.com/repos/{owner}/{repo}/stargazers", owner = "rust-lang-nursery", repo = "rust-cookbook"); println!("{}", request_url); let client = reqwest::blocking::Client::new(); let response = client .get(request_url) .header(USER_AGENT, "rust-web-api-client") // gh api requires a user-agent header .send()?; let users: Vec<User> = response.json()?; println!("{:?}", users); Ok(()) }
Check if an API resource exists
[![reqwest-badge]][reqwest] [![cat-net-badge]][cat-net]
Query the GitHub Users Endpoint using a HEAD request (Client::head
) and then
inspect the response code to determine success. This is a quick way to query a
rest resource without needing to receive a body. reqwest::Client
configured
with ClientBuilder::timeout
ensures a request will not last longer than a
timeout.
Due to both ClientBuilder::build
and [ReqwestBuilder::send
] returning reqwest::Error
types, the shortcut reqwest::Result
is used for the main function return type.
use reqwest::Result; use std::time::Duration; use reqwest::ClientBuilder; fn main() -> Result<()> { let user = "ferris-the-crab"; let request_url = format!("https://api.github.com/users/{}", user); println!("{}", request_url); let timeout = Duration::new(5, 0); let client = reqwest::blocking::ClientBuilder::new().timeout(timeout).build()?; let response = client.head(&request_url).send()?; if response.status().is_success() { println!("{} is a user!", user); } else { println!("{} is not a user!", user); } Ok(()) }
Create and delete Gist with GitHub API
[![reqwest-badge]][reqwest] [![serde-badge]][serde] [![cat-net-badge]][cat-net] [![cat-encoding-badge]][cat-encoding]
Creates a gist with POST request to GitHub [gists API v3][gists-api] using
Client::post
and removes it with DELETE request using Client::delete
.
The reqwest::Client
is responsible for details of both requests including
URL, body and authentication. The POST body from serde_json::json!
macro
provides arbitrary JSON body. Call to RequestBuilder::json
sets the request
body. RequestBuilder::basic_auth
handles authentication. The call to
RequestBuilder::send
synchronously executes the requests.
use anyhow::Result; use serde::Deserialize; use serde_json::json; use std::env; use reqwest::Client; #[derive(Deserialize, Debug)] struct Gist { id: String, html_url: String, } fn main() -> Result<()> { let gh_user = env::var("GH_USER")?; let gh_pass = env::var("GH_PASS")?; let gist_body = json!({ "description": "the description for this gist", "public": true, "files": { "main.rs": { "content": r#"fn main() { println!("hello world!");}"# } }}); let request_url = "https://api.github.com/gists"; let response = reqwest::blocking::Client::new() .post(request_url) .basic_auth(gh_user.clone(), Some(gh_pass.clone())) .json(&gist_body) .send()?; let gist: Gist = response.json()?; println!("Created {:?}", gist); let request_url = format!("{}/{}",request_url, gist.id); let response = reqwest::blocking::Client::new() .delete(&request_url) .basic_auth(gh_user, Some(gh_pass)) .send()?; println!("Gist {} deleted! Status code: {}",gist.id, response.status()); Ok(()) }
The example uses HTTP Basic Auth in order to authorize access to GitHub API. Typical use case would employ one of the much more complex OAuth authorization flows.
Consume a paginated RESTful API
[![reqwest-badge]][reqwest] [![serde-badge]][serde] [![cat-net-badge]][cat-net] [![cat-encoding-badge]][cat-encoding]
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.
// cargo-deps: reqwest="0.11", serde="1" mod paginated { use reqwest::Result; use reqwest::header::USER_AGENT; use serde::Deserialize; #[derive(Deserialize)] struct ApiResponse { dependencies: Vec<Dependency>, meta: Meta, } #[derive(Deserialize)] pub struct Dependency { pub crate_id: String, pub id: u32, } #[derive(Deserialize)] struct Meta { total: u32, } pub struct ReverseDependencies { crate_id: String, dependencies: <Vec<Dependency> as IntoIterator>::IntoIter, client: reqwest::blocking::Client, page: u32, per_page: u32, total: u32, } impl ReverseDependencies { pub fn of(crate_id: &str) -> Result<Self> { Ok(ReverseDependencies { crate_id: crate_id.to_owned(), dependencies: vec![].into_iter(), client: reqwest::blocking::Client::new(), page: 0, per_page: 100, total: 0, }) } fn try_next(&mut self) -> Result<Option<Dependency>> { if let Some(dep) = self.dependencies.next() { return Ok(Some(dep)); } if self.page > 0 && self.page * self.per_page >= self.total { return Ok(None); } 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); println!("{}", url); let response = self.client.get(&url).header( USER_AGENT, "cookbook agent", ).send()?.json::<ApiResponse>()?; self.dependencies = response.dependencies.into_iter(); self.total = response.meta.total; Ok(self.dependencies.next()) } } impl Iterator for ReverseDependencies { type Item = Result<Dependency>; fn next(&mut self) -> Option<Self::Item> { match self.try_next() { Ok(Some(dep)) => Some(Ok(dep)), Ok(None) => None, Err(err) => Some(Err(err)), } } } } fn main() -> Result<(), Box<dyn std::error::Error>> { for dep in paginated::ReverseDependencies::of("serde")? { let dependency = dep?; println!("{} depends on {}", dependency.id, dependency.crate_id); } Ok(()) } <!-- Links, in a few categories. Follow the existing structure. Keep lines sorted. --> <!-- Categories --> [cat-caching-badge]: https://badge-cache.kominick.com/badge/caching--x.svg?style=social [cat-caching]: https://crates.io/categories/caching [cat-command-line-badge]: https://badge-cache.kominick.com/badge/command_line--x.svg?style=social [cat-command-line]: https://crates.io/categories/command-line-interface [cat-compression-badge]: https://badge-cache.kominick.com/badge/compression--x.svg?style=social [cat-compression]: https://crates.io/categories/compression [cat-concurrency-badge]: https://badge-cache.kominick.com/badge/concurrency--x.svg?style=social [cat-concurrency]: https://crates.io/categories/concurrency [cat-config-badge]: https://badge-cache.kominick.com/badge/config--x.svg?style=social [cat-config]: https://crates.io/categories/config [cat-cryptography-badge]: https://badge-cache.kominick.com/badge/cryptography--x.svg?style=social [cat-cryptography]: https://crates.io/categories/cryptography [cat-database-badge]: https://badge-cache.kominick.com/badge/database--x.svg?style=social [cat-database]: https://crates.io/categories/database [cat-date-and-time-badge]: https://badge-cache.kominick.com/badge/date_and_time--x.svg?style=social [cat-date-and-time]: https://crates.io/categories/date-and-time [cat-debugging-badge]: https://badge-cache.kominick.com/badge/debugging--x.svg?style=social [cat-debugging]: https://crates.io/categories/debugging [cat-development-tools-badge]: https://badge-cache.kominick.com/badge/development_tools--x.svg?style=social [cat-development-tools]: https://crates.io/categories/development-tools [cat-encoding-badge]: https://badge-cache.kominick.com/badge/encoding--x.svg?style=social [cat-encoding]: https://crates.io/categories/encoding [cat-filesystem-badge]: https://badge-cache.kominick.com/badge/filesystem--x.svg?style=social [cat-filesystem]: https://crates.io/categories/filesystem [cat-hardware-support-badge]: https://badge-cache.kominick.com/badge/hardware_support--x.svg?style=social [cat-hardware-support]: https://crates.io/categories/hardware-support [cat-net-badge]: https://badge-cache.kominick.com/badge/net--x.svg?style=social [cat-net]: https://crates.io/categories/network-programming [cat-no-std-badge]: https://badge-cache.kominick.com/badge/no_std--x.svg?style=social [cat-no-std]: https://crates.io/categories/no-std [cat-os-badge]: https://badge-cache.kominick.com/badge/OS--x.svg?style=social [cat-os]: https://crates.io/categories/os [cat-rendering-badge]: https://badge-cache.kominick.com/badge/rendering--x.svg?style=social [cat-rendering]: https://crates.io/categories/rendering [cat-rust-patterns-badge]: https://badge-cache.kominick.com/badge/rust_patterns--x.svg?style=social [cat-rust-patterns]: https://crates.io/categories/rust-patterns [cat-science-badge]: https://badge-cache.kominick.com/badge/science--x.svg?style=social [cat-science]: https://crates.io/categories/science [cat-text-processing-badge]: https://badge-cache.kominick.com/badge/text_processing--x.svg?style=social [cat-text-processing]: https://crates.io/categories/text-processing [cat-time-badge]: https://badge-cache.kominick.com/badge/time--x.svg?style=social [cat-time]: https://crates.io/categories/date-and-time <!-- Crates --> [ansi_term-badge]: https://badge-cache.kominick.com/crates/v/ansi_term.svg?label=ansi_term [ansi_term]: https://docs.rs/ansi_term/ [anyhow-badge]: https://badge-cache.kominick.com/crates/v/anyhow.svg?label=anyhow [anyhow]: https://docs.rs/anyhow/ [base64-badge]: https://badge-cache.kominick.com/crates/v/base64.svg?label=base64 [base64]: https://docs.rs/base64/ [bitflags-badge]: https://badge-cache.kominick.com/crates/v/bitflags.svg?label=bitflags [bitflags]: https://docs.rs/bitflags/ [byteorder-badge]: https://badge-cache.kominick.com/crates/v/byteorder.svg?label=byteorder [byteorder]: https://docs.rs/byteorder/ [cc-badge]: https://badge-cache.kominick.com/crates/v/cc.svg?label=cc [cc]: https://docs.rs/cc [chrono-badge]: https://badge-cache.kominick.com/crates/v/chrono.svg?label=chrono [chrono]: https://docs.rs/chrono/ [clap-badge]: https://badge-cache.kominick.com/crates/v/clap.svg?label=clap [clap]: https://docs.rs/clap/ [crossbeam-badge]: https://badge-cache.kominick.com/crates/v/crossbeam.svg?label=crossbeam [crossbeam]: https://docs.rs/crossbeam/ [csv-badge]: https://badge-cache.kominick.com/crates/v/csv.svg?label=csv [csv]: https://docs.rs/csv/ [data-encoding-badge]: https://badge-cache.kominick.com/crates/v/data-encoding.svg?label=data-encoding [data-encoding]: https://docs.rs/data-encoding/ [env_logger-badge]: https://badge-cache.kominick.com/crates/v/env_logger.svg?label=env_logger [env_logger]: https://docs.rs/env_logger/ [error-chain-badge]: https://badge-cache.kominick.com/crates/v/error-chain.svg?label=error-chain [error-chain]: https://docs.rs/error-chain/ [flate2-badge]: https://badge-cache.kominick.com/crates/v/flate2.svg?label=flate2 [flate2]: https://docs.rs/flate2/ [glob-badge]:https://badge-cache.kominick.com/crates/v/glob.svg?label=glob [glob]: https://docs.rs/glob/ [hyper-badge]: https://badge-cache.kominick.com/crates/v/hyper.svg?label=hyper [hyper]: https://docs.rs/hyper/ [image-badge]: https://badge-cache.kominick.com/crates/v/image.svg?label=image [image]: https://docs.rs/image/ [lazy_static-badge]: https://badge-cache.kominick.com/crates/v/lazy_static.svg?label=lazy_static [lazy_static]: https://docs.rs/lazy_static/ [log-badge]: https://badge-cache.kominick.com/crates/v/log.svg?label=log [log4rs-badge]: https://badge-cache.kominick.com/crates/v/log4rs.svg?label=log4rs [log4rs]: https://docs.rs/log4rs/ [log]: https://docs.rs/log/ [memmap-badge]: https://badge-cache.kominick.com/crates/v/memmap.svg?label=memmap [memmap]: https://docs.rs/memmap/ [mime-badge]: https://badge-cache.kominick.com/crates/v/csv.svg?label=mime [mime]: https://docs.rs/mime/ [nalgebra-badge]: https://badge-cache.kominick.com/crate/nalgebra.svg?label=nalgebra [nalgebra]: https://docs.rs/nalgebra [ndarray-badge]: https://badge-cache.kominick.com/crate/ndarray.svg?label=ndarray [ndarray]: https://docs.rs/ndarray [num-badge]: https://badge-cache.kominick.com/crates/v/num.svg?label=num [num]: https://docs.rs/num/ [num_cpus-badge]: https://badge-cache.kominick.com/crates/v/num_cpus.svg?label=num_cpus [num_cpus]: https://docs.rs/num_cpus/ [percent-encoding-badge]: https://badge-cache.kominick.com/crates/v/percent-encoding.svg?label=percent-encoding [postgres-badge]: https://badge-cache.kominick.com/crates/v/postgres.svg?label=postgres [postgres]: https://docs.rs/postgres/0.15.2/postgres/ [rand-badge]: https://badge-cache.kominick.com/crates/v/rand.svg?label=rand [rand]: https://docs.rs/rand/ [rand_distr-badge]: https://badge-cache.kominick.com/crates/v/rand_distr.svg?label=rand_distr [rand_distr]: https://docs.rs/rand_distr/ [rayon-badge]: https://badge-cache.kominick.com/crates/v/rayon.svg?label=rayon [rayon]: https://docs.rs/rayon/ [regex-badge]: https://badge-cache.kominick.com/crates/v/regex.svg?label=regex [regex]: https://docs.rs/regex/ [reqwest-badge]: https://badge-cache.kominick.com/crates/v/reqwest.svg?label=reqwest [reqwest]: https://docs.rs/reqwest/ [ring-badge]: https://badge-cache.kominick.com/crates/v/ring.svg?label=ring [ring]: https://briansmith.org/rustdoc/ring/ [rusqlite-badge]: https://badge-cache.kominick.com/crates/v/rusqlite.svg?label=rusqlite [rusqlite]: https://crates.io/crates/rusqlite/ [same_file-badge]: https://badge-cache.kominick.com/crates/v/same_file.svg?label=same_file [same_file]: https://docs.rs/same-file/ [select-badge]: https://badge-cache.kominick.com/crates/v/select.svg?label=select [select]: https://docs.rs/select/ [semver-badge]: https://badge-cache.kominick.com/crates/v/semver.svg?label=semver [semver]: https://docs.rs/semver/ [serde-badge]: https://badge-cache.kominick.com/crates/v/serde.svg?label=serde [serde-json-badge]: https://badge-cache.kominick.com/crates/v/serde_json.svg?label=serde_json [serde-json]: https://docs.rs/serde_json/*/serde_json/ [serde]: https://docs.rs/serde/ [std-badge]: https://badge-cache.kominick.com/badge/std-1.29.1-blue.svg [std]: https://doc.rust-lang.org/std [syslog-badge]: https://badge-cache.kominick.com/crates/v/syslog.svg?label=syslog [syslog]: https://docs.rs/syslog/ [tar-badge]: https://badge-cache.kominick.com/crates/v/tar.svg?label=tar [tar]: https://docs.rs/tar/ [tempfile-badge]: https://badge-cache.kominick.com/crates/v/tempfile.svg?label=tempfile [tempfile]: https://docs.rs/tempfile/ [thiserror-badge]: https://badge-cache.kominick.com/crates/v/thiserror.svg?label=thiserror [thiserror]: https://docs.rs/thiserror/ [threadpool-badge]: https://badge-cache.kominick.com/crates/v/threadpool.svg?label=threadpool [threadpool]: https://docs.rs/threadpool/ [toml-badge]: https://badge-cache.kominick.com/crates/v/toml.svg?label=toml [toml]: https://docs.rs/toml/ [url-badge]: https://badge-cache.kominick.com/crates/v/url.svg?label=url [url]: https://docs.rs/url/ [unicode-segmentation-badge]: https://badge-cache.kominick.com/crates/v/unicode-segmentation.svg?label=unicode-segmentation [unicode-segmentation]: https://docs.rs/unicode-segmentation/ [walkdir-badge]: https://badge-cache.kominick.com/crates/v/walkdir.svg?label=walkdir [walkdir]: https://docs.rs/walkdir/