Skip to content

Commit

Permalink
♻️ Expose only middleware modifier.
Browse files Browse the repository at this point in the history
  • Loading branch information
connor-ricks committed Sep 15, 2024
1 parent bfad054 commit 6fe13be
Show file tree
Hide file tree
Showing 14 changed files with 174 additions and 193 deletions.
43 changes: 22 additions & 21 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,13 +25,13 @@ You can use `Group` to help organize your routes and cut down on the repetitiven

```swift
app.register {
Group(path: "api", "v1") {
Group(path: "movies") {
Group("api", "v1") {
Group("movies") {
GET("latest") { ... }
GET("popular") { ... }
GET(":movie") { ... }
}
Group(path: "books") {
Group("books") {
GET("new") { ... }
GET("trending") { ... }
GET(":book") { ... }
Expand All @@ -40,20 +40,19 @@ app.register {
}
```

### Middleware Groups
### Middleware

Sometimes you may want to wrap certain routes in middleware. A common use case requiring middleware could be authentication. You have have some routes that require middleware, and others that do now. Adding middleware using `@RouteBuilder` is similar to wrapping routes in `Group(path:)`.
Sometimes you may want to wrap certain routes in middleware. A common use case requiring middleware could be authentication. You have have some routes that require middleware, and others that do now. Adding middleware using `@RouteBuilder` is similar to adding view modifiers in SwiftUI.

```swift
app.register {
Group(path: "api", "v1") {
Group(middleware: AuthenticationMiddleware()) {
Group(path: "profile") {
GET("favorites") { ... }
GET("friends") { ... }
}
Group("api", "v1") {
Group("profile") {
GET("favorites") { ... }
GET("friends") { ... }
}
Group(path: "books") {
.middleware(AuthenticationMiddleware())
Group("books") {
GET("new") { ... }
GET("trending") { ... }
GET(":book") { ... }
Expand All @@ -64,15 +63,17 @@ app.register {

In the above example, you'll see that we've only wrapped our `/profile/*` endpoints in our `AuthenticationMiddleware`, while all of the `/book/*` endpoints have no middleware associated with them.

If you have more than one middleware... no worries, `Group(middleware:)` accepts a variadic amount of middleware.
If you have more than one middleware... no worries, `.middleware(_:)` accepts a variadic amount of middleware.

```swift
Group(middleware: Logging()) {
Group {
GET("foo") { ... }
Group(middleware: Authentication(), Validator()) {
Group {
GET("bar") { ... }
}
.middleware(Authentication(), Validator())
}
.middleware(Logging())
```

Remember that order matters here. Incoming requests will always execute middleware from top to bottom. So in the above example, the order of an incoming request would be as follows ➡️ `Logging`, `Authentication`, `Validator`. Outgoing respones will always execute middleware in the reverse order. ➡️ `Validator`, `Authentication`, `Logging`.
Expand All @@ -84,7 +85,7 @@ Often times, as your routes grow, a single large definition can become unwieldly
```swift
struct MoviesUserCase: RouteComponent {
var body: some RouteComponent {
Group(path: "movies") {
Group("movies") {
MovieUseCase()
GET("latest") { ... }
GET("trending") { ... }
Expand All @@ -94,7 +95,7 @@ struct MoviesUserCase: RouteComponent {

struct MovieUseCase: RouteComponent {
var body: some RouteComponent {
Group(path: ":movie") {
Group(":movie") {
GET("credits") { ... }
GET("related") { ... }
}
Expand All @@ -118,7 +119,7 @@ app.register {
This package ships with a few conveniences for creating routes. You can use `GET`, `POST`, `PUT`, `PATCH`, and `DELETE` to cut down on the verbosity of defining your routes. If you need more fine grained control, you can always fall back to using a `Route` directly in your `@RouteBuilder`

```swift
Group(path: "movies", ":movie") {
Group("movies", ":movie") {
Route(.OPTIONS, "credits") { ... }
}
```
Expand All @@ -128,7 +129,7 @@ Group(path: "movies", ":movie") {
Defining a websocket is as simple as using the `Socket` route component.

```swift
Group(path: "api") {
Group("api") {
Socket("foo") { ... }
}
```
Expand All @@ -151,7 +152,7 @@ app.register {
```swift
app.register {
for category in categories {
Group(path: "\(category.rawValue)") {
Group("\(category.rawValue)") {
switch category {
case .movies:
GET(":movie") { ... }
Expand Down Expand Up @@ -191,7 +192,7 @@ books.register {
- [Confirmed] Handle routes at the root of a RouteComponent.

```swift
Group(path: ":movie") {
Group(":movie") {
... How do we handle JUST ":movie"?
GET("credits") { ... }
GET("category") { ... }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,37 @@
import Vapor

extension Group {

// MARK: Initializers

/// Creates a group, nesting the provided `content` underneath the provided `middleware`.
///
/// > Note: Usage of `Group(middleware:)` is internal to the framework. Attaching middleware should be accomplished
/// by making use of the `.middleware(_:)` route modifier.
init<C: RouteComponent>(
middleware: any Vapor.Middleware...,
@RouteBuilder content: () -> C
) where Content == Group<C>.Middleware<[any Vapor.Middleware]> {
self.init(middleware: middleware, content: content)
}

/// Creates a group, nesting the provided `content` underneath the provided `middleware`.
///
/// > Note: Usage of `Group(middleware:)` is internal to the framework. Attaching middleware should be accomplished
/// by making use of the `.middleware(_:)` route modifier.
init<C: RouteComponent, M: Collection<any Vapor.Middleware>>(
middleware: M,
@RouteBuilder content: () -> C
) where Content == Group<C>.Middleware<M> {
self.content = Content(middleware: middleware, content: content)
}

// MARK: Middleware

/// Groups content under the provided middleware.
///
/// See ``Group.init(middleware:content:)`` for more info.
public struct Middleware<Middlewares: Collection<any Vapor.Middleware>>: RouteComponent {
struct Middleware<Middlewares: Collection<any Vapor.Middleware>>: RouteComponent {

// MARK: Properties

Expand All @@ -39,15 +66,15 @@ extension Group {
// MARK: Initializers

@inlinable
public init(
init(
middleware: any Vapor.Middleware...,
@RouteBuilder content: () -> Content
) where Middlewares == [any Vapor.Middleware] {
self.init(middleware: middleware, content: content)
}

@inlinable
public init(
init(
middleware: Middlewares,
@RouteBuilder content: () -> Content
) {
Expand All @@ -57,7 +84,7 @@ extension Group {

// MARK: Boot

public func boot(routes: any RoutesBuilder) throws {
func boot(routes: any RoutesBuilder) throws {
let routes = routes.grouped(Array(middleware))
if let route = content as? Route {
routes.add(route)
Expand Down
63 changes: 63 additions & 0 deletions Sources/VaporRouteBuilder/RouteComponents/Groups/Group+Path.swift
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,69 @@
import Vapor

extension Group {

// MARK: Initializers

/// Creates a group, nesting the provided `content` underneath the provided `path`.
///
/// This can be useful when you have a series of endpoints that all exist under a given parent path.
/// For example, take an api that serves up content about books and movies. You might have the following endpoints...
///
/// - `/movies/latest`
/// - `/movies/upcoming`
/// - `/books/latest`
/// - `/books/upcoming`
///
/// ```swift
/// Group("movies") {
/// Route("latest", on .GET) { ... }
/// Route("upcoming", on .GET) { ... }
/// }
/// Group("books") {
/// Route("latest", on .GET) { ... }
/// Route("upcoming", on .GET) { ... }
/// }
/// ```
///
@inlinable
public init<C: RouteComponent>(
_ path: PathComponent...,
@RouteBuilder content: () -> C
) where Content == Group<C>.Path<[PathComponent]> {
self.init(path, content: content)
}

/// Creates a group, nesting the provided `content` underneath the provided `path`.
///
/// This can be useful when you have a series of endpoints that all exist under a given parent path.
/// For example, take an api that serves up content about books and movies. You might have the following endpoints...
///
/// - `/movies/latest`
/// - `/movies/upcoming`
/// - `/books/latest`
/// - `/books/upcoming`
///
/// ```swift
/// Group("movies") {
/// Route("latest", on .GET) { ... }
/// Route("upcoming", on .GET) { ... }
/// }
/// Group("books") {
/// Route("latest", on .GET) { ... }
/// Route("upcoming", on .GET) { ... }
/// }
/// ```
///
@inlinable
public init<C: RouteComponent, P: Collection<PathComponent>>(
_ path: P,
@RouteBuilder content: () -> C
) where Content == Group<C>.Path<P> {
self.content = Content(path: path, content: content)
}

// MARK: Path

/// Groups content under a provided path.
///
/// See ``Group.init(path:content:)`` for more info.
Expand Down
Loading

0 comments on commit 6fe13be

Please sign in to comment.