-
-
Notifications
You must be signed in to change notification settings - Fork 1.4k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
type safe routing #133
type safe routing #133
Changes from all commits
ac1edc3
c1b3682
8fc63ee
e1ed60a
886bdd5
feec832
1bf4f4a
5f9bf1e
172148a
31e2a04
55e2ed4
3e7d214
69ee4df
e9552d8
2d43f15
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,232 @@ | ||
// | ||
// main.swift | ||
// Vapor | ||
// | ||
// Created by Tanner Nelson on 3/13/16. | ||
// Copyright © 2016 Tanner Nelson. All rights reserved. | ||
// | ||
|
||
import Foundation | ||
|
||
let GENERIC_MAP = ["T", "U", "V", "W", "X"] | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I feel like its sensible to have at least one more parameter. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I made it 5 from 7 and 6 because they took significantly longer to compile than 5 in general development. Maybe a couple other people could run it w/ 6 and see how the compile times feel There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think 5 is more than enough. Check out the caveat section in VaporWiki.Routing for my reasoning. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Reason for 6 is it fits 3 nested components. i.e.: blogs/:blog_id/posts/:post_id/comments/:comment_id Which is needed for things like DELETE on a comment. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. That's true... @loganwright how much longer did it take to compile? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't have specifics, w/ 7 especially, it took long enough that I was worried Xcode was broken. I think you guys should try locally. The logic for 6 is pretty strong. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We can always add more. Starting with less is fine I think. |
||
let MAX_PARAMS = GENERIC_MAP.count | ||
|
||
struct Func: CustomStringConvertible { | ||
enum Method { | ||
case Get, Post, Put, Patch, Delete, Options | ||
} | ||
|
||
var method: Method | ||
var params: [Param] | ||
|
||
var description: String { | ||
|
||
|
||
|
||
let wildcards = params.filter { param in | ||
return param.type == .Wildcard | ||
} | ||
|
||
|
||
var f = "" | ||
f += "\tpublic func " | ||
f += "\(method)".lowercaseString | ||
|
||
//generic <> | ||
if wildcards.count > 0 { | ||
let genericsString = wildcards.map { wildcard in | ||
return "\(wildcard.generic): StringInitializable" | ||
}.joinWithSeparator(", ") | ||
|
||
f += "<\(genericsString)>" | ||
} | ||
|
||
let paramsString = params.enumerate().map { (index, param) in | ||
if index > 0 { | ||
return "_ \(param)" | ||
} else { | ||
return param.description | ||
} | ||
}.joinWithSeparator(", ") | ||
|
||
f += "(\(paramsString), handler: (Request" | ||
|
||
//handler params | ||
if wildcards.count > 0 { | ||
let genericsString = wildcards.map { wildcard in | ||
return wildcard.generic | ||
}.joinWithSeparator(", ") | ||
|
||
f += ", \(genericsString)) throws -> ResponseConvertible) {\n" | ||
|
||
} else { | ||
f += ") throws -> ResponseConvertible) {\n" | ||
} | ||
|
||
let pathString = params.map { param in | ||
if param.type == .Wildcard { | ||
return ":\(param.name)" | ||
} | ||
|
||
return "\\(\(param.name))" | ||
}.joinWithSeparator("/") | ||
|
||
f += "\t\tself.add(.\(method), path: \"\(pathString)\") { request in\n" | ||
|
||
//function body | ||
if wildcards.count > 0 { | ||
//grab from request params | ||
for wildcard in wildcards { | ||
f += "\t\t\tguard let v\(wildcard.name) = request.parameters[\"\(wildcard.name)\"] else {\n" | ||
f += "\t\t\t\tthrow Abort.BadRequest\n" | ||
f += "\t\t\t}\n" | ||
} | ||
|
||
f += "\n" | ||
|
||
//try | ||
for wildcard in wildcards { | ||
f += "\t\t\tlet e\(wildcard.name) = try \(wildcard.generic)(from: v\(wildcard.name))\n" | ||
} | ||
|
||
f += "\n" | ||
|
||
//ensure conversion worked | ||
for wildcard in wildcards { | ||
f += "\t\t\tguard let c\(wildcard.name) = e\(wildcard.name) else {\n" | ||
f += "\t\t\t\tthrow Abort.BadRequest\n" | ||
f += "\t\t\t}\n" | ||
} | ||
|
||
f += "\n" | ||
|
||
|
||
let wildcardString = wildcards.map { wildcard in | ||
return "c\(wildcard.name)" | ||
}.joinWithSeparator(", ") | ||
|
||
|
||
f += "\t\t\treturn try handler(request, \(wildcardString))\n" | ||
|
||
} else { | ||
|
||
f += "\t\t\treturn try handler(request)\n" | ||
} | ||
|
||
f += "\t\t}\n" | ||
|
||
f += "\t}" | ||
return f | ||
} | ||
} | ||
|
||
func paramTypeCount(type: Param.`Type`, params: [Param]) -> Int { | ||
var i = 0 | ||
|
||
for param in params { | ||
if param.type == type { | ||
i += 1 | ||
} | ||
} | ||
|
||
return i | ||
} | ||
|
||
struct Param: CustomStringConvertible { | ||
var name: String | ||
var type: Type | ||
var generic: String | ||
|
||
var description: String { | ||
var description = "\(name): " | ||
if type == .Wildcard { | ||
description += "\(generic).Type" | ||
} else { | ||
description += "String" | ||
} | ||
return description | ||
} | ||
|
||
enum `Type` { | ||
case Path, Wildcard | ||
} | ||
static var types: [Type] = [.Path, .Wildcard] | ||
|
||
static func addTypePermutations(toArray paramsArray: [[Param]]) -> [[Param]] { | ||
var permParamsArray: [[Param]] = [] | ||
|
||
for paramArray in paramsArray { | ||
for type in Param.types { | ||
var mutableParamArray = paramArray | ||
|
||
var name = "" | ||
if type == .Wildcard { | ||
name = "w" | ||
} else { | ||
name = "p" | ||
} | ||
|
||
let count = paramTypeCount(type, params: paramArray) | ||
name += "\(count)" | ||
|
||
let generic = GENERIC_MAP[count] | ||
|
||
let param = Param(name: name, type: type, generic: generic) | ||
|
||
mutableParamArray.append(param) | ||
permParamsArray.append(mutableParamArray) | ||
} | ||
} | ||
|
||
return permParamsArray | ||
} | ||
} | ||
|
||
|
||
|
||
|
||
var paramPermutations: [[Param]] = [] | ||
|
||
for paramCount in 0...MAX_PARAMS { | ||
var perms: [[Param]] = [[]] | ||
for _ in 0..<paramCount { | ||
perms = Param.addTypePermutations(toArray: perms) | ||
} | ||
|
||
paramPermutations += perms | ||
} | ||
|
||
var generated = "// *** GENERATED CODE ***\n" | ||
generated += "// \(NSDate())\n" | ||
generated += "//\n" | ||
generated += "// DO NOT EDIT THIS FILE OR CHANGES WILL BE OVERWRITTEN\n\n" | ||
generated += "extension Application {\n\n" | ||
|
||
for method: Func.Method in [.Get, .Post, .Put, .Patch, .Delete, .Options] { | ||
for params in paramPermutations { | ||
guard params.count > 0 else { | ||
continue | ||
} | ||
|
||
var f = Func(method: method, params: params) | ||
generated += "\(f)\n\n" | ||
} | ||
} | ||
|
||
generated += "}\n" | ||
|
||
if Process.arguments.count < 2 { | ||
fatalError("Please pass $SRCROOT as a parameter") | ||
} | ||
|
||
let path = Process.arguments[1].stringByReplacingOccurrencesOfString("XcodeProject", withString: "") | ||
let url = NSURL(fileURLWithPath: path + "/Sources/Vapor/Core/Generated.swift") | ||
|
||
do{ | ||
// writing to disk | ||
try generated.writeToURL(url, atomically: true, encoding: NSUTF8StringEncoding) | ||
print("File created at \(url)") | ||
} catch let error as NSError { | ||
print("Error writing generated file at \(url)") | ||
print(error.localizedDescription) | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The readme should have compilable code examples IMO..
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think including
.self
will confuse people an detract from the point the documentation is trying to get across.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I feel like this is trying to show how clever we are, and not how to use it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This excerpt from the swift mailing list makes me feel like
.self
is confusing for peopleThere was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Its requirement was confusing. Thats still exactly the case here; we're just adding more confusion by providing code examples omitting it.