Open-Sourcing our Slack Client for Scala
To celebrate the release of Sign in with Slack, we‘re open-sourcing the Slack client we wrote for Scala. We’ve been using this client in production for several months, since the release of our Slack integration earlier this year.
libraryDependencies += "com.kifi" % "slack-client" %% "0.2"
The Slack API is quite feature-rich and growing rapidly. Generally speaking, there are a lot of API methods, all of which require an access token — a secure string that represents a Slack user who has given you permission to act on their behalf. An access token can only be used on a subset of the API methods determined by the set of auth scopes the token’s user has granted.
To use the Slack API at all you need to ask a user for permission to act on their behalf and be granted an access token. Slack has detailed documentation of the OAuth flow.
Invoking Slack’s API methods once you have an access token is simply a matter of sending a GET request to the desired API endpoint with the appropriate arguments. Almost every one of these arguments end up being represented as strings — from a user’s id to a message timestamp. In our codebase, we opted to use wrapper types around these to make it explicit exactly what each string represented.
Most of these wrapper classes are extremely simple — they wrap a string and do nothing else. Some of the more complex wrappers include SlackTimestamp (which can be converted to a Joda DateTime) and SlackChannelId (which has subclasses for public, private, direct message, and user channels).
The parse function is type-selective, so it can be used to write Play! JSON formatters that are aware of what kind of channel id is expected (e.g., the channels.list API is expected to return only public channels).
Working with any remote API can be a challenge, but Slack’s API has a lot of pleasant features; first and foremost among these is the extremely consistent and unambiguous interface. Every API method endpoint will reply with a JSON object, and that object will always have an “ok” field in it telling you whether your request succeeded or not.
Thus, regardless of which method you are invoking, the same basic structure applies: specify a route and a deserializer for the return type, hit the route, check the “ok” field, then try to deserialize the payload.
There are two broad categories of things that can go wrong when calling the Slack API:
- Everything went fine, but Slack refused to fulfill the request for any number of reasons: you’re missing a required auth scope, or the request you’re making is impossible to fulfill, etc. In this case you will receive an object with “ok”: false, and an informative error.
- Some technical malfunction prevented Slack from even responding to your request: Slack is temporarily out of service or encountered an internal error, your request URI was too large, etc. In this case you won’t get an object back at all, and you’ll have to figure out what went wrong.
These failures are all captured by the SlackFail trait, which extends Exception. A call to Slack will generally have a type of Future[T], and if something goes wrong it will yield a failed future containing a SlackFail. It is common in our codebase to recover from specific SlackAPIErrorResponses (#1 above) using the SlackErrorCode unapply method.
The call will still fail if anything else goes wrong (e.g., invalid access token, non-existent message, Slack is temporarily down, etc).
We wrote this post while working on Kifi — Connecting People with Knowledge. Learn more about our Slack integration.