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

    120 Million monthly users
 3 Billion requests / month
 lots

    of data
 lots of services 
 1200 employees
 all using Slack
  2. 6.
  3. 8.
  4. 9.
  5. 10.
  6. 13.
  7. 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 •…
  8. 16.
  9. 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.
  10. 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
  11. 19.
  12. 21.
  13. 22.
  14. 29.
  15. 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"
  16. 32.

    Slack build.rs Main Rust crate codegen build.rs Helper Rust crate

    Slack API ref Ruby rake h<ps:/ /api.slack.com/methods
  17. 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)
  18. 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
  19. 36.

    Lessons learned Doing anything right is hard
 Doing anything right

    takes /me
 
 Simple things are difficult
  20. 37.
  21. 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