phabricator_api/
client.rs1use 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}