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?

    View Slide

  2. @magithub.com/mre

    View Slide

  3. Mo/va/on

    View Slide

  4. 120 Million monthly users

    3 Billion requests / month

    lots of data

    lots of services

    1200 employees

    all using Slack

    View Slide

  5. Write bots! (Chatops)
    Collect metrics! (Messages per day…)
    Clone Alien-Dinosaurs!
    Have fun!

    View Slide

  6. View Slide

  7. 2 Million!!!!11!
    Jan 2016

    View Slide

  8. View Slide

  9. View Slide

  10. View Slide

  11. To create a

    Slack bot
    we need a

    Slack client
    (d’oh)

    View Slide

  12. slack-rs-api

    example
    Holy Batman…
    Pass client and token

    with every request?
    ಠ_ಠ

    View Slide

  13. View Slide

  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
    •…

    View Slide

  15. wat
    if I create one?

    View Slide

  16. Goals

    View Slide

  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.

    View Slide

  18. Topics
    1. Idioma/c Rust: Builder pa2. Tes/ng: yup-hyper-mock
    3. Error handling: error_chain
    4. JSON to seman/c types: serde_json
    5. Full Slack API: json schema

    View Slide

  19. Idioma/c Rust

    Using the Builder pa1. Do_it_yourself
    2. derive_builder
    https://aturon.github.io/ownership/builders.html

    View Slide

  20. h/github.com/colin-kiegel/rust-derive-builder/issues/31

    View Slide

  21. View Slide

  22. View Slide

  23. Error handling

    View Slide

  24. error_chain

    View Slide

  25. error_chain

    View Slide

  26. error_chain

    View Slide

  27. error_chain

    View Slide

  28. Tes/ng
    yup_hyper_mock

    View Slide

  29. View Slide

  30. Code genera/on

    View Slide

  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"

    View Slide

  32. Slack
    build.rs
    Main Rust crate
    codegen
    build.rs
    Helper Rust crate
    Slack API ref
    Ruby rake
    h/api.slack.com/methods

    View Slide

  33. let mut slack = Client::new("token");
    let response = slack.im_history("channel_name")

    .with_latest(123.1)

    .send();
    Slack client usage

    View Slide

  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)

    View Slide

  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

    View Slide

  36. Lessons learned
    Doing anything right is hard

    Doing anything right takes /me


    Simple things are difficult

    View Slide

  37. Outlook

    View Slide

  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/scribbles.pascalhertleif.de/
    elegant-apis-in-rust.html

    View Slide