Upgrade to Pro — share decks privately, control downloads, hide ads and more …

What's so hard about writing a Slack client in ...

What's so hard about writing a Slack client in Rust?

"I'll just write a simple API wrapper for that. Give me two hours." Does that sound oddly familiar? Don't be fooled: writing an easy to use, idiomatic abstraction layer is a lot of work - in any language. I want to tell you my story about writing a Slack client in Rust. From documentation to testing and error handling there's a lot of pitfalls to avoid and laughs to share.

The talk is pretty lighthearted. I want to show that mere mortals like myself can be productive and have fun with Rust.

Matthias Endler

March 01, 2017
Tweet

More Decks by Matthias Endler

Other Decks in Programming

Transcript

  1. 120 Million monthly users
 3 Billion requests / month
 lots

    of data
 lots of services 
 1200 employees
 all using Slack
  2. What could be improved? •Documenta/on:
 Some/mes the only way to

    make things working
 is to look at the source code •Not idioma/c:
 Code doesn’t feel ergonomic/rus/c •No tests •…
  3. Goals Great ergonomics. Easy to use! Pure, idioma/c Rust code.

    Fully documented and tested. Solid error handling. Convert from JSON to seman/c types. Full Slack API implementa/on.
  4. Topics 1. Idioma/c Rust: Builder pa<ern 2. Tes/ng: yup-hyper-mock 3.

    Error handling: error_chain 4. JSON to seman/c types: serde_json 5. Full Slack API: json schema
  5. Group: "dnd" Method: "dnd.endSnooze" Method: "dnd.setSnooze" Method: "dnd.endDnd" Method: "dnd.info"

    Method: "dnd.teamInfo" Group: "search" Method: "search.messages" Method: "search.files" Method: "search.all" Group: "api" Method: "api.test" Group: "auth" Method: "auth.test" Method: "auth.revoke" Group: "users" Method: "users.info" Method: "users.setPresence" Method: "users.setPhoto" Method: "users.profile.set" Method: "users.deletePhoto" Method: "users.profile.get" Method: "users.setAc/ve" Method: "users.list" Method: "users.iden/ty" Method: "users.getPresence" Group: "reac/ons" Method: "reac/ons.add" Method: "reac/ons.list" Method: "reac/ons.remove" Method: "reac/ons.get" Group: "oauth" Method: "oauth.access" Group: "stars" Method: "stars.add" Method: "stars.list" Method: "stars.remove" Group: "files" Method: "files.upload" Method: "files.list" Method: "files.sharedPublicURL" Method: "files.revokePublicURL" Method: "files.comments.delete" Method: "files.delete" Method: "files.comments.edit" Method: "files.info" Method: "files.comments.add" Group: "bots" Method: "bots.info" Group: "im" Method: "im.replies" Method: "im.mark" Method: "im.history" Method: "im.open" Method: "im.close" Method: "im.list" Group: "pins" Method: "pins.list" Method: "pins.add" Method: "pins.remove" Group: "mpim" Method: "mpim.history" Method: "mpim.mark" Method: "mpim.list" Method: "mpim.close" Method: "mpim.open" Method: "mpim.replies" Group: "channels" Method: "channels.mark" Method: "channels.list" Method: "channels.kick" Method: "channels.rename" Method: "channels.create" Method: "channels.setPurpose" Method: "channels.history" Method: "channels.unarchive" Method: "channels.join" Method: "channels.archive" Method: "channels.replies" Method: "channels.info" Method: "channels.setTopic" Method: "channels.leave" Method: "channels.invite" Group: "emoji" Method: "emoji.list" Group: "groups" Method: "groups.setTopic" Method: "groups.history" Method: "groups.unarchive" Method: "groups.leave" Method: "groups.create" Method: "groups.createChild" Method: "groups.archive" Method: "groups.kick" Method: "groups.setPurpose" Method: "groups.list" Method: "groups.invite" Method: "groups.open" Method: "groups.mark" Method: "groups.close" Method: "groups.replies" Method: "groups.rename" Method: "groups.info" Group: "usergroups" Method: "usergroups.create" Method: "usergroups.list" Method: "usergroups.users.u Method: "usergroups.disable Method: "usergroups.update Method: "usergroups.users.li Method: "usergroups.enable Group: "reminders" Method: "reminders.add" Method: "reminders.info" Method: "reminders.list" Method: "reminders.delete" Method: "reminders.complet Group: "rtm" Method: "rtm.start" Group: "team" Method: "team.accessLogs" Method: "team.billableInfo" Method: "team.integra/onLo Method: "team.info" Method: "team.profile.get" Group: "chat" Method: "chat.postMessage" Method: "chat.update" Method: "chat.meMessage" Method: "chat.delete"
  6. Slack build.rs Main Rust crate codegen build.rs Helper Rust crate

    Slack API ref Ruby rake h<ps:/ /api.slack.com/methods
  7. let mut slack = Client::new("token"); let response = slack.send( history::HistoryOptions

    { channel: "hello", inclusive: Some(true), ..Default::default() }; Slack client usage
 (alterna/ve)
  8. Some(ImHistory { ok: true, latest: None, messages: vec![Message { msg_type:

    MessageType::Message, ts: 1358546515.000008, user: Some("U2147483896".to_string()), text: Some("Hello".to_string()), is_starred: false, wibblr: false, reactions: vec![], }, // ... Message { msg_type: MessageType::Im, ts: 1358546515.000007, user: None, text: None, is_starred: false, wibblr: true, reactions: vec![], }], has_more: false, }) Result
  9. Lessons learned Doing anything right is hard
 Doing anything right

    takes /me
 
 Simple things are difficult
  10. Outlook • Support Real/me Slack API • Convert all Slack

    types to Rust (Slack::Emoji, Timestamp,…) • Iterators for pagina/on • Proper into conversions • Just read Pascal’s Blog.
 h<ps:/ /scribbles.pascalhertleif.de/ elegant-apis-in-rust.html