About Me

Decree – A Declarative URL Request Framework

I'm very happy to officially announce my new Swift framework called Decree. I've made several frameworks publicly available in the past, but I've never been more proud of one.

Decree allows you to make HTTP requests in a clear and type-safe way by declaring web services and endpoints. It supports all Apple platforms as well as linux. It is the culmination of a technique I've developed over many projects. Some of those projects even include both a frontend and backend implemented in Swift that get to share a single source of endpoint declarations.

I hope that I can convince you to give Decree a try, or at least a look!

First, I want to highlight some of the features I'm most proud of:

  1. Supports JSON, URL Query, Form URL Encoded, Form Data Encoded, and XML input.
  2. Supports JSON and XML output.
  3. Highly configurable with reasonable defaults.
  4. Virtually 100% code coverage.
  5. Thorough inline documentation on public interfaces.
  6. Thorough online wiki.
  7. Option for verbose logging for easy debugging.
  8. Thorough error reporting designed to be user friendly with an option to get detailed diagnostic information.
  9. Powerful mocking system for easy automated testing.

It also has the beginnings of a separate repository for popular service declarations called DecreeServices. This is kept separate to keep the main repository light-weight.

If that doesn't convince you to check it out on Github right now, let's take a quick look at a few examples.

A request without any input or output looks very simple.

// Asynchronous request mostly used for client apps
CheckStatus().makeRequest() { result in
    switch result {
    case .success:
        print("Success :)")
    case .failure(let error):
        print("Error :( \(error)")
    }
}

// Synchronous requests mostly used on backends
try CheckStatus().makeSynchronousRequest()

A login request, accepting a username and password and returning a token, can still look quite simple.

let credentials = Credentials(username: "username", password: "secret")

// Asynchronous request mostly used for client apps
Login().makeRequest(with: credentials) { result in
    switch result {
    case .success(let output):
        print("Token: \(output.token)")
    case .failure(let error):
        print("Error :( \(error)")
    }
}

// Synchronous requests mostly used on backends
let token = try Login().makeSynchronousRequest().token

These call sites are extremely understandable. You don't have all the garbage around creating URLSessions, data tasks, formatting the input, parsing the output, etc.

To make these two requests possible, you only need to declare the web service itself and the endpoints.

struct ExampleService: WebService {
    // There is no service wide standard response or error formats
    typealias BasicResponse = NoBasicResponse
    typealias ErrorResponse = NoErrorResponse

    // Requests should use this service instance by default
    static var shared = ExampleService()

    // Required for mocking to be possible
    var sessionOverride: Session?

    // All requests will be sent to their endpoint at "https://example.com"
    let baseURL = URL(string: "https://example.com")!
}

The endpoints are then declared as:

struct CheckStatus: EmptyEndpoint {
    typealias Service = ExampleService

    let path = "status"
}

struct Credentials: Encodable {
    let username: String
    let password: String
}

struct Login: InOutEndpoint {
    typealias Service = ExampleService
    static let method = Method.post

    let path = "login"

    typealias Input = Credentials

    struct Output: Decodable {
        let token: String
    }
}

Most of the code has been moved to these declarations instead of the call site. Also, this code is quite readable and lays out the possibilities concisely. Finally, much more customization is available if you need it.

For much more information check out the code itself or the wiki.

I've put a lot of effort into making this usable for other developers. I'd love feedback, questions, or any other comments you might have. You can reach out to me through my contact form or create an issue on Github.