headhunter_bindings/
client.rs

1use super::{Error, Result, request::*, response::*};
2
3use std::fmt::Display;
4
5/// Provides access to Headhunter's API using the user-defined `Request` and `Response` structures
6pub struct Client {
7    access_token: String,
8    reusable_client: reqwest::Client,
9}
10
11impl Client {
12    const USER_AGENT: &'static str = "App/1.0 (hidden@gmail.com)";
13    const BASE_URL: &'static str = "https://api.hh.ru";
14
15    /// Creates new `Client` instance with `access_token` (token doesn't checked)
16    #[allow(clippy::result_large_err)]
17    pub fn new(access_token: String) -> Result<Self> {
18        Ok(Self {
19            access_token,
20            reusable_client: reqwest::ClientBuilder::new()
21                .user_agent(Self::USER_AGENT)
22                .build()?,
23        })
24    }
25
26    #[inline]
27    #[allow(clippy::result_large_err)]
28    fn build_url<Req: Request>() -> Result<String> {
29        let req = Req::method().ok_or_else(|| Error::UrlBuild)?;
30        Ok(format!("{url}/{req}", url = Self::BASE_URL))
31    }
32
33    #[inline]
34    #[allow(clippy::result_large_err)]
35    fn build_url_with_value<Req: Request, T: Display>(value: T) -> Result<String> {
36        let req = Req::build_url(value).ok_or_else(|| Error::UrlBuild)?;
37        Ok(format!("{url}/{req}", url = Self::BASE_URL))
38    }
39
40    async fn request_base<Url: reqwest::IntoUrl, Req: Request>(
41        &self,
42        method: reqwest::Method,
43        url: Url,
44        req: &Req,
45    ) -> Result<reqwest::Response> {
46        let response = self
47            .reusable_client
48            .request(method, url)
49            .json(req)
50            .bearer_auth(&self.access_token)
51            .send()
52            .await?;
53
54        Ok(response)
55    }
56
57    async fn request<Url: reqwest::IntoUrl, Req: Request>(
58        &self,
59        method: reqwest::Method,
60        url: Url,
61        req: &Req,
62    ) -> Result<Req::Response> {
63        let ret = self.request_base(method, url, req).await?;
64
65        if ret.status() == reqwest::StatusCode::NO_CONTENT {
66            return Ok(serde_json::from_str::<'_, Req::Response>("null")?);
67        }
68
69        let response = ret.json::<HeadhunterResponse<Req::Response>>().await?;
70        match response {
71            HeadhunterResponse::Response(response) => Ok(response),
72            HeadhunterResponse::Error(error) => Err(Error::Headhunter(error)),
73        }
74    }
75
76    /// Interacts with API using the GET method, you need to pass `&Request` structure
77    pub async fn get<Req: Request>(&self, req: &Req) -> Result<Req::Response> {
78        let url = Self::build_url::<Req>()?;
79        self.request(reqwest::Method::GET, url, req).await
80    }
81
82    /// Interacts with API using the POST method, you need to pass `&Request` structure and `value`
83    pub async fn post_with_value<Req: Request, T: Display>(
84        &self,
85        req: &Req,
86        value: T,
87    ) -> Result<Req::Response> {
88        let url = Self::build_url_with_value::<Req, _>(value)?;
89        self.request(reqwest::Method::POST, url, req).await
90    }
91}