Despite an existing Swagger-compatible API, most of Strava interactions written in Ruby don’t use any auto-generated code and prefer strava-api-v3, a thin client that covers the majority of the Strava API. This is a much cleaner than any swagger-generated mess, but only comes with bare minimum extras. For example, the client can retrieve an activity, but does not have any code to convert a distance from meters to miles or calculate an athlete’s pace. It will also leave you having to refer to the Strava documentation on whether
distance is in meters, yards or feet.
That seems reasonable for a thin client, but I’ve already had to copy-paste a ton of code between publishing my runs to github pages and my Strava Slack bot. I considered adding that code to the strava-api-v3 client, but found its implementation gross enough to write a brand new one.
Unlike strava-api-v3 provides complete OAuth refresh token flow support, a richer first class interface to Strava models, natively supports pagination and implements more consistent error handling.
The rest of this post is about implementation details.
This is not my first API client rodeo, following the very popular slack-ruby-client and a newer iex-ruby-client, so you can be sure I integrated many of the lessons learned into this work. If you’re building a Ruby client for an API, I strongly encourage you to reuse this as a boilerplate.
This is very straightforward, but many API clients don’t even include a version, much less announce themselves to servers properly.
We are going to use this in the API’s user agent.
And make sure we have a test.
API clients tend to want to be configurable globally or directly in a client instance. This is a pretty interesting pattern with the following configuration class.
Note that this pattern can be extended deeper, and the configuration can be further nested, as in iex-ruby-client#88.
The client itself.
What does this do? It allows global configuration.
And allows local configuration that overrides global configuration.
For HTTP clients I prefer to use Faraday, which allows clients to compose modules in a single pipeline, including
Faraday::FlatParamsEncoder to convert input arguments into the HTTP GET query string,
FaradayMiddleware::ParseJson to parse JSON in response or
Faraday::Response::RaiseError to raise exceptions on HTTP errors. This also allows the caller to swap the HTTP engine in the future, often desired in complex applications that want fewer ways to HTTP.
These are mixed into the client class and reuse options from
This allows for first class, strongly typed instance properties and for future extensibility if the API adds fields or a stronger contract with
You can also easily extend the class with additional methods.
A basic API
Something with parameters. I choose to use an extensible
options, but YMMV.
A collection of objects.
Clients should help with pagination. I typically roll out a cursor and wrap all collection API calls into it, enabling developers to automatically paginate through results by supplying a block. This can be seen in strava-ruby-client@40f2a0fd.
Famous Last Words
Writing clients is fun. If you are stuck with a crappy client with poor error handling or data modeling, roll your own based on strava-ruby-client in less than a day’s worth of work.