Skip to content

Commit

Permalink
New Slack files upload API support (#276)
Browse files Browse the repository at this point in the history
* New Slack files upload API support

* Working version of upload

* Ver fix

* Comment update
  • Loading branch information
abdolence authored Jun 2, 2024
1 parent b03c088 commit 3b81a84
Show file tree
Hide file tree
Showing 6 changed files with 247 additions and 7 deletions.
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "slack-morphism"
version = "2.2.0"
version = "2.3.0-alpha.1"
authors = ["Abdulla Abdurakhmanov <[email protected]>"]
edition = "2021"
license = "Apache-2.0"
Expand Down
33 changes: 27 additions & 6 deletions examples/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -88,13 +88,34 @@ async fn test_file_upload() -> Result<(), Box<dyn std::error::Error + Send + Syn
let token: SlackApiToken = SlackApiToken::new(token_value);
let session = client.open_session(&token);

let file_upload_req = SlackApiFilesUploadRequest::new()
.with_channels(vec!["#random".into()])
.with_binary_content("test-content".into())
.with_filename("test.txt".into());
let test_content: String = "test-content".into();

let file_upload_resp = session.files_upload(&file_upload_req).await?;
println!("file upload resp: {:#?}", &file_upload_resp);
let get_upload_url_req =
SlackApiFilesGetUploadUrlExternalRequest::new("test.txt".into(), test_content.len());
let upload_url_resp = session.get_upload_url_external(&get_upload_url_req).await?;
println!("get url resp: {:#?}", &upload_url_resp);

let file_upload_req = SlackApiFilesUploadViaUrlRequest::new(
upload_url_resp.upload_url,
test_content.into(),
"text/plain".into(),
);

let file_upload_resp = session.files_upload_via_url(&file_upload_req).await?;
println!("file_upload_resp: {:#?}", &file_upload_resp);

let complete_file_upload_req =
SlackApiFilesCompleteUploadExternalRequest::new(vec![SlackApiFilesComplete::new(
upload_url_resp.file_id,
)]); // .with_channel_id("C.....".into());

let complete_file_upload_resp = session
.files_complete_upload_external(&complete_file_upload_req)
.await?;
println!(
"complete_file_upload_resp: {:#?}",
&complete_file_upload_resp
);

Ok(())
}
Expand Down
108 changes: 108 additions & 0 deletions src/api/files.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ use rvstruct::ValueStruct;
use serde::{Deserialize, Serialize, Serializer};
use serde_with::skip_serializing_none;

use crate::api::{
SlackApiUsersConversationsRequest, SlackApiUsersConversationsResponse,
SlackApiUsersProfileSetRequest, SlackApiUsersProfileSetResponse,
};
use crate::models::*;
use crate::multipart_form::FileMultipartData;
use crate::ratectl::*;
Expand All @@ -20,6 +24,9 @@ where
///
/// https://api.slack.com/methods/files.upload
///
#[deprecated(
note = "Deprecated by Slack. Use `getUploadURLExternal/files_upload_via_url/completeUploadExternal` instead."
)]
pub async fn files_upload(
&self,
req: &SlackApiFilesUploadRequest,
Expand Down Expand Up @@ -64,6 +71,57 @@ where
)
.await
}

///
/// https://api.slack.com/methods/files.getUploadURLExternal
///
pub async fn get_upload_url_external(
&self,
req: &SlackApiFilesGetUploadUrlExternalRequest,
) -> ClientResult<SlackApiFilesGetUploadUrlExternalResponse> {
self.http_session_api
.http_get(
"files.getUploadURLExternal",
&vec![
("filename", Some(&req.filename)),
("length", Some(&req.length.to_string())),
("alt_txt", req.alt_txt.as_ref()),
("snippet_type", req.snippet_type.as_ref().map(|v| v.value())),
],
Some(&SLACK_TIER4_METHOD_CONFIG),
)
.await
}

pub async fn files_upload_via_url(
&self,
req: &SlackApiFilesUploadViaUrlRequest,
) -> ClientResult<SlackApiFilesUploadViaUrlResponse> {
self.http_session_api
.http_post_uri_binary(
req.upload_url.value().clone(),
req.content_type.clone(),
&req.content,
Some(&SLACK_TIER4_METHOD_CONFIG),
)
.await
}

///
/// https://api.slack.com/methods/files.completeUploadExternal
///
pub async fn files_complete_upload_external(
&self,
req: &SlackApiFilesCompleteUploadExternalRequest,
) -> ClientResult<SlackApiFilesCompleteUploadExternalResponse> {
self.http_session_api
.http_post(
"files.completeUploadExternal",
req,
Some(&SLACK_TIER4_METHOD_CONFIG),
)
.await
}
}

#[skip_serializing_none]
Expand All @@ -87,6 +145,56 @@ pub struct SlackApiFilesUploadResponse {
pub file: SlackFile,
}

#[skip_serializing_none]
#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Builder)]
pub struct SlackApiFilesGetUploadUrlExternalRequest {
pub filename: String,
pub length: usize,
pub alt_txt: Option<String>,
pub snippet_type: Option<SlackFileSnippetType>,
}

#[skip_serializing_none]
#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Builder)]
pub struct SlackApiFilesGetUploadUrlExternalResponse {
pub upload_url: SlackFileUploadUrl,
pub file_id: SlackFileId,
}

#[skip_serializing_none]
#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Builder)]
pub struct SlackApiFilesUploadViaUrlRequest {
pub upload_url: SlackFileUploadUrl,
pub content: Vec<u8>,
pub content_type: String,
}

#[skip_serializing_none]
#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Builder)]
pub struct SlackApiFilesUploadViaUrlResponse {}

