Skip to content

Steven0351/SwiftyRequest

Repository files navigation

SwiftyRequest

Build Status Carthage compatible swift-package-manager platforms swift-version license

These are some HTTP related helpers that I like to use when building out network communication managers. This is not meant to be a complete solution for all networking needs: it just adds some additional goodies on top of existing Foundation types.

Note: I'm not supporting anything outside of my own use. As such, you will notice things like the TransportProtocol enum:

enum TransportProtocol {
  case http, https
}

There are plenty of other url schemes that are valid such as ftp, ws, etc, but I personally don't use them enough to care to add them or worry about testing them. That being said, if you want to use these helpers but it doesn't have a feature you need, feel free to fork the repo and add whatever you need.

Endpoint Configuration

The Endpoint (a typealias for URLComponents) helpers largely let you configure your endpoints in a manner that doesn't require them to be nested in any kind of helper function. For example:

let baseEndpoint = Endpoint { base in
  base.protocol = .https
  base.host = "somewebservice.com"
  base.pathComponents = ["api", "v1"]
  // https://somewebservice.com/api/v1
}

This could also be achieved in a similar manner using property evaluation:

let base: URLComponents = {
  var components = URLComponents()
  components.scheme = "https"
  components.host = "somewebservice.com"
  components.path = "/api/v1"
  return components
}()

I prefer to have a clean declarative block instead of creating a local variable and then returning it at the end. There is nothing inherently wrong with that approach, but when possible I prefer to omit anything that may distract from what is going on at the callsite.

I've found that in most cases, endpoints that I need to fetch data from are typically unchanged except from a path here or a query parameter there. (Note: Query is a typealias for URLQueryItem)

let peopleEndpoint = baseEndpoint.appending(pathComponent: "people")
// https://somewebservice.com/api/v1/people
let peopleWithBlackHairEndpoint = peopleEndpoint.appending(query:
  Query(name: "haircolor", value: "black")
)
// https://somewebservice.com/api/v1/people?haircolor=black

Query values are oftentimes supplied by the user. You could manage query names in a constants file or an enum, and initialize a URLQueryItem using one of those predefined keys and allow the user defined input as the value. However, I've taken some inspiration from functional languages and utilize something called "Partial Application". Partial Application is a concept in functional languages that are curried by default which allows you to pass fewer parameters than the function takes. The result is a function that takes the remaining parameters and returns what the original function intended. Swift does not support this natively, so to achieve a similar result I've added a static function on Query/URLQueryItem called partialInit(name:) that returns (String) -> Query:

let queryHairColor = Query.partialInit(name: "haircolor")
...
let peopleWithBlackHairEndpoint = peopleEndpoint.appending(
  query: queryHairColor("black")
)
// https://somewebservice.com/api/v1/people?haircolor=black

I've never personally run into a case where I've allowed a user to freely input text to define what property to search on, so being able to predefine queries with the appropriate key that is a partially applied Query initializer can reduce boilerplate setup at the call-site.

Request Configuration

Request (a typealias for URLRequest) uses the same style of configuration as above.

let peopleRequest = Request(endpoint: peopleEndpoint) { request in
  request.method = .post
  request.headerFields = [
    .authorization: "Bearer ==wqeoriuj943ru8sajdf",
    .contentType: "application/json"
  ]
  request.httpBody = try? JSONEncoder().encode(person)
}

A simple GET request that requires no authentication is as simple as

let peopleRequest = Request(endpoint: peopleEndpoint)