misskey_http/client/
builder.rs

1use std::convert::TryInto;
2use std::result::Result as StdResult;
3
4use crate::client::HttpClient;
5use crate::error::{Error, Result};
6
7use isahc::http::{
8    self,
9    header::{HeaderMap, HeaderName, HeaderValue},
10};
11use url::Url;
12
13#[derive(Debug)]
14struct HttpClientBuilderInner {
15    url: Url,
16    token: Option<String>,
17    additional_headers: HeaderMap,
18}
19
20/// Builder for [`HttpClient`].
21#[derive(Debug)]
22pub struct HttpClientBuilder {
23    inner: Result<HttpClientBuilderInner>,
24}
25
26trait ResultExt<T, E> {
27    fn err_into<U>(self) -> StdResult<T, U>
28    where
29        E: Into<U>;
30    fn and_then_mut<F>(&mut self, op: F)
31    where
32        F: FnOnce(&mut T) -> StdResult<(), E>;
33}
34
35impl<T, E> ResultExt<T, E> for StdResult<T, E> {
36    fn err_into<U>(self) -> StdResult<T, U>
37    where
38        E: Into<U>,
39    {
40        self.map_err(Into::into)
41    }
42
43    fn and_then_mut<F>(&mut self, f: F)
44    where
45        F: FnOnce(&mut T) -> StdResult<(), E>,
46    {
47        if let Ok(x) = self {
48            if let Err(e) = f(x) {
49                *self = Err(e);
50            }
51        }
52    }
53}
54
55impl HttpClientBuilder {
56    /// Creates a new builder instance with `url`.
57    ///
58    /// All configurations are set to default.
59    pub fn new<T>(url: T) -> Self
60    where
61        T: TryInto<Url>,
62        T::Error: Into<Error>,
63    {
64        let inner = url.try_into().err_into().map(|url| HttpClientBuilderInner {
65            url,
66            token: None,
67            additional_headers: HeaderMap::new(),
68        });
69        HttpClientBuilder { inner }
70    }
71
72    /// Creates a new builder instance with the given host name `host`.
73    ///
74    /// This method configures the builder with a URL of the form `https://{host}/api/`.
75    /// All other configurations are set to default.
76    pub fn with_host<S>(host: S) -> Self
77    where
78        S: AsRef<str>,
79    {
80        let url = format!("https://{}/api/", host.as_ref());
81        HttpClientBuilder::new(url.as_str())
82    }
83
84    /// Sets an additional header for all requests.
85    pub fn header<K, V>(mut self, key: K, value: V) -> Self
86    where
87        K: TryInto<HeaderName>,
88        V: TryInto<HeaderValue>,
89        K::Error: Into<http::Error>,
90        V::Error: Into<http::Error>,
91    {
92        self.inner.and_then_mut(|inner| {
93            let result = key.try_into().err_into().and_then(|key| {
94                let value = value.try_into().err_into()?;
95                Ok((key, value))
96            });
97            match result {
98                Ok((key, value)) => {
99                    inner.additional_headers.insert(key, value);
100                    Ok(())
101                }
102                Err(e) => Err(Error::Network(isahc::Error::InvalidHttpFormat(e))),
103            }
104        });
105        self
106    }
107
108    /// Sets an API token.
109    pub fn token<S>(mut self, token: S) -> Self
110    where
111        S: Into<String>,
112    {
113        self.inner.and_then_mut(|inner| {
114            inner.token = Some(token.into());
115            Ok(())
116        });
117        self
118    }
119
120    /// Finish this builder instance and build [`HttpClient`].
121    pub fn build(self) -> Result<HttpClient> {
122        self.inner.and_then(|inner| {
123            Ok(HttpClient {
124                url: inner.url,
125                token: inner.token,
126                client: isahc::HttpClientBuilder::new()
127                    .default_headers(&inner.additional_headers)
128                    .build()?,
129            })
130        })
131    }
132}