Вызов Веб API

Запрос к API GitHub

reqwest-badge serde-badge cat-net-badge cat-encoding-badge

Пример посылает запрос к GitHub stargazers API v3 используя метод reqwest::get, чтобы получить список всех пользователей, кто отметил какой-то проект на GitHub звёздочкой. reqwest::Response десериализуется с помощью Response::json в объекты типа User, которые реализуют типаж serde::Deserialize.

[tokio::main] используется, чтобы установить асинхронный движок и обрабатывать ожидания выполнения для [reqwet::get] до обработки запросов и преобразования в значения типа User.

use serde::Deserialize;
use reqwest::Error;

#[derive(Deserialize, Debug)]
struct User {
    login: String,
    id: u32,
}

#[tokio::main]
async fn main() -> Result<(), Error> {
    let request_url = format!("https://api.github.com/repos/{owner}/{repo}/stargazers",
                              owner = "rust-lang-nursery",
                              repo = "rust-cookbook");
    println!("{}", request_url);
    let response = reqwest::get(&request_url).await?;

    let users: Vec<User> = response.json().await?;
    println!("{:?}", users);
    Ok(())
}

Проверка существования ресурса в API

reqwest-badge cat-net-badge

Данный рецепт запрашивает точку Users широко известного сервиса GitHub с использованием метода HEAD (Client::head) и затем смотрит код ответа для определения того, был ли вызов успешен или нет. Такой способ подходит для того, чтобы быстро "ткнуть" в REST ресурс без необходимости загружать тело ответа. reqwest::Client сконфигурированный с ClientBuilder::timeout, который гарантирует то, что запрос не будет длиться дольше данного временного интервала.

Из-за возвращения методами ClientBuilder::build и [ReqwestBuilder::send] типов ошибки reqwest::Error, используется псевдоним reqwest::Result для возвращаемого значения функции main.

use reqwest::Result;
use std::time::Duration;
use reqwest::ClientBuilder;

#[tokio::main]
async 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 = ClientBuilder::new().timeout(timeout).build()?;
    let response = client.head(&request_url).send().await?;

    if response.status().is_success() {
        println!("{} is a user!", user);
    } else {
        println!("{} is not a user!", user);
    }

    Ok(())
}

Создать и удалить Gist через GitHub API

reqwest-badge serde-badge cat-net-badge cat-encoding-badge

Пример создаёт фрагмент кода (в терминах GitHub называется gist) с помощью POST-запроса gists API v3 используя Client::post и удаляет только что созданный фрагмент с помощью DELETE-запроса используя Client::delete.

Объект reqwest::Client отвечает за детали для обоих типов запросов выше, детали включают URL адрес, тело запроса и аутентификацию. Тело POST-запроса как JSON можно подготовить с помощью макроса serde_json::json!. Вызов RequestBuilder::json определяет тело запроса. RequestBuilder::basic_auth управляет аутентификацией. А вызов RequestBuilder::send синхронно выполняет созданные запросы.

use error_chain::error_chain;
use serde::Deserialize;
use serde_json::json;
use std::env;
use reqwest::Client;

error_chain! {
    foreign_links {
        EnvVar(env::VarError);
        HttpRequest(reqwest::Error);
    }
}

#[derive(Deserialize, Debug)]
struct Gist {
    id: String,
    html_url: String,
}

#[tokio::main]
async 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 = Client::new()
        .post(request_url)
        .basic_auth(gh_user.clone(), Some(gh_pass.clone()))
        .json(&gist_body)
        .send().await?;

    let gist: Gist = response.json().await?;
    println!("Created {:?}", gist);

    let request_url = format!("{}/{}",request_url, gist.id);
    let response = Client::new()
        .delete(&request_url)
        .basic_auth(gh_user, Some(gh_pass))
        .send().await?;

    println!("Gist {} deleted! Status code: {}",gist.id, response.status());
    Ok(())
}

Данный пример использует HTTP Basic Auth чтобы получить доступ к GitHub API. Но в промышленном коде типичный пример использования может задействовать гораздо более сложный OAuth процесс авторизации.

Обращение к RESTful API с постраничной обработкой

reqwest-badge serde-badge cat-net-badge cat-encoding-badge

Пример оборачивает метода веб интерфейса в удобный итератор на Rust. Этот итератор лениво запрашивает следующую страницу результатов от удалённого сервера по достижении конца каждой страницы.

use reqwest::Result;
use serde::Deserialize;

#[derive(Deserialize)]
struct ApiResponse {
    dependencies: Vec<Dependency>,
    meta: Meta,
}

#[derive(Deserialize)]
struct Dependency {
    crate_id: String,
}

#[derive(Deserialize)]
struct Meta {
    total: u32,
}

struct ReverseDependencies {
    crate_id: String,
    dependencies: <Vec<Dependency> as IntoIterator>::IntoIter,
    client: reqwest::blocking::Client,
    page: u32,
    per_page: u32,
    total: u32,
}

impl ReverseDependencies {
    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);

        let response = self.client.get(&url).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<()> {
    for dep in ReverseDependencies::of("serde")? {
        println!("reverse dependency: {}", dep?.crate_id);
    }
    Ok(())
}