mirror of
https://github.com/System-End/slack-morphism-rust.git
synced 2026-04-19 22:05:15 +00:00
Extracted a separate modules in lib: client, token
This commit is contained in:
parent
8b0e9dffb7
commit
efb10dcfc6
6 changed files with 442 additions and 422 deletions
|
|
@ -6,7 +6,7 @@ use rsb_derive::Builder;
|
|||
use serde::{Deserialize, Serialize};
|
||||
use serde_with::skip_serializing_none;
|
||||
|
||||
use crate::{ClientResult, SlackClient, SlackClientHttpApi};
|
||||
use crate::client::*;
|
||||
use hyper::Body;
|
||||
use slack_morphism_models::*;
|
||||
|
||||
|
|
|
|||
|
|
@ -68,7 +68,7 @@ impl<'a> SlackClientSession<'a> {
|
|||
self.http_api
|
||||
.http_get(
|
||||
"users.identity",
|
||||
&crate::SLACK_HTTP_EMPTY_GET_PARAMS.clone(),
|
||||
&crate::client::SLACK_HTTP_EMPTY_GET_PARAMS.clone(),
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
|
|
|||
418
src/client/src/client.rs
Normal file
418
src/client/src/client.rs
Normal file
|
|
@ -0,0 +1,418 @@
|
|||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::errors;
|
||||
use crate::listener::*;
|
||||
use crate::token::*;
|
||||
|
||||
use bytes::buf::BufExt as _;
|
||||
use futures::future::TryFutureExt;
|
||||
use hyper::body::HttpBody;
|
||||
use hyper::client::*;
|
||||
use hyper::http::StatusCode;
|
||||
use hyper::{Body, Request, Response, Uri};
|
||||
use hyper_tls::HttpsConnector;
|
||||
use lazy_static::*;
|
||||
use mime::Mime;
|
||||
use rvstruct::ValueStruct;
|
||||
use std::collections::HashMap;
|
||||
use std::io::Read;
|
||||
use url::Url;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct SlackClient {
|
||||
pub http_api: SlackClientHttpApi,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct SlackClientHttpApi {
|
||||
connector: Client<HttpsConnector<HttpConnector>>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct SlackClientSession<'a> {
|
||||
pub http_api: SlackClientHttpSessionApi<'a>,
|
||||
client: &'a SlackClient,
|
||||
token: &'a SlackApiToken,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct SlackClientHttpSessionApi<'a> {
|
||||
client: &'a SlackClient,
|
||||
token: &'a SlackApiToken,
|
||||
}
|
||||
|
||||
pub type ClientResult<T> = std::result::Result<T, Box<dyn std::error::Error + Send + Sync>>;
|
||||
|
||||
#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
|
||||
struct SlackEnvelopeMessage {
|
||||
ok: bool,
|
||||
error: Option<String>,
|
||||
warnings: Option<Vec<String>>,
|
||||
}
|
||||
|
||||
lazy_static! {
|
||||
pub static ref SLACK_HTTP_EMPTY_GET_PARAMS: Vec<(&'static str, Option<&'static String>)> =
|
||||
vec![];
|
||||
}
|
||||
|
||||
impl SlackClientHttpApi {
|
||||
const SLACK_API_URI_STR: &'static str = "https://slack.com/api";
|
||||
|
||||
fn new() -> Self {
|
||||
let https_connector = HttpsConnector::new();
|
||||
Self {
|
||||
connector: Client::builder().build(https_connector),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn create_method_uri_path(method_relative_uri: &str) -> String {
|
||||
format!("{}/{}", Self::SLACK_API_URI_STR, method_relative_uri)
|
||||
}
|
||||
|
||||
pub(crate) fn create_url(url_str: &String) -> Uri {
|
||||
url_str.parse().unwrap()
|
||||
}
|
||||
|
||||
pub(crate) fn create_url_with_params<'p, PT, TS>(url_str: &str, params: &'p PT) -> Uri
|
||||
where
|
||||
PT: std::iter::IntoIterator<Item = (&'p str, Option<&'p TS>)> + Clone,
|
||||
TS: std::string::ToString + 'p,
|
||||
{
|
||||
let url_query_params: Vec<(String, String)> = params
|
||||
.clone()
|
||||
.into_iter()
|
||||
.map(|(k, vo)| vo.map(|v| (k.to_string(), v.to_string())))
|
||||
.flatten()
|
||||
.collect();
|
||||
|
||||
Url::parse_with_params(url_str, url_query_params)
|
||||
.unwrap()
|
||||
.as_str()
|
||||
.parse()
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
pub fn parse_query_params(request: &Request<Body>) -> HashMap<String, String> {
|
||||
request
|
||||
.uri()
|
||||
.query()
|
||||
.map(|v| {
|
||||
url::form_urlencoded::parse(v.as_bytes())
|
||||
.into_owned()
|
||||
.collect()
|
||||
})
|
||||
.unwrap_or_else(HashMap::new)
|
||||
}
|
||||
|
||||
pub fn hyper_redirect_to(
|
||||
url: &str,
|
||||
) -> Result<Response<Body>, Box<dyn std::error::Error + Send + Sync>> {
|
||||
Response::builder()
|
||||
.status(hyper::http::StatusCode::FOUND)
|
||||
.header(hyper::header::LOCATION, url)
|
||||
.body(Body::empty())
|
||||
.map_err(|e| e.into())
|
||||
}
|
||||
|
||||
fn setup_token_auth_header(
|
||||
request_builder: hyper::http::request::Builder,
|
||||
token: Option<&SlackApiToken>,
|
||||
) -> hyper::http::request::Builder {
|
||||
if token.is_none() {
|
||||
request_builder
|
||||
} else {
|
||||
let token_header_value = format!("Bearer {}", token.unwrap().token_value.value());
|
||||
request_builder.header(hyper::header::AUTHORIZATION, token_header_value)
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn setup_basic_auth_header(
|
||||
request_builder: hyper::http::request::Builder,
|
||||
username: &String,
|
||||
password: &String,
|
||||
) -> hyper::http::request::Builder {
|
||||
let header_value = format!(
|
||||
"Basic {}",
|
||||
base64::encode(format!("{}:{}", username, password))
|
||||
);
|
||||
request_builder.header(hyper::header::AUTHORIZATION, header_value)
|
||||
}
|
||||
|
||||
pub(crate) fn create_http_request(
|
||||
uri: Uri,
|
||||
method: hyper::http::Method,
|
||||
) -> hyper::http::request::Builder {
|
||||
hyper::http::request::Builder::new()
|
||||
.method(method)
|
||||
.uri(uri)
|
||||
.header("accept-charset", "utf-8")
|
||||
}
|
||||
|
||||
async fn http_body_to_string<T>(body: T) -> ClientResult<String>
|
||||
where
|
||||
T: HttpBody,
|
||||
T::Error: std::error::Error + Sync + Send + 'static,
|
||||
{
|
||||
let http_body = hyper::body::aggregate(body).await?;
|
||||
let mut http_reader = http_body.reader();
|
||||
let mut http_body_str = String::new();
|
||||
http_reader.read_to_string(&mut http_body_str)?;
|
||||
Ok(http_body_str)
|
||||
}
|
||||
|
||||
fn http_response_content_type<RS>(response: &Response<RS>) -> Option<Mime> {
|
||||
let http_headers = response.headers();
|
||||
http_headers.get(hyper::header::CONTENT_TYPE).map(|hv| {
|
||||
let hvs = hv.to_str().unwrap();
|
||||
hvs.parse::<Mime>().unwrap()
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) async fn decode_signed_response(
|
||||
req: Request<Body>,
|
||||
signature_verifier: &SlackEventSignatureVerifier,
|
||||
) -> ClientResult<String> {
|
||||
let headers = &req.headers().clone();
|
||||
let req_body = req.into_body();
|
||||
match (
|
||||
headers.get(SlackEventSignatureVerifier::SLACK_SIGNED_HASH_HEADER),
|
||||
headers.get(SlackEventSignatureVerifier::SLACK_SIGNED_TIMESTAMP),
|
||||
) {
|
||||
(Some(received_hash), Some(received_ts)) => {
|
||||
Self::http_body_to_string(req_body)
|
||||
.and_then(|body| async {
|
||||
signature_verifier
|
||||
.verify(
|
||||
&received_hash.to_str().unwrap(),
|
||||
&body,
|
||||
&received_ts.to_str().unwrap(),
|
||||
)
|
||||
.map(|_| body)
|
||||
.map_err(|e| e.into())
|
||||
})
|
||||
.await
|
||||
}
|
||||
_ => Err(Box::new(SlackEventAbsentSignatureError::new())),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) async fn send_webapi_request<RS>(&self, request: Request<Body>) -> ClientResult<RS>
|
||||
where
|
||||
RS: for<'de> serde::de::Deserialize<'de>,
|
||||
{
|
||||
let http_res = self.connector.request(request).await?;
|
||||
let http_status = http_res.status();
|
||||
let http_content_type = Self::http_response_content_type(&http_res);
|
||||
let http_body_str = Self::http_body_to_string(http_res).await?;
|
||||
|
||||
match http_status {
|
||||
StatusCode::OK
|
||||
if http_content_type.iter().all(|response_mime| {
|
||||
response_mime.type_() == mime::APPLICATION
|
||||
&& response_mime.subtype() == mime::JSON
|
||||
}) =>
|
||||
{
|
||||
let slack_message: SlackEnvelopeMessage =
|
||||
serde_json::from_str(http_body_str.as_str())?;
|
||||
if slack_message.error.is_none() {
|
||||
let decoded_body = serde_json::from_str(http_body_str.as_str())?;
|
||||
Ok(decoded_body)
|
||||
} else {
|
||||
Err(errors::SlackClientError::ApiError(
|
||||
errors::SlackClientApiError::new(slack_message.error.unwrap())
|
||||
.opt_warnings(slack_message.warnings)
|
||||
.with_http_response_body(http_body_str),
|
||||
)
|
||||
.into())
|
||||
}
|
||||
}
|
||||
StatusCode::OK => serde_json::from_str("{}").map_err(|e| e.into()),
|
||||
_ => Err(errors::SlackClientError::HttpError(
|
||||
errors::SlackClientHttpError::new(http_status)
|
||||
.with_http_response_body(http_body_str),
|
||||
)
|
||||
.into()),
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn http_get_uri<'a, RS>(
|
||||
&self,
|
||||
full_uri: Uri,
|
||||
token: Option<&'a SlackApiToken>,
|
||||
) -> ClientResult<RS>
|
||||
where
|
||||
RS: for<'de> serde::de::Deserialize<'de>,
|
||||
{
|
||||
let base_http_request = Self::create_http_request(full_uri, hyper::http::Method::GET);
|
||||
|
||||
let http_request = Self::setup_token_auth_header(base_http_request, token);
|
||||
|
||||
let body = self
|
||||
.send_webapi_request(http_request.body(Body::empty())?)
|
||||
.await?;
|
||||
|
||||
Ok(body)
|
||||
}
|
||||
|
||||
async fn http_get_token<'a, 'p, RS, PT, TS>(
|
||||
&self,
|
||||
method_relative_uri: &str,
|
||||
params: &'p PT,
|
||||
token: Option<&'a SlackApiToken>,
|
||||
) -> ClientResult<RS>
|
||||
where
|
||||
RS: for<'de> serde::de::Deserialize<'de>,
|
||||
PT: std::iter::IntoIterator<Item = (&'p str, Option<&'p TS>)> + Clone,
|
||||
TS: std::string::ToString + 'p,
|
||||
{
|
||||
let full_uri = Self::create_url_with_params(
|
||||
&Self::create_method_uri_path(&method_relative_uri),
|
||||
params,
|
||||
);
|
||||
|
||||
self.http_get_uri(full_uri, token).await
|
||||
}
|
||||
|
||||
pub async fn http_get<'p, RS, PT, TS>(
|
||||
&self,
|
||||
method_relative_uri: &str,
|
||||
params: &'p PT,
|
||||
) -> ClientResult<RS>
|
||||
where
|
||||
RS: for<'de> serde::de::Deserialize<'de>,
|
||||
PT: std::iter::IntoIterator<Item = (&'p str, Option<&'p TS>)> + Clone,
|
||||
TS: std::string::ToString + 'p,
|
||||
{
|
||||
self.http_get_token(&method_relative_uri, params, None)
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn http_post_uri<'a, RQ, RS>(
|
||||
&self,
|
||||
full_uri: Uri,
|
||||
request_body: &RQ,
|
||||
token: Option<&'a SlackApiToken>,
|
||||
) -> ClientResult<RS>
|
||||
where
|
||||
RQ: serde::ser::Serialize,
|
||||
RS: for<'de> serde::de::Deserialize<'de>,
|
||||
{
|
||||
let post_json = serde_json::to_string(&request_body)?;
|
||||
|
||||
let base_http_request = Self::create_http_request(full_uri, hyper::http::Method::POST)
|
||||
.header("content-type", "application/json; charset=utf-8");
|
||||
|
||||
let http_request = Self::setup_token_auth_header(base_http_request, token);
|
||||
|
||||
let response_body = self
|
||||
.send_webapi_request(http_request.body(post_json.into())?)
|
||||
.await?;
|
||||
|
||||
Ok(response_body)
|
||||
}
|
||||
|
||||
async fn http_post_token<'a, RQ, RS>(
|
||||
&self,
|
||||
method_relative_uri: &str,
|
||||
request: &RQ,
|
||||
token: Option<&'a SlackApiToken>,
|
||||
) -> ClientResult<RS>
|
||||
where
|
||||
RQ: serde::ser::Serialize,
|
||||
RS: for<'de> serde::de::Deserialize<'de>,
|
||||
{
|
||||
let full_uri = Self::create_url(&SlackClientHttpApi::create_method_uri_path(
|
||||
&method_relative_uri,
|
||||
));
|
||||
|
||||
self.http_post_uri(full_uri, &request, token).await
|
||||
}
|
||||
|
||||
pub async fn http_post<RQ, RS>(
|
||||
&self,
|
||||
method_relative_uri: &str,
|
||||
request: &RQ,
|
||||
) -> ClientResult<RS>
|
||||
where
|
||||
RQ: serde::ser::Serialize,
|
||||
RS: for<'de> serde::de::Deserialize<'de>,
|
||||
{
|
||||
self.http_post_token(method_relative_uri, &request, None)
|
||||
.await
|
||||
}
|
||||
}
|
||||
|
||||
impl SlackClient {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
http_api: SlackClientHttpApi::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn open_session<'a>(&'a self, token: &'a SlackApiToken) -> SlackClientSession<'a> {
|
||||
let http_session_api = SlackClientHttpSessionApi {
|
||||
client: self,
|
||||
token,
|
||||
};
|
||||
|
||||
SlackClientSession {
|
||||
client: self,
|
||||
token,
|
||||
http_api: http_session_api,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> SlackClientHttpSessionApi<'a> {
|
||||
pub async fn http_get_uri<RS, PT, TS>(&self, full_uri: Uri) -> ClientResult<RS>
|
||||
where
|
||||
RS: for<'de> serde::de::Deserialize<'de>,
|
||||
{
|
||||
self.client
|
||||
.http_api
|
||||
.http_get_uri(full_uri, Some(&self.token))
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn http_get<'p, RS, PT, TS>(
|
||||
&self,
|
||||
method_relative_uri: &str,
|
||||
params: &'p PT,
|
||||
) -> ClientResult<RS>
|
||||
where
|
||||
RS: for<'de> serde::de::Deserialize<'de>,
|
||||
PT: std::iter::IntoIterator<Item = (&'p str, Option<&'p TS>)> + Clone,
|
||||
TS: std::string::ToString + 'p,
|
||||
{
|
||||
self.client
|
||||
.http_api
|
||||
.http_get_token(&method_relative_uri, params, Some(&self.token))
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn http_post<RQ, RS>(
|
||||
&self,
|
||||
method_relative_uri: &str,
|
||||
request: &RQ,
|
||||
) -> ClientResult<RS>
|
||||
where
|
||||
RQ: serde::ser::Serialize,
|
||||
RS: for<'de> serde::de::Deserialize<'de>,
|
||||
{
|
||||
self.client
|
||||
.http_api
|
||||
.http_post_token(&method_relative_uri, &request, Some(&self.token))
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn http_post_uri<RQ, RS>(&self, full_uri: Uri, request: &RQ) -> ClientResult<RS>
|
||||
where
|
||||
RQ: serde::ser::Serialize,
|
||||
RS: for<'de> serde::de::Deserialize<'de>,
|
||||
{
|
||||
self.client
|
||||
.http_api
|
||||
.http_post_uri(full_uri, &request, Some(&self.token))
|
||||
.await
|
||||
}
|
||||
}
|
||||
|
|
@ -1,425 +1,10 @@
|
|||
pub mod api;
|
||||
mod client;
|
||||
pub mod errors;
|
||||
pub mod listener;
|
||||
mod scroller;
|
||||
mod token;
|
||||
|
||||
pub use client::*;
|
||||
pub use scroller::*;
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::listener::{SlackEventAbsentSignatureError, SlackEventSignatureVerifier};
|
||||
use bytes::buf::BufExt as _;
|
||||
use futures::future::TryFutureExt;
|
||||
use hyper::body::HttpBody;
|
||||
use hyper::client::*;
|
||||
use hyper::http::StatusCode;
|
||||
use hyper::{Body, Request, Response, Uri};
|
||||
use hyper_tls::HttpsConnector;
|
||||
use lazy_static::*;
|
||||
use mime::Mime;
|
||||
use rsb_derive::Builder;
|
||||
use std::collections::HashMap;
|
||||
use std::io::Read;
|
||||
use url::Url;
|
||||
|
||||
#[derive(Debug, PartialEq, Clone, Builder)]
|
||||
pub struct SlackApiToken {
|
||||
value: String,
|
||||
workspace_id: Option<String>,
|
||||
scope: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct SlackClient {
|
||||
pub http_api: SlackClientHttpApi,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct SlackClientHttpApi {
|
||||
connector: Client<HttpsConnector<HttpConnector>>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct SlackClientSession<'a> {
|
||||
pub http_api: SlackClientHttpSessionApi<'a>,
|
||||
client: &'a SlackClient,
|
||||
token: &'a SlackApiToken,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct SlackClientHttpSessionApi<'a> {
|
||||
client: &'a SlackClient,
|
||||
token: &'a SlackApiToken,
|
||||
}
|
||||
|
||||
pub type ClientResult<T> = std::result::Result<T, Box<dyn std::error::Error + Send + Sync>>;
|
||||
|
||||
#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
|
||||
struct SlackEnvelopeMessage {
|
||||
ok: bool,
|
||||
error: Option<String>,
|
||||
warnings: Option<Vec<String>>,
|
||||
}
|
||||
|
||||
lazy_static! {
|
||||
static ref SLACK_HTTP_EMPTY_GET_PARAMS: Vec<(&'static str, Option<&'static String>)> = vec![];
|
||||
}
|
||||
|
||||
impl SlackClientHttpApi {
|
||||
const SLACK_API_URI_STR: &'static str = "https://slack.com/api";
|
||||
|
||||
fn new() -> Self {
|
||||
let https_connector = HttpsConnector::new();
|
||||
Self {
|
||||
connector: Client::builder().build(https_connector),
|
||||
}
|
||||
}
|
||||
|
||||
fn create_method_uri_path(method_relative_uri: &str) -> String {
|
||||
format!("{}/{}", Self::SLACK_API_URI_STR, method_relative_uri)
|
||||
}
|
||||
|
||||
fn create_url(url_str: &String) -> Uri {
|
||||
url_str.parse().unwrap()
|
||||
}
|
||||
|
||||
fn create_url_with_params<'p, PT, TS>(url_str: &str, params: &'p PT) -> Uri
|
||||
where
|
||||
PT: std::iter::IntoIterator<Item = (&'p str, Option<&'p TS>)> + Clone,
|
||||
TS: std::string::ToString + 'p,
|
||||
{
|
||||
let url_query_params: Vec<(String, String)> = params
|
||||
.clone()
|
||||
.into_iter()
|
||||
.map(|(k, vo)| vo.map(|v| (k.to_string(), v.to_string())))
|
||||
.flatten()
|
||||
.collect();
|
||||
|
||||
Url::parse_with_params(url_str, url_query_params)
|
||||
.unwrap()
|
||||
.as_str()
|
||||
.parse()
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
pub fn parse_query_params(request: &Request<Body>) -> HashMap<String, String> {
|
||||
request
|
||||
.uri()
|
||||
.query()
|
||||
.map(|v| {
|
||||
url::form_urlencoded::parse(v.as_bytes())
|
||||
.into_owned()
|
||||
.collect()
|
||||
})
|
||||
.unwrap_or_else(HashMap::new)
|
||||
}
|
||||
|
||||
pub fn hyper_redirect_to(
|
||||
url: &str,
|
||||
) -> Result<Response<Body>, Box<dyn std::error::Error + Send + Sync>> {
|
||||
Response::builder()
|
||||
.status(hyper::http::StatusCode::FOUND)
|
||||
.header(hyper::header::LOCATION, url)
|
||||
.body(Body::empty())
|
||||
.map_err(|e| e.into())
|
||||
}
|
||||
|
||||
fn setup_token_auth_header(
|
||||
request_builder: hyper::http::request::Builder,
|
||||
token: Option<&SlackApiToken>,
|
||||
) -> hyper::http::request::Builder {
|
||||
if token.is_none() {
|
||||
request_builder
|
||||
} else {
|
||||
let token_header_value = format!("Bearer {}", token.unwrap().value);
|
||||
request_builder.header(hyper::header::AUTHORIZATION, token_header_value)
|
||||
}
|
||||
}
|
||||
|
||||
fn setup_basic_auth_header(
|
||||
request_builder: hyper::http::request::Builder,
|
||||
username: &String,
|
||||
password: &String,
|
||||
) -> hyper::http::request::Builder {
|
||||
let header_value = format!(
|
||||
"Basic {}",
|
||||
base64::encode(format!("{}:{}", username, password))
|
||||
);
|
||||
request_builder.header(hyper::header::AUTHORIZATION, header_value)
|
||||
}
|
||||
|
||||
fn create_http_request(uri: Uri, method: hyper::http::Method) -> hyper::http::request::Builder {
|
||||
hyper::http::request::Builder::new()
|
||||
.method(method)
|
||||
.uri(uri)
|
||||
.header("accept-charset", "utf-8")
|
||||
}
|
||||
|
||||
async fn http_body_to_string<T>(body: T) -> ClientResult<String>
|
||||
where
|
||||
T: HttpBody,
|
||||
T::Error: std::error::Error + Sync + Send + 'static,
|
||||
{
|
||||
let http_body = hyper::body::aggregate(body).await?;
|
||||
let mut http_reader = http_body.reader();
|
||||
let mut http_body_str = String::new();
|
||||
http_reader.read_to_string(&mut http_body_str)?;
|
||||
Ok(http_body_str)
|
||||
}
|
||||
|
||||
fn http_response_content_type<RS>(response: &Response<RS>) -> Option<Mime> {
|
||||
let http_headers = response.headers();
|
||||
http_headers.get(hyper::header::CONTENT_TYPE).map(|hv| {
|
||||
let hvs = hv.to_str().unwrap();
|
||||
hvs.parse::<Mime>().unwrap()
|
||||
})
|
||||
}
|
||||
|
||||
async fn decode_signed_response(
|
||||
req: Request<Body>,
|
||||
signature_verifier: &SlackEventSignatureVerifier,
|
||||
) -> ClientResult<String> {
|
||||
let headers = &req.headers().clone();
|
||||
let req_body = req.into_body();
|
||||
match (
|
||||
headers.get(SlackEventSignatureVerifier::SLACK_SIGNED_HASH_HEADER),
|
||||
headers.get(SlackEventSignatureVerifier::SLACK_SIGNED_TIMESTAMP),
|
||||
) {
|
||||
(Some(received_hash), Some(received_ts)) => {
|
||||
Self::http_body_to_string(req_body)
|
||||
.and_then(|body| async {
|
||||
signature_verifier
|
||||
.verify(
|
||||
&received_hash.to_str().unwrap(),
|
||||
&body,
|
||||
&received_ts.to_str().unwrap(),
|
||||
)
|
||||
.map(|_| body)
|
||||
.map_err(|e| e.into())
|
||||
})
|
||||
.await
|
||||
}
|
||||
_ => Err(Box::new(SlackEventAbsentSignatureError::new())),
|
||||
}
|
||||
}
|
||||
|
||||
async fn send_webapi_request<RS>(&self, request: Request<Body>) -> ClientResult<RS>
|
||||
where
|
||||
RS: for<'de> serde::de::Deserialize<'de>,
|
||||
{
|
||||
let http_res = self.connector.request(request).await?;
|
||||
let http_status = http_res.status();
|
||||
let http_content_type = Self::http_response_content_type(&http_res);
|
||||
let http_body_str = Self::http_body_to_string(http_res).await?;
|
||||
|
||||
match http_status {
|
||||
StatusCode::OK
|
||||
if http_content_type.iter().all(|response_mime| {
|
||||
response_mime.type_() == mime::APPLICATION
|
||||
&& response_mime.subtype() == mime::JSON
|
||||
}) =>
|
||||
{
|
||||
let slack_message: SlackEnvelopeMessage =
|
||||
serde_json::from_str(http_body_str.as_str())?;
|
||||
if slack_message.error.is_none() {
|
||||
let decoded_body = serde_json::from_str(http_body_str.as_str())?;
|
||||
Ok(decoded_body)
|
||||
} else {
|
||||
Err(errors::SlackClientError::ApiError(
|
||||
errors::SlackClientApiError::new(slack_message.error.unwrap())
|
||||
.opt_warnings(slack_message.warnings)
|
||||
.with_http_response_body(http_body_str),
|
||||
)
|
||||
.into())
|
||||
}
|
||||
}
|
||||
StatusCode::OK => serde_json::from_str("{}").map_err(|e| e.into()),
|
||||
_ => Err(errors::SlackClientError::HttpError(
|
||||
errors::SlackClientHttpError::new(http_status)
|
||||
.with_http_response_body(http_body_str),
|
||||
)
|
||||
.into()),
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn http_get_uri<'a, RS>(
|
||||
&self,
|
||||
full_uri: Uri,
|
||||
token: Option<&'a SlackApiToken>,
|
||||
) -> ClientResult<RS>
|
||||
where
|
||||
RS: for<'de> serde::de::Deserialize<'de>,
|
||||
{
|
||||
let base_http_request = Self::create_http_request(full_uri, hyper::http::Method::GET);
|
||||
|
||||
let http_request = Self::setup_token_auth_header(base_http_request, token);
|
||||
|
||||
let body = self
|
||||
.send_webapi_request(http_request.body(Body::empty())?)
|
||||
.await?;
|
||||
|
||||
Ok(body)
|
||||
}
|
||||
|
||||
async fn http_get_token<'a, 'p, RS, PT, TS>(
|
||||
&self,
|
||||
method_relative_uri: &str,
|
||||
params: &'p PT,
|
||||
token: Option<&'a SlackApiToken>,
|
||||
) -> ClientResult<RS>
|
||||
where
|
||||
RS: for<'de> serde::de::Deserialize<'de>,
|
||||
PT: std::iter::IntoIterator<Item = (&'p str, Option<&'p TS>)> + Clone,
|
||||
TS: std::string::ToString + 'p,
|
||||
{
|
||||
let full_uri = Self::create_url_with_params(
|
||||
&Self::create_method_uri_path(&method_relative_uri),
|
||||
params,
|
||||
);
|
||||
|
||||
self.http_get_uri(full_uri, token).await
|
||||
}
|
||||
|
||||
pub async fn http_get<'p, RS, PT, TS>(
|
||||
&self,
|
||||
method_relative_uri: &str,
|
||||
params: &'p PT,
|
||||
) -> ClientResult<RS>
|
||||
where
|
||||
RS: for<'de> serde::de::Deserialize<'de>,
|
||||
PT: std::iter::IntoIterator<Item = (&'p str, Option<&'p TS>)> + Clone,
|
||||
TS: std::string::ToString + 'p,
|
||||
{
|
||||
self.http_get_token(&method_relative_uri, params, None)
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn http_post_uri<'a, RQ, RS>(
|
||||
&self,
|
||||
full_uri: Uri,
|
||||
request_body: &RQ,
|
||||
token: Option<&'a SlackApiToken>,
|
||||
) -> ClientResult<RS>
|
||||
where
|
||||
RQ: serde::ser::Serialize,
|
||||
RS: for<'de> serde::de::Deserialize<'de>,
|
||||
{
|
||||
let post_json = serde_json::to_string(&request_body)?;
|
||||
|
||||
let base_http_request = Self::create_http_request(full_uri, hyper::http::Method::POST)
|
||||
.header("content-type", "application/json; charset=utf-8");
|
||||
|
||||
let http_request = Self::setup_token_auth_header(base_http_request, token);
|
||||
|
||||
let response_body = self
|
||||
.send_webapi_request(http_request.body(post_json.into())?)
|
||||
.await?;
|
||||
|
||||
Ok(response_body)
|
||||
}
|
||||
|
||||
async fn http_post_token<'a, RQ, RS>(
|
||||
&self,
|
||||
method_relative_uri: &str,
|
||||
request: &RQ,
|
||||
token: Option<&'a SlackApiToken>,
|
||||
) -> ClientResult<RS>
|
||||
where
|
||||
RQ: serde::ser::Serialize,
|
||||
RS: for<'de> serde::de::Deserialize<'de>,
|
||||
{
|
||||
let full_uri = Self::create_url(&SlackClientHttpApi::create_method_uri_path(
|
||||
&method_relative_uri,
|
||||
));
|
||||
|
||||
self.http_post_uri(full_uri, &request, token).await
|
||||
}
|
||||
|
||||
pub async fn http_post<RQ, RS>(
|
||||
&self,
|
||||
method_relative_uri: &str,
|
||||
request: &RQ,
|
||||
) -> ClientResult<RS>
|
||||
where
|
||||
RQ: serde::ser::Serialize,
|
||||
RS: for<'de> serde::de::Deserialize<'de>,
|
||||
{
|
||||
self.http_post_token(method_relative_uri, &request, None)
|
||||
.await
|
||||
}
|
||||
}
|
||||
|
||||
impl SlackClient {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
http_api: SlackClientHttpApi::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn open_session<'a>(&'a self, token: &'a SlackApiToken) -> SlackClientSession<'a> {
|
||||
let http_session_api = SlackClientHttpSessionApi {
|
||||
client: self,
|
||||
token,
|
||||
};
|
||||
|
||||
SlackClientSession {
|
||||
client: self,
|
||||
token,
|
||||
http_api: http_session_api,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> SlackClientHttpSessionApi<'a> {
|
||||
pub async fn http_get_uri<RS, PT, TS>(&self, full_uri: Uri) -> ClientResult<RS>
|
||||
where
|
||||
RS: for<'de> serde::de::Deserialize<'de>,
|
||||
{
|
||||
self.client
|
||||
.http_api
|
||||
.http_get_uri(full_uri, Some(&self.token))
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn http_get<'p, RS, PT, TS>(
|
||||
&self,
|
||||
method_relative_uri: &str,
|
||||
params: &'p PT,
|
||||
) -> ClientResult<RS>
|
||||
where
|
||||
RS: for<'de> serde::de::Deserialize<'de>,
|
||||
PT: std::iter::IntoIterator<Item = (&'p str, Option<&'p TS>)> + Clone,
|
||||
TS: std::string::ToString + 'p,
|
||||
{
|
||||
self.client
|
||||
.http_api
|
||||
.http_get_token(&method_relative_uri, params, Some(&self.token))
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn http_post<RQ, RS>(
|
||||
&self,
|
||||
method_relative_uri: &str,
|
||||
request: &RQ,
|
||||
) -> ClientResult<RS>
|
||||
where
|
||||
RQ: serde::ser::Serialize,
|
||||
RS: for<'de> serde::de::Deserialize<'de>,
|
||||
{
|
||||
self.client
|
||||
.http_api
|
||||
.http_post_token(&method_relative_uri, &request, Some(&self.token))
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn http_post_uri<RQ, RS>(&self, full_uri: Uri, request: &RQ) -> ClientResult<RS>
|
||||
where
|
||||
RQ: serde::ser::Serialize,
|
||||
RS: for<'de> serde::de::Deserialize<'de>,
|
||||
{
|
||||
self.client
|
||||
.http_api
|
||||
.http_post_uri(full_uri, &request, Some(&self.token))
|
||||
.await
|
||||
}
|
||||
}
|
||||
pub use token::*;
|
||||
|
|
|
|||
17
src/client/src/token.rs
Normal file
17
src/client/src/token.rs
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
use rsb_derive::Builder;
|
||||
use rvstruct::ValueStruct;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use slack_morphism_models::SlackTeamId;
|
||||
|
||||
#[derive(Debug, Eq, PartialEq, Hash, Clone, Serialize, Deserialize, ValueStruct)]
|
||||
pub struct SlackApiTokenValue(pub String);
|
||||
|
||||
#[derive(Debug, Eq, PartialEq, Hash, Clone, Serialize, Deserialize, ValueStruct)]
|
||||
pub struct SlackApiTokenScope(pub String);
|
||||
|
||||
#[derive(Debug, PartialEq, Clone, Builder)]
|
||||
pub struct SlackApiToken {
|
||||
pub token_value: SlackApiTokenValue,
|
||||
pub team_id: Option<SlackTeamId>,
|
||||
pub scope: Option<SlackApiTokenScope>,
|
||||
}
|
||||
|
|
@ -19,7 +19,7 @@ use templates::*;
|
|||
#[allow(dead_code)]
|
||||
async fn test_client() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
||||
let client = SlackClient::new();
|
||||
let token_value: String = std::env::var("SLACK_TEST_TOKEN")?;
|
||||
let token_value: SlackApiTokenValue = std::env::var("SLACK_TEST_TOKEN")?.into();
|
||||
let token: SlackApiToken = SlackApiToken::new(token_value);
|
||||
let session = client.open_session(&token);
|
||||
println!("{:#?}", session);
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue