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.

Ed9e9992fe069a0d3de05b69d8d187c3?s=128

Matthias Endler

March 01, 2017
Tweet

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