After finishing the wonderful Haskellbook, the first “real world” project I’ve started writing is a Haskell API wrapper for Shipwire called Ballast. While doing that, I was following the general architecture of dmjio’s Stripe API wrapper. In this post I will try to describe an overview of how both the stripe library and Ballast are built
Making an easy to use library
A goal of Ballast was to make it easy to use. Here’s some example code:
A couple of things:
sandboxEnvConfig gets environment variables and is needed for authentication with Shipwire. getReceivings is a function defined in the Client module that corresponds to a specific endpoint and accepts optional query parameters via (-&-).
You can find similar functions for all the other endpoints.
Simplicity through type families
I wanted to be able to vary return type based on what sort of request I made. To do this I used type families.
shipwire needs to return ShipwireReturn a where a is dependent on the type of ShipwireRequest we have submitted.
Because of that restriction I define the following type family:
This lets us specify a particular request and response type for each endpoint. We can generate a real-time shipping quote with the Rate endpoint like so:
With that in place, I can now define our GetRate datatype, whose JSON representation will be sent to this endpoint:
Ballast uses the following function to perform the HTTP request:
mkShipwirerequest is a constructor that creates our request, NHTM.methodPost is http-client’s POST method.
Handling optional query parameters
You might be wondering what TupleBS8 BSL.ByteString is. That’s how we pass optional parameters to an endpoint.
Here’s how I set that up:
That allows us to specify which endpoint might have optional query parameters like so:
You can chain multiple parameters with (-&-):
This proved to be a pleasant way to structure a client API wrapper. It’s straightforward and flexible and I believe I will reuse this in my future projects.