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

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

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. What’s so hard about wri/ng a Slack Client in Rust?

  2. @ma<hiasendler github.com/mre

  3. Mo/va/on

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

    of data
 lots of services 
 1200 employees
 all using Slack
  5. Write bots! (Chatops) Collect metrics! (Messages per day…) Clone Alien-Dinosaurs!

    Have fun!
  6. None
  7. 2 Million!!!!11! Jan 2016

  8. None
  9. None
  10. None
  11. To create a
 Slack bot we need a
 Slack client

    (d’oh)
  12. slack-rs-api
 example Holy Batman… Pass client and token
 with every

    request? ಠ_ಠ
  13. None
  14. 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 •…
  15. wat if I create one?

  16. Goals

  17. 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.
  18. 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
  19. Idioma/c Rust
 Using the Builder pa<ern 1. Do_it_yourself 2. derive_builder

    https://aturon.github.io/ownership/builders.html
  20. h<ps:/ /github.com/colin-kiegel/rust-derive-builder/issues/31

  21. None
  22. None
  23. Error handling

  24. error_chain

  25. error_chain

  26. error_chain

  27. error_chain

  28. Tes/ng yup_hyper_mock

  29. None
  30. Code genera/on

  31. 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"
  32. Slack build.rs Main Rust crate codegen build.rs Helper Rust crate

    Slack API ref Ruby rake h<ps:/ /api.slack.com/methods
  33. let mut slack = Client::new("token"); let response = slack.im_history("channel_name")
 .with_latest(123.1)


    .send(); Slack client usage
  34. let mut slack = Client::new("token"); let response = slack.send( history::HistoryOptions

    { channel: "hello", inclusive: Some(true), ..Default::default() }; Slack client usage
 (alterna/ve)
  35. 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
  36. Lessons learned Doing anything right is hard
 Doing anything right

    takes /me
 
 Simple things are difficult
  37. Outlook

  38. 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