Skip to content

Instantly share code, notes, and snippets.

@d-srd
Created July 10, 2019 00:16
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save d-srd/c633c5d36af84faa6716e8260ac49188 to your computer and use it in GitHub Desktop.
Save d-srd/c633c5d36af84faa6716e8260ac49188 to your computer and use it in GitHub Desktop.
testing the new Swift function builders for creating a simple routing system
//
// RouteBuilder.swift
// RouteBuilder
//
// Created by Dino on 09/07/2019.
// Copyright © 2019 Dino Srdoc. All rights reserved.
//
import Foundation
struct Router {
let routes: [Route]
init(@RouterBuilder _ content: () -> Router) {
self = content()
}
init(routes: [Route]) {
self.routes = routes
}
func handle(request: URLRequest) -> Response {
guard let responder = routes.first(where: { $0.responds(to: request)} ) else {
return Response(statusCode: 500, message: "Unable to handle request")
}
return responder.handler.handle(request)
}
}
@_functionBuilder
class RouterBuilder {
static func buildBlock(_ children: Route...) -> Router {
Router(routes: children)
}
static func buildFunction(_ children: Route...) -> Router {
Router(routes: children)
}
}
struct Route {
let destination: String
let method: Method
let handler: Handler
init(@RouteBuilder _ content: () -> Route) {
self = content()
}
init(destination: String, method: Method, handler: Handler) {
self.destination = destination
self.method = method
self.handler = handler
}
func responds(to request: URLRequest) -> Bool {
guard let url = request.url,
let components = URLComponents(url: url, resolvingAgainstBaseURL: true)
else { return false }
return components.path == destination && request.httpMethod == method.spelledOut
}
}
protocol DescribingRoute { }
enum Method: DescribingRoute {
case put
case post
case get
var spelledOut: String {
switch self {
case .put:
return "PUT"
case .post:
return "POST"
case .get:
return "GET"
}
}
}
let PUT = Method.put
let POST = Method.post
let GET = Method.get
struct Destination: DescribingRoute {
let destination: String
init(_ destination: String) {
self.destination = destination
}
}
extension String: DescribingRoute { }
struct Response {
let statusCode: Int
let message: String
}
struct Handler: DescribingRoute {
let handle: (URLRequest) -> Response
}
struct Identified: DescribingRoute {
let destinationURL: String
init(_ destination: String) {
self.destinationURL = destination
}
}
@_functionBuilder
class RouteBuilder {
static func buildBlock(_ children: DescribingRoute...) -> Route {
let method = children.first(where: { $0 is Method }) as! Method
let handler = children.first(where: { $0 is Handler }) as! Handler
if let identified = children.first(where: { $0 is Identified }) as? Identified {
return Route(destination: identified.destinationURL, method: method, handler: handler)
}
if let destination = children.first(where: { $0 is String }) as? String {
return Route(destination: destination, method: method, handler: handler)
}
fatalError("Must provide a destination for the Route")
}
}
let router = Router {
Route {
GET
"/api/user"
Handler { request in
Response(statusCode: 200, message: "Here are some users")
}
}
Route {
POST
"/api/user"
Handler { request in
Response(statusCode: 200, message: "Did you mean to post some stuff?")
}
}
Route {
GET
Identified("/api/user/%S")
Handler { request in
Response(statusCode: 500, message: "Fatal error")
}
}
}
func doStuff() {
var req0 = URLRequest(url: URL(string: "https://www.mojsuperapi.com/api/user")!)
req0.httpMethod = GET.spelledOut
let resp0 = router.handle(request: req0)
dump(resp0)
// ▿ RouteBuilder.Response
// - statusCode: 200
// - message: "Here are some users"
var req1 = URLRequest(url: URL(string: "https://www.mojsuperapi.com/api/user")!)
req1.httpMethod = POST.spelledOut
let resp1 = router.handle(request: req1)
dump(resp1)
// ▿ RouteBuilder.Response
// - statusCode: 200
// - message: "Did you mean to post some stuff?"
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment