Intro and motivation

Type-safety

All of the models, API and Block Kit support in Slack Morphism are well-typed.

Easy to use

The library depends only on familiar for Rust developers principles and libraries like Serde, futures, hyper.

Async

Using latest Rust async/await language features and libraries, the library provides access to all of the functions in asynchronous manner.

Getting Started

Cargo.toml dependencies example:

[dependencies]
slack-morphism="0.3"
slack-morphism-models="0.3"

All imports you need:

use slack_morphism::prelude::*;

or more granularly:

use slack_morphism::*; // access to network/client functions
use slack_morphism::api::*; // Slack Web API methods (chat, users, views, etc)
use slack_morphism::listener::*; // Slack Events API listener (routes) implementation
use slack_morphism_models::*; // common Slack models like SlackUser, etc and macros
use slack_morphism_models::blocks::*; // Slack Block Kit models
use slack_morphism_models::events::*; // Slack Events Models

Slack Web API client

Create a client instance

use slack_morphism::*;
let client = SlackClient::new();

Make Web API methods calls

For most of Slack Web API methods (except for OAuth methods, Incoming Webhooks and event replies) you need a Slack token to make a call. For simple bots you can have it in your config files, or you can obtain workspace tokens using Slack OAuth.

In the example below, we’re using a hardcoded Slack token, but don’t do that for your production bots and apps. You should securely and properly store all of Slack tokens.


use slack_morphism::*;
use slack_morphism::api::*;
use slack_morphism_models::*;

async fn example() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {

    let client = SlackClient::new();
    
    // Create our Slack API token
    let token_value: SlackApiTokenValue = "xoxb-89.....".into();
    let token: SlackApiToken = SlackApiToken::new(token_value);
    
    // Create a Slack session with this token
    let session = client.open_session(&token);
    
    // Make your first API call (which is `api.test` here)
    let test: SlackApiTestResponse = session
            .api_test(&SlackApiTestRequest::new().with_foo("Test".into()))
            .await?;

    // Send a simple text message
    let post_chat_req =
        SlackApiChatPostMessageRequest::new("#general".into(),
               SlackMessageContent::new().with_text("Hey there!".into())
        );

    let post_chat_resp = session.chat_post_message(&post_chat_req).await?;

    Ok(())
}

Pagination support

Some Web API methods defines cursors and pagination, to give you an ability to load a lot of data continually (using batching and continually making many requests).

Examples: conversations.history, conversations.list, users.list, ...

To help with those methods Slack Morphism provides additional a “scroller” implementation, which deal with all scrolling/batching requests for you.

For example for users.list:


use slack_morphism::*;
use slack_morphism::api::*;
use slack_morphism_models::*;
use std::time::Duration;

async fn example() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {

    let client = SlackClient::new();
    let token_value: SlackApiTokenValue = "xoxb-89.....".into();
    let token: SlackApiToken = SlackApiToken::new(token_value);
    let session = client.open_session(&token);
    
    // Create a first request and specify a batch limit:
    let scroller_req: SlackApiUsersListRequest = SlackApiUsersListRequest::new().with_limit(5);
    
    // Create a scroller from this request
    let scroller = scroller_req.scroller();
    
    // Option 1: Create a Rust Futures Stream from this scroller and use it
    use futures::stream::BoxStream;
    use futures::TryStreamExt;
    
    let mut items_stream = scroller.to_items_stream(&session);
    while let Some(items) = items_stream.try_next().await? {
        println!("users batch: {:#?}", items);
    }
    
    // Option 2: Collect all of the data in a vector (which internally uses the same approach above)
    let collected_members: Vec<SlackUser> = scroller
        .collect_items_stream(&session, Duration::from_millis(1000))
        .await?;
    Ok(())
}

Block Kit support

To support Slack Block Kit rich messages and views, the library provides:

  • Well-typed models
  • Macros to help build blocks, block elements

Let’s take some very simple block example:

{
  "blocks": [
      {
        "type": "section",
        "text": {
            "type": "mrkdwn",
            "text": "A message *with some bold text* and _some italicized text_."
        }
      }
  ]
}

Now, let’s look at how it looks with type-safe code using Slack Morphism Blocks macro support:

use slack_morphism_models::*;
use slack_morphism_models::blocks::*;

let blocks : Vec<SlackBlock> = slack_blocks![
 some_into(
    SlackSectionBlock::new()
        .with_text(md!("A message *with some bold text* and _some italicized text_."))
 )
];

Let’s look at another more complex example for welcoming message:


use slack_morphism::*;
use slack_morphism::api::*;
use slack_morphism_models::*;
use slack_morphism_models::blocks::*;

async fn example() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {

    use std::time::Duration;
    use rsb_derive::Builder;

    let client = SlackClient::new();
    let token_value: SlackApiTokenValue = "xoxb-89.....".into();
    let token: SlackApiToken = SlackApiToken::new(token_value);
    let session = client.open_session(&token);

    // Our welcome message params as a struct
    #[derive(Debug, Clone, Builder)]
    pub struct WelcomeMessageTemplateParams {
        pub user_id: SlackUserId,
    }

    // Define our welcome message template using block macros, a trait and models from the library
    impl SlackMessageTemplate for WelcomeMessageTemplateParams {
        fn render_template(&self) -> SlackMessageContent {
            SlackMessageContent::new()
                .with_text(format!("Hey {}", self.user_id.to_slack_format()))
                .with_blocks(slack_blocks![
                    some_into(
                        SlackSectionBlock::new()
                            .with_text(md!("Hey {}", self.user_id.to_slack_format()))
                    ),
                    some_into(SlackDividerBlock::new()),
                    some_into(SlackImageBlock::new(
                        "https://www.gstatic.com/webp/gallery3/2_webp_ll.png".into(),
                        "Test Image".into()
                    )),
                    some_into(SlackActionsBlock::new(slack_blocks![some_into(
                        SlackBlockButtonElement::new(
                            "simple-message-button".into(),
                            pt!("Simple button text")
                        )
                    )]))
                ])
        }
    }

    // Use it
    let message = WelcomeMessageTemplateParams::new("some-slack-uid".into());

    let post_chat_req =
        SlackApiChatPostMessageRequest::new("#general".into(), message.render_template());

    Ok(())
}

Look other examples in examples/templates.rs.

Events API and OAuth

The library provides route implementation in SlackClientEventsListener based on Hyper/Tokio for:

  • Push Events
  • Interaction Events
  • Command Events
  • OAuth v2 redirects and client functions

You can chain all of the routes using chain_service_routes_fn from the library.

Hyper configuration

In order to use Events API/OAuth you need to configure Hyper HTTP server. There is nothing special about how to do that, and you can use the official hyper docs. This is just merely a quick example how to use it with Slack Morphism routes.

To create a server, you need hyper make_service_fn and service_fn.

Example


use slack_morphism::api::*;
use slack_morphism::listener::*;
use slack_morphism::*;
use slack_morphism_models::*;

// Hyper imports
use hyper::service::{make_service_fn, service_fn};
use hyper::{Body, Request, Response};

// For logging
use log::*;

async fn create_slack_events_listener_server() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {

    let addr = std::net::SocketAddr::from(([127, 0, 0, 1], 8080));
    info!("Loading server: {}", addr);

    // This is our default HTTP route when Slack routes didn't handle incoming request (different/other path).
    async fn your_others_routes(
        _req: Request<Body>,
    ) -> Result<Response<Body>, Box<dyn std::error::Error + Send + Sync>> {
        Response::builder()
            .body("Hey, this is a default users route handler".into())
            .map_err(|e| e.into())
    }
   
    // Our error handler for Slack Events API
    fn slack_listener_error_handler(err: Box<dyn std::error::Error + Send + Sync>, _client: Arc<SlackClient>) {
        error!("Slack Events error: {:#?}", err);
    }

    // We need also a client instance. `Arc` used here because we would like 
    // to share the the same client for all of the requests and all hyper threads
    let client: Arc<SlackClient> = Arc::new(SlackClient::new());

    // In this example we're going to use all of the events handlers, but
    // you don't have to.

    // Our Slack OAuth handler with a token response after installation
    async fn slack_oauth_install_function(
        resp: SlackOAuthV2AccessTokenResponse,
        _client: Arc<SlackClient>,
    ) {
        println!("{:#?}", resp);
    }

    // Push events handler
    async fn slack_push_events_function(event: SlackPushEvent, _client: Arc<SlackClient>) {
        println!("{:#?}", event);
    }

    // Interaction events handler
    async fn slack_interaction_events_function(event: SlackInteractionEvent, _client: Arc<SlackClient>) {
        println!("{:#?}", event);
    }

    // Commands events handler
    async fn slack_command_events_function(
        event: SlackCommandEvent,
        _client: Arc<SlackClient>,
    ) -> Result<SlackCommandEventResponse, Box<dyn std::error::Error + Send + Sync>> {
        println!("{:#?}", event);
        Ok(SlackCommandEventResponse::new(
            SlackMessageContent::new().with_text("Working on it".into()),
        ))
    }

    // Now we need some configuration for our Slack listener routes.
    // You can additionally configure HTTP route paths using theses configs,
    // but for simplicity we will skip that part here and configure only required parameters.
    let oauth_listener_config = Arc::new(SlackOAuthListenerConfig::new(
        std::env::var("SLACK_CLIENT_ID")?,
        std::env::var("SLACK_CLIENT_SECRET")?,
        std::env::var("SLACK_BOT_SCOPE")?,
        std::env::var("SLACK_REDIRECT_HOST")?,
    ));

    let push_events_config = Arc::new(SlackPushEventsListenerConfig::new(std::env::var(
        "SLACK_SIGNING_SECRET",
    )?));

    let interactions_events_config = Arc::new(SlackInteractionEventsListenerConfig::new(
        std::env::var("SLACK_SIGNING_SECRET")?,
    ));

    let command_events_config = Arc::new(SlackCommandEventsListenerConfig::new(std::env::var(
        "SLACK_SIGNING_SECRET",
    )?));
   
    let make_svc = make_service_fn(move |_| {
        // Because of threading model you have to create copies of configs.
        let thread_oauth_config = oauth_listener_config.clone();
        let thread_push_events_config = push_events_config.clone();
        let thread_interaction_events_config = interactions_events_config.clone();
        let thread_command_events_config = command_events_config.clone();
 
        // Creating listener
        let listener =
            SlackClientEventsListener::new(client.clone()).with_error_handler(slack_listener_error_handler);
        
        // Chaining all of the possible routes for Slack.
        // `chain_service_routes_fn` is an auxiliary function from Slack Morphism. 
        async move {
            let routes = chain_service_routes_fn(
                listener.oauth_service_fn(thread_oauth_config, test_oauth_install_function),
                chain_service_routes_fn(
                    listener.push_events_service_fn(
                        thread_push_events_config,
                        slack_push_events_function,
                    ),
                    chain_service_routes_fn(
                        listener.interaction_events_service_fn(
                            thread_interaction_events_config,
                            slack_interaction_events_function,
                        ),
                        chain_service_routes_fn(
                            listener.command_events_service_fn(
                                thread_command_events_config,
                                slack_command_events_function,
                            ),
                            your_others_routes,
                        ),
                    ),
                ),
            );

            Ok::<_, Box<dyn std::error::Error + Send + Sync>>(service_fn(routes))
        }

    )};

    // Starting a server with listener routes
    let server = hyper::server::Server::bind(&addr).serve(make_svc);
    server.await.map_err(|e| {
        error!("Server error: {}", e);
        e.into()
    })
}

Also the library provides Slack events signature verifier (SlackEventSignatureVerifier), which is already integrated in the routes implementation for you and you don't need to use it directly. All you need is provide your client id and secret configuration to route implementation.

Look at the examples/test_server sources for a complete ready to use example.

Limitations

Slack Morphism doesn't provide:

  • RTM API (the usage of which is slowly declining in favour of Events API)
  • Legacy Web/Events API methods and models (like Slack Message attachments, which should be replaced with Slack Blocks)