#[skip_serializing_none]
#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Builder)]
pub struct SlackApiFilesCompleteUploadExternalRequest {
pub files: Vec<SlackApiFilesComplete>,
pub channel_id: Option<SlackChannelId>,
pub initial_comment: Option<String>,
pub thread_ts: Option<SlackTs>,
}

#[skip_serializing_none]
#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Builder)]
pub struct SlackApiFilesCompleteUploadExternalResponse {
pub files: Vec<SlackApiFilesComplete>,
}

#[skip_serializing_none]
#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Builder)]
pub struct SlackApiFilesComplete {
pub id: SlackFileId,
pub title: Option<String>,
}

fn to_csv<S: Serializer>(x: &Option<Vec<SlackChannelId>>, s: S) -> Result<S::Ok, S::Error> {
match x {
None => s.serialize_none(),
Expand Down
60 changes: 60 additions & 0 deletions src/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,16 @@ pub trait SlackClientHttpConnector {
}
}

fn http_post_uri_binary<'a, 'p, RS>(
&'a self,
full_uri: Url,
content_type: String,
data: &'a [u8],
context: SlackClientApiCallContext<'a>,
) -> BoxFuture<'a, ClientResult<RS>>
where
RS: for<'de> serde::de::Deserialize<'de> + Send + 'a + Send + 'a;

fn create_method_uri_path(&self, method_relative_uri: &str) -> ClientResult<Url> {
Ok(SlackClientHttpApiUri::create_method_uri_path(method_relative_uri).parse()?)
}
Expand Down Expand Up @@ -378,4 +388,54 @@ where
.http_post_multipart_form(method_relative_uri, file, params, context)
.await
}

pub async fn http_post_uri_multipart_form<'p, RS, PT, TS>(
&self,
full_uri: Url,
file: Option<FileMultipartData<'p>>,
params: &'p PT,
rate_control_params: Option<&'a SlackApiMethodRateControlConfig>,
) -> ClientResult<RS>
where
RS: for<'de> serde::de::Deserialize<'de> + Send,
PT: std::iter::IntoIterator<Item = (&'p str, Option<TS>)> + Clone,
TS: AsRef<str> + 'p + Send,
{
let context = SlackClientApiCallContext {
rate_control_params,
token: Some(self.token),
tracing_span: &self.span,
is_sensitive_url: false,
};

self.client
.http_api
.connector
.http_post_uri_multipart_form(full_uri, file, params, context)
.await
}

pub async fn http_post_uri_binary<'p, RS>(
&self,
full_uri: Url,
content_type: String,
data: &'a [u8],
rate_control_params: Option<&'a SlackApiMethodRateControlConfig>,
) -> ClientResult<RS>
where
RS: for<'de> serde::de::Deserialize<'de> + Send,
{
let context = SlackClientApiCallContext {
rate_control_params,
token: Some(self.token),
tracing_span: &self.span,
is_sensitive_url: true,
};

self.client
.http_api
.connector
.http_post_uri_binary(full_uri, content_type, data, context)
.await
}
}
44 changes: 44 additions & 0 deletions src/hyper_tokio/connector.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,12 @@ use crate::hyper_tokio::multipart_form::{
use crate::multipart_form::FileMultipartData;
use crate::prelude::hyper_ext::HyperExtensions;
use crate::ratectl::SlackApiRateControlConfig;
use bytes::BytesMut;
use std::hash::Hash;
use std::hash::Hasher;
use std::sync::Arc;
use std::time::Duration;

use tracing::*;
use url::Url;

Expand Down Expand Up @@ -448,4 +450,46 @@ impl<H: 'static + Send + Sync + Clone + connect::Connect> SlackClientHttpConnect
Err(err) => futures::future::err(err.into()).boxed(),
}
}

fn http_post_uri_binary<'a, 'p, RS>(
&'a self,
full_uri: Url,
content_type: String,
data: &'a [u8],
context: SlackClientApiCallContext<'a>,
) -> BoxFuture<'a, ClientResult<RS>>
where
RS: for<'de> serde::de::Deserialize<'de> + Send + 'a + Send + 'a,
{
let context_token = context.token;
let body_bytes = BytesMut::from(data).freeze();

async move {
let response_body = self
.send_rate_controlled_request(
move || {
let http_body = Full::new(body_bytes.clone()).boxed();
let http_base_request = HyperExtensions::create_http_request(
full_uri.clone(),
hyper::http::Method::POST,
)
.header("content-type", content_type.as_str());

let http_request = HyperExtensions::setup_token_auth_header(
http_base_request,
context_token,
);

http_request.body(http_body).map_err(|e| e.into())
},
context,
None,
0,
)
.await?;

Ok(response_body)
}
.boxed()
}
}
7 changes: 7 additions & 0 deletions src/models/files/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ use serde_with::skip_serializing_none;
use url::Url;

pub mod comments;

pub use comments::*;

#[derive(Debug, Eq, PartialEq, Hash, Clone, Serialize, Deserialize, ValueStruct)]
Expand All @@ -21,6 +22,12 @@ pub struct SlackFilePrettyType(pub String);
#[derive(Debug, Eq, PartialEq, Hash, Clone, Serialize, Deserialize, ValueStruct)]
pub struct SlackFileExternalType(pub String);

#[derive(Debug, Eq, PartialEq, Hash, Clone, Serialize, Deserialize, ValueStruct)]
pub struct SlackFileSnippetType(pub String);

#[derive(Debug, Eq, PartialEq, Hash, Clone, Serialize, Deserialize, ValueStruct)]
pub struct SlackFileUploadUrl(pub Url);

#[skip_serializing_none]
#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Builder)]
pub struct SlackFile {
Expand Down

0 comments on commit 3b81a84

Please sign in to comment.