af_slack/
api.rs

1// Copyright © 2020 Alexandra Frydl
2//
3// This Source Code Form is subject to the terms of the Mozilla Public
4// License, v. 2.0. If a copy of the MPL was not distributed with this
5// file, You can obtain one at http://mozilla.org/MPL/2.0/.
6
7//! A client for the Slack API.
8
9use af_core::prelude::*;
10
11/// The URL of the Slack API.
12const URL: &str = "https://slack.com/api/";
13
14/// A Slack API client.
15pub struct Client {
16  authorization: String,
17  http: reqwest::Client,
18}
19
20impl Client {
21  /// Creates a new client with a given OAuth token.
22  pub fn new(token: impl AsRef<str>) -> Self {
23    Self { authorization: format!("Bearer {}", token.as_ref()), http: default() }
24  }
25
26  /// Sends a `POST` request.
27  pub async fn get<Q, O>(&self, method: &str, query: &Q) -> Result<Result<O, ErrorResponse>>
28  where
29    Q: Serialize,
30    O: for<'a> Deserialize<'a>,
31  {
32    let req = self
33      .http
34      .get(&format!("{}{}?{}", URL, method, serde_qs::to_string(query).unwrap()))
35      .header("Authorization", &self.authorization);
36
37    let mut res: ResponseProps = req.send().await?.json().await?;
38    let ok = res.remove("ok").and_then(|v| v.as_bool()).unwrap_or_default();
39
40    Ok(match ok {
41      true => Ok(json::from_value(json::Value::Object(res))?),
42      false => Err(json::from_value(json::Value::Object(res))?),
43    })
44  }
45
46  /// Sends a `POST` request.
47  pub async fn post<I, O>(&self, method: &str, body: &I) -> Result<Result<O, ErrorResponse>>
48  where
49    I: Serialize,
50    O: for<'a> Deserialize<'a>,
51  {
52    let req = self
53      .http
54      .post(&format!("{}{}", URL, method))
55      .header("Authorization", &self.authorization)
56      .json(body);
57
58    let mut res: ResponseProps = req.send().await?.json().await?;
59    let ok = res.remove("ok").and_then(|v| v.as_bool()).unwrap_or_default();
60
61    Ok(match ok {
62      true => Ok(json::from_value(json::Value::Object(res))?),
63      false => Err(json::from_value(json::Value::Object(res))?),
64    })
65  }
66}
67
68/// A Slack API error.
69#[derive(Debug, Error)]
70pub enum Error {
71  /// The response body could not be deserialized.
72  #[error("invalid response body: {0}")]
73  InvalidResponse(#[from] json::Error),
74  /// The HTTP request failed.
75  #[error(transparent)]
76  RequestFailed(#[from] reqwest::Error),
77}
78
79/// The result of a Slack API call.
80pub type Result<T = (), E = Error> = std::result::Result<T, E>;
81
82/// Properties on a Slack API response.
83pub type ResponseProps = json::Map<String, json::Value>;
84
85/// An Slack API error response.
86#[derive(Debug, Deserialize, Serialize)]
87pub struct ErrorResponse {
88  /// A machine-readable string indicating what kind of error occurred.
89  pub error: String,
90  /// The remaining properties of the error.
91  #[serde(flatten)]
92  pub props: ResponseProps,
93}
94
95impl Display for ErrorResponse {
96  fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
97    let json = match f.alternate() {
98      true => json::to_string_pretty(self).unwrap(),
99      false => json::to_string(self).unwrap(),
100    };
101
102    Display::fmt(&json, f)
103  }
104}