From 9aa14dbe7db30679643b57d5adddb9527e2b42ee Mon Sep 17 00:00:00 2001 From: Tanner Nelson Date: Wed, 3 Aug 2016 14:44:14 -0400 Subject: [PATCH] params --- routing/basic.md | 12 +++++ routing/parameters.md | 121 ++++++++++++++++++++++++++++++++++++++++++ routing/type-safe.md | 0 3 files changed, 133 insertions(+) create mode 100644 routing/parameters.md delete mode 100644 routing/type-safe.md diff --git a/routing/basic.md b/routing/basic.md index a9132493..afa404ba 100644 --- a/routing/basic.md +++ b/routing/basic.md @@ -28,6 +28,18 @@ drop.post("form") { request in You can also use `any` to match all methods. +## Nesting + +To nest paths (adding `/`s in the URL), simply add commas. + +```swift +drop.get("foo", "bar", "baz") { request in + return "You requested /foo/bar/baz" +} +``` + +You can also use `/`, but commas are often easier to type and work better with type safe route [parameters](parameters.md). + ## Alternate An alternate syntax that accepts a `Method` as the first parameter is also available. diff --git a/routing/parameters.md b/routing/parameters.md new file mode 100644 index 00000000..9a8d60aa --- /dev/null +++ b/routing/parameters.md @@ -0,0 +1,121 @@ +--- +currentMenu: routing-parameters +--- + +# Routing Parameters + +Traditional web frameworks leave room for error in routing by using strings for route parameter names and types. Vapor takes advantage of Swift's closures to provide a safer and more intuitive method for accessing route parameters. + +## Type Safe + +To create a type safe route simply replace one of the parts of your path with a `Type`. + +```swift +drop.get("users", Int.self) { request, userId in + return "You requested User #\(userId)" +} +``` + +This creates a route that matches `users/:id` where the `:id` is an `Int`. Here's what it would look like using manual route parameters. + +```swift +drop.get("users", ":id") { request in + guard let userId = request.parameters["id"].int else { + throw Abort.badRequest + } + + return "You requested User #\(userId)" +} +``` + +Here you can see that type safe routing saves ~3 lines of code and also prevents runtime errors, i.e. mispelling `:id`. + +## String Initializable + +Any type that conforms to `StringInitializable` can be used as a type-safe routing parameter. By default, the following types conform. + +- String +- Int +- Model + +`String` is the most generic and always matches. `Int` only matches when the string supplied can be turned into an integer. `Model` only matches when the string, used as an identifier, can be used to find the model in the database. + +Our previous example with "users" can be further simplified. + +```swift +drop.get("users", User.self) { request, user in + return "You requested \(user.name)" +} +``` + +Here the identifier supplied is automatically used to lookup a user. For example, if `users/5` is requested, the `User` model will be asked for a user with identifier `5`. If one is found, the request succeeds and the closure is called. If not, a not found error is thrown. + +Here is what this would look like if model didn't conform to `StringInitializable`. + +```swift +drop.get("users", Int.self) { request, userId in + guard let user = try User.find(userId) else { + throw Abort.notFound + } + + return "You requested User #\(userId)" +} +``` + +Alltogether, type safe routing can remove as much as 6 lines of code from each route. + +### Protocol + +Conforming your own types to `StringInitializable` is easy. + +```swift +public protocol StringInitializable { + init?(from string: String) throws +} +``` + +Here is what `Model`s conformance looks like for those who are curious. + +```swift +extension Model { + public init?(from string: String) throws { + if let model = try Self.find(string) { + self = model + } else { + return nil + } + } +} +``` + +The `init` method can _both_ `throw` and return `nil`. This allows you to `throw` your own errors. Or, if you want the default error and behavior, just return `nil`. + +### Limits + +Type safe routing is currently limited to three path parts. This is usually remedied by adding route [groups](group.md). + +```swift +drop.group("v1", "users") { users in + users.get(User.self, "posts", Post.self) { request, user, post in + return "Requested \(post.name) for \(user.name)" + } +} +``` + +The resulting path for the above example is `/v1/users/:userId/posts/:postId`. If you are clamoring for more type safe routing, please let us know and we can look into increasing the limit of three. + +## Manual + +As shown briefly above, you are still free to do traditional routing. This can be useful for especially complex situations. + +```swift +drop.get("v1", "users", ":userId", "posts", ":postId", "comments": ":commentId") { request in + let userId = try request.parameters.extract("userId") as Int + let postId = try request.parameters.extract("postId") as Int + let commentId = try request.parameters.extract("commentId") as Int + + return "You requested comment #\(commentId) for post #\(postId) for user #\(userId)" +} +``` + +Request parameters can be accessed either as a dictionary or using the `extract` syntax which throws instead of returning an optional. diff --git a/routing/type-safe.md b/routing/type-safe.md deleted file mode 100644 index e69de29b..00000000