Skip to main content

byo_graphql/
client.rs

1use {
2    crate::*,
3    serde::{
4        de::DeserializeOwned,
5        Deserialize,
6        Serialize,
7    },
8    std::collections::HashMap,
9};
10
11/// A client that you may reuse to do several queries
12pub struct GraphqlClient {
13    requester: reqwest::Client,
14    url: String,
15    bearer_auth: Option<String>,
16}
17
18#[derive(Debug, Serialize)]
19struct GraphqlRequest {
20    query: String,
21}
22
23#[derive(Debug, Deserialize)]
24struct GraphqlResponse<D> {
25    data: Option<D>,
26    errors: Option<Vec<GraphqlError>>,
27}
28
29impl GraphqlClient {
30    /// create a client
31    pub fn new<S: Into<String>>(url: S) -> ByoResult<Self> {
32        let requester = reqwest::Client::builder().user_agent("byo/0.1").build()?;
33        Ok(Self {
34            requester,
35            url: url.into(),
36            bearer_auth: None,
37        })
38    }
39    /// specify the optional authentication token which will be used
40    /// in all requests
41    pub fn set_bearer_auth<S: Into<String>>(
42        &mut self,
43        auth: S,
44    ) {
45        self.bearer_auth = Some(auth.into());
46    }
47    /// return a raw reqwest Response. You should usually not
48    /// need this function
49    pub async fn raw<S: Into<String>>(
50        &self,
51        query: S,
52    ) -> ByoResult<reqwest::Response> {
53        let mut builder = self.requester.post(&self.url);
54        if let Some(auth) = &self.bearer_auth {
55            builder = builder.bearer_auth(auth);
56        }
57        let res = builder
58            .json(&GraphqlRequest {
59                query: query.into(),
60            })
61            .send()
62            .await?
63            .error_for_status()?;
64        Ok(res)
65    }
66    /// get the server's answer as unparsed text.
67    /// This is mainly useful to debug and tune your structures or query
68    pub async fn text<S: Into<String>>(
69        &self,
70        query: S,
71    ) -> ByoResult<String> {
72        Ok(self.raw(query).await?.text().await?)
73    }
74    /// get the `data` part of the answer in the desired type
75    /// (it usually looks like a map)
76    pub async fn get_data<S: Into<String>, Data: DeserializeOwned>(
77        &self,
78        query: S,
79    ) -> ByoResult<Data> {
80        let res = self.raw(query).await?;
81        let response: GraphqlResponse<Data> = res.json().await?;
82        if let Some(errors) = response.errors {
83            Err(ByoError::Graphql(errors))
84        } else {
85            response.data.ok_or(ByoError::NoData)
86        }
87    }
88    /// get the first item in the answer, if present.
89    /// This is a convenience method for the simplest case, most
90    /// especially for when you query a unique item.
91    pub async fn get_first_item<S: Into<String>, Item: DeserializeOwned>(
92        &self,
93        query: S,
94    ) -> ByoResult<Item> {
95        let mut map: HashMap<String, Option<Item>> = self.get_data(query).await?;
96        let single = map.drain().next().and_then(|e| e.1);
97        single.ok_or(ByoError::NoData)
98    }
99}