phabricator_api/
client.rs

1use super::ser::serialize_phab;
2use serde::Serialize;
3use serde::{de::DeserializeOwned, Deserialize};
4use thiserror::Error;
5use url::Url;
6
7pub trait ApiRequest: Serialize {
8    type Reply: DeserializeOwned + std::fmt::Debug;
9    const ROUTE: &'static str;
10
11    fn route(&self) -> &'static str {
12        Self::ROUTE
13    }
14}
15
16#[derive(Debug, Serialize)]
17struct Request<'a, R: Serialize> {
18    #[serde(rename = "api.token")]
19    token: &'a str,
20    #[serde(flatten, serialize_with = "serialize_phab")]
21    request: &'a R,
22}
23
24#[derive(Debug, Deserialize)]
25struct Reply<R> {
26    error_code: Option<String>,
27    error_info: Option<String>,
28    result: Option<R>,
29}
30
31#[derive(Debug, Clone)]
32pub struct Client {
33    base: Url,
34    token: String,
35    client: reqwest::Client,
36}
37
38#[derive(Error, Debug)]
39pub enum RequestError {
40    #[error("API Error ({code}): {info}")]
41    Api { code: String, info: String },
42    #[error("Incomplete reply")]
43    Incomplete,
44    #[error("Request failure {0}")]
45    Request(#[from] reqwest::Error),
46}
47
48impl Client {
49    pub fn new(base: Url, token: String) -> Client {
50        let client = reqwest::Client::new();
51        Client {
52            base,
53            token,
54            client,
55        }
56    }
57
58    pub async fn request<R>(&self, request: &R) -> Result<R::Reply, RequestError>
59    where
60        R: ApiRequest,
61    {
62        let u = self.base.join(request.route()).unwrap();
63        let t = Request {
64            token: &self.token,
65            request,
66        };
67
68        let response = self.client.post(u).form(&t).send().await?;
69        let reply: Reply<R::Reply> = response.error_for_status()?.json().await?;
70
71        match reply {
72            Reply {
73                result: Some(r), ..
74            } => Ok(r),
75            Reply {
76                error_code: Some(code),
77                error_info: Some(info),
78                ..
79            } => Err(RequestError::Api { code, info }),
80            _ => Err(RequestError::Incomplete),
81        }
82    }
83}