Safe API Calls with Enums

I really hate runtime errors. I’m a firm believer in the concept that as many errors as conceivably possible should be caught at compile-time. It makes sense, after all: a compile time error for me means a few minutes of my time spent fixing it; a runtime error for my users means a few seconds of their time staring at an error… multiplied across however many users there may be. And, really, how often are you working on an app where you outnumber the users?

In this vein, I’ve been tinkering with how to handle network requests. The easy first thing to do was to swap out stringly-typed URLs for an enum, and Swift’s associated values make this a breeze:

enum Endpoint {
case globalConfiguration
case category(Category.ID)
case product(Product.ID)
...
}

Your definitions may vary – this is, of course, very specific to the actual use case. But look at that – not only do we have clear representations of the various endpoints available, we can even link the variable components of those URLs to being the proper type for the model we expect to get back. How’s that for self-documenting code?

Next, we need a way to convert from this lovely enum to the actual URL we’re going to use to make our requests. I’ve split this up a little bit:

extension Endpoint {
var pathComponents: String {
switch self {
case .globalConfiguration:
return "/config.json"
case .category(let ID):
return "/category/\(ID).json"
case .product(let ID):
return "/product?id=\(ID)"
...
}
}

var url: URL {
#if DEBUG
return URL(string: "https://dev.my.app")!.appendingPathComponent(pathComponents)
#else
return URL(string: "https://api.my.app")!.appendingPathComponent(pathComponents)
#endif
}
}

Et voila, converting to our known URLs, and a nice little “automatically use the dev server in debug mode” check, while we’re at it.

Now, depending on how you’re organizing your networking code, we can tweak the API surface here a little bit to make things clearer. Add those extensions in the same file as the code that makes the network requests, and declare them fileprivate – and now, you’ve got an automatic reminder from the compiler if you start writing some URL-based networking code outside of the network component. No more leaky abstraction!