Type Safe Request URLs
Since I have spent a good deal of time working with dynamic programming languages, using a statically typed language is something of a novelty. Perhaps as the result of my previous, abortive attempts at learning Haskell, I am particularly interested in exploiting the type-system where I can.
One of my current interests is building clients for interacting with remote REST web-APIs. This is something I’ve done more than once and I’ve never been entirely happy with how I’ve accomplished it to this point.
So as part of my quest for type-safety and nice web client’s, I started a small project, Yopuy. It is a library for building REST clients, with a number of specific goals.
- Type-safety; it should be impossible to construct invalid paths
- Encoding agnostic; it should have no opinion about what library is used to turn JSON into values
- HTTP client agnostic; it should not implement HTTP support, but rather defer to which-ever client the user prefers
So here is a taste of how the library might be used — with many details elided.
let path = Blog.show(10) / Post.show(1) / Comment.delete(12)
let result = webService.call(path) { result in
print(result)
}
We construct a type-safe path, which also captures the HTTP method via Comment.delete
. This is then passed to an instance of the Service
, type which coordinates the actual request.
In this post I will be focussing on the type-safe construction of paths. In later posts I will discuss other features of the library.
Type-Safety
The first goal for the library is achieved through the use of protocols and associated types. The core part of this library is the Resource
protocol. The basic idea is that the resources on the API are represented with types that conform to the Resource
protocol and other protocols which we will discuss later.
Conforming to these protocols adds a number of static functions to the types. These functions produce values which represent either full or partial paths. For example, lets consider a Blog
resource. After conforming to the protocols we can generate path values.
let path = Blog.show(19)
The result is a strongly typed value of Path<Blog, SingularPath, GET>
. This type captures a number of things.
- The resource;
Blog
in this case - If the path points to a single resource or collection
- The HTTP method
This path can then be passed off to an instance of the Service
struct in order to make a request or composed with other paths.
Representing a Hierarchy
Since REST resources can also be arranged in a hierarchy, Yopuy allows us to capture that relationship in the types. In our example, the Post
resource is a child of the Blog
. This is done by making Post
conform to the ChildResource
protocol. It requires an associatedtype
called Parent
. In case of Post
it is declared as typealias Parent = Blog
.
struct Blog: RootResource {
}
struct Post: ChildResource {
typealias Parent = Blog
}
// We can do this
let good = Blog.show(1) / Post.list
// But not this
let bad = Post.show(1) / Blog.show(3)
Defining the hierarchy via associated types means it’s impossible to construct invalid paths. Additionally any ChildResource
type can also be a parent to other resources; the hierarchy can be as deep as necessary.
REST Actions
By default a resource doesn’t actually have any of the static properties or functions for constructing paths. They are all optional, since potentially not all actions would be supported for a given API. For example, you might have an API which disallows the DELETE
method on certain resources.
Yopuy provides protocols for the different capabilities of a resource.
IsListable
; can be retrieved as a collection e.g.GET /posts
IsShowable
; can be retrieved as a single record e.g.GET /posts/1
IsCreatable
; new resources can be created e.g.POST /posts
IsReplaceable
; a resource can be updated viaPUT
e.g.PUT /posts/1
IsPatchable
; a resource can be updated viaPATCH
e.g.PATCH /posts/1
IsDeletable
; a resource can be deleted viaDELETE
e.g.DELETE /posts/1
Here is an example of a record that can be updated, but not created or deleted.
struct Foo: RootResource, IsPatchable, IsShowable {
// Details elided...
}
Custom actions
It’s not unusual to have an API that requires an action outside of the standard set provided by REST. Yopuy does support this, but it does unfortunately require some knowledge of how the library works. I’m investigating ways to make this easier.
But Wait!
There’s more! Yopuy provides more than just type-safe paths. It also has other interesting features. In a future post I will discuss how any HTTP client or JSON library can be integrated into Yopuy.