Adventures in Rust

I recently decided to write my next few projects in rust to get a better feel for the language. What better way to show off a compiled, strongly-typed language that offers safety and speed? That's right, implement something that has to interact with an Atlassian API. /s

My current job has me dealing with tons of interrupts, and we've realized that we need to track these in such a way that we can factor number of interrupts and time spent on those interrupts into our bi-weekly sprint reviews. Now if that sentence didn't elicit some visceral urge to roll your eyes, you probably aren't Agile enough. Anyway, it is what it is.

My requirements were:

  • Opening a simple interrupt should be one command - no prompts.
    • Optionally add that new interrupt to the current sprint.
  • Should be easily configurable - maybe one of our other teams would use it for their own projects.
  • Distribution of the tool should be easy - no external dependencies.

The current (not final) product is a program called "oh-bother" (ob) that, once configured, makes opening interrupts as easy as:

$ ob new 'schedule a scrum' -d "because we don't have enough of them"

Authentication and whatnot is handled behind the scenes with the help of a YAML configuration file. The previous tools I've written use YAML, so that's what I wanted to stick with. This is where I hit my first major snag. As of the time I wrote this, rust is pretty good at reading YAML files but pretty bad at writing them. The only option (not involving serde) for reading YAML files is yaml-rust, and while it does allow you to write YAML files by emitting the internal representation to a string, it's missing one thing: the ability to insert comments. Being able to write comments is important for creating an initial config file with helpful messages for the end-user. To be fair, I don't (off the top of my head) know of a YAML library for ruby or any other language that supports comments in this way. I wouldn't expect them to, either. What I'm used to doing is using some sort of templating engine to generate my initial YAML file. This was my first major hurdle.

As of today, the rust ecosystem is still relatively young, and one of the major (IMHO) things lacking is a good, general purpose, templating engine. There are actually quite a few HTML-specific templating engines, aimed at the growing number of people interested in powering their web apps with rust. The best I could find for writing arbitrary files in my 30 minutes or so of searching was tera, which approximates Jinja2, but after a few hours of reading through the (decent) documentation and trying to get it to loop over a data structure that wasn't an Array, I gave up trying to be fancy with a templating engine and just shoved a giant multiline string into the body of a function which formats that string and writes to a file. Not elegant but gets the job done until the rust templating engine scene matures.

Not surprisingly, dealing with JSON web responses in a statically typed language is a bit tedious. I opted for rustc_serialize over serde for reasons of I wanted to try the "old" way of doing things over the "new" way (I do this often so I can appreciate the new better, or at least that's how I justify it). The whole "structs within structs" thing to achieve "easy" serialization/de-serialization is a bit tedious. Macro support would be nice, though I suppose I could read more about the builders that each library provides.

Of course the biggest hurdle of all wasn't rust-related: there's no way to, in a single request, create a JIRA ticket that is added to the current sprint. I still need to work that out. I've noticed that, with the JIRA instance we have at work, API requests tend to take 5-10 seconds. I'd rather not sit through 3-4 of those to add a ticket to the current sprint.

There are upsides, though. I'm used to thor and click, so I was happy to find clap-rs, which has most of the features I'm used to and some nice extras (like groups and conflicts). Error handling is also good, and I've made liberal use of Result<T> to defer handling and displaying errors to the user from a single, top-level piece of code.

Here's an example (note I haven't switched to using ? instead of try!):

pub fn query(&self, query: &str) -> JiraResult<Option<IssueVec>> {
    let url = try!(self.base_url.join("rest/api/2/search"));
    let q = JQLQuery::new(query);
    let body = try!(json::encode(&q));
    let mut res = try!(self.client.post(url).body(body.as_str()).send());
    let mut response_body = String::new();
    try!(res.read_to_string(&mut response_body));
    let data = try!(Json::from_str(response_body.as_str()));
    Ok(Issue::issues_from_response(&data))
}

Distribution is also much simpler (as expected). Asking people to have ruby on their systems is a bit much, and, while I can use pyinstaller to make "python binaries," those come out around 7 MB just because of the packaged interpreter. Lastly, the toolchain is excellent. Compiler errors are actually useful. Cargo behaves like I would expect (coming from bundler). Overall, everything I found missing from golang.

I've since written a second tool for opening Bitbucket Server pull requests (yay, another Atlassian API). Given the ease of distribution (binaries), the error handling, and the "speed," I'll likely be writing my tools in rust for the foreseeable future. I'll keep my fingers crossed for a general-purpose templating library as the language matures.