Skip to content

Commit 6fe13be

Browse files
committed
♻️ Expose only middleware modifier.
1 parent bfad054 commit 6fe13be

File tree

14 files changed

+174
-193
lines changed

14 files changed

+174
-193
lines changed

README.md

Lines changed: 22 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -25,13 +25,13 @@ You can use `Group` to help organize your routes and cut down on the repetitiven
2525

2626
```swift
2727
app.register {
28-
Group(path: "api", "v1") {
29-
Group(path: "movies") {
28+
Group("api", "v1") {
29+
Group("movies") {
3030
GET("latest") { ... }
3131
GET("popular") { ... }
3232
GET(":movie") { ... }
3333
}
34-
Group(path: "books") {
34+
Group("books") {
3535
GET("new") { ... }
3636
GET("trending") { ... }
3737
GET(":book") { ... }
@@ -40,20 +40,19 @@ app.register {
4040
}
4141
```
4242

43-
### Middleware Groups
43+
### Middleware
4444

45-
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:)`.
45+
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.
4646

4747
```swift
4848
app.register {
49-
Group(path: "api", "v1") {
50-
Group(middleware: AuthenticationMiddleware()) {
51-
Group(path: "profile") {
52-
GET("favorites") { ... }
53-
GET("friends") { ... }
54-
}
49+
Group("api", "v1") {
50+
Group("profile") {
51+
GET("favorites") { ... }
52+
GET("friends") { ... }
5553
}
56-
Group(path: "books") {
54+
.middleware(AuthenticationMiddleware())
55+
Group("books") {
5756
GET("new") { ... }
5857
GET("trending") { ... }
5958
GET(":book") { ... }
@@ -64,15 +63,17 @@ app.register {
6463

6564
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.
6665

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

6968
```swift
70-
Group(middleware: Logging()) {
69+
Group {
7170
GET("foo") { ... }
72-
Group(middleware: Authentication(), Validator()) {
71+
Group {
7372
GET("bar") { ... }
7473
}
74+
.middleware(Authentication(), Validator())
7575
}
76+
.middleware(Logging())
7677
```
7778

7879
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`.
@@ -84,7 +85,7 @@ Often times, as your routes grow, a single large definition can become unwieldly
8485
```swift
8586
struct MoviesUserCase: RouteComponent {
8687
var body: some RouteComponent {
87-
Group(path: "movies") {
88+
Group("movies") {
8889
MovieUseCase()
8990
GET("latest") { ... }
9091
GET("trending") { ... }
@@ -94,7 +95,7 @@ struct MoviesUserCase: RouteComponent {
9495

9596
struct MovieUseCase: RouteComponent {
9697
var body: some RouteComponent {
97-
Group(path: ":movie") {
98+
Group(":movie") {
9899
GET("credits") { ... }
99100
GET("related") { ... }
100101
}
@@ -118,7 +119,7 @@ app.register {
118119
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`
119120

120121
```swift
121-
Group(path: "movies", ":movie") {
122+
Group("movies", ":movie") {
122123
Route(.OPTIONS, "credits") { ... }
123124
}
124125
```
@@ -128,7 +129,7 @@ Group(path: "movies", ":movie") {
128129
Defining a websocket is as simple as using the `Socket` route component.
129130

130131
```swift
131-
Group(path: "api") {
132+
Group("api") {
132133
Socket("foo") { ... }
133134
}
134135
```
@@ -151,7 +152,7 @@ app.register {
151152
```swift
152153
app.register {
153154
for category in categories {
154-
Group(path: "\(category.rawValue)") {
155+
Group("\(category.rawValue)") {
155156
switch category {
156157
case .movies:
157158
GET(":movie") { ... }
@@ -191,7 +192,7 @@ books.register {
191192
- [Confirmed] Handle routes at the root of a RouteComponent.
192193

193194
```swift
194-
Group(path: ":movie") {
195+
Group(":movie") {
195196
... How do we handle JUST ":movie"?
196197
GET("credits") { ... }
197198
GET("category") { ... }

Sources/VaporRouteBuilder/RouteComponents/Groups/Group+Middleware.swift

Lines changed: 31 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,10 +23,37 @@
2323
import Vapor
2424

2525
extension Group {
26+
27+
// MARK: Initializers
28+
29+
/// Creates a group, nesting the provided `content` underneath the provided `middleware`.
30+
///
31+
/// > Note: Usage of `Group(middleware:)` is internal to the framework. Attaching middleware should be accomplished
32+
/// by making use of the `.middleware(_:)` route modifier.
33+
init<C: RouteComponent>(
34+
middleware: any Vapor.Middleware...,
35+
@RouteBuilder content: () -> C
36+
) where Content == Group<C>.Middleware<[any Vapor.Middleware]> {
37+
self.init(middleware: middleware, content: content)
38+
}
39+
40+
/// Creates a group, nesting the provided `content` underneath the provided `middleware`.
41+
///
42+
/// > Note: Usage of `Group(middleware:)` is internal to the framework. Attaching middleware should be accomplished
43+
/// by making use of the `.middleware(_:)` route modifier.
44+
init<C: RouteComponent, M: Collection<any Vapor.Middleware>>(
45+
middleware: M,
46+
@RouteBuilder content: () -> C
47+
) where Content == Group<C>.Middleware<M> {
48+
self.content = Content(middleware: middleware, content: content)
49+
}
50+
51+
// MARK: Middleware
52+
2653
/// Groups content under the provided middleware.
2754
///
2855
/// See ``Group.init(middleware:content:)`` for more info.
29-
public struct Middleware<Middlewares: Collection<any Vapor.Middleware>>: RouteComponent {
56+
struct Middleware<Middlewares: Collection<any Vapor.Middleware>>: RouteComponent {
3057

3158
// MARK: Properties
3259

@@ -39,15 +66,15 @@ extension Group {
3966
// MARK: Initializers
4067

4168
@inlinable
42-
public init(
69+
init(
4370
middleware: any Vapor.Middleware...,
4471
@RouteBuilder content: () -> Content
4572
) where Middlewares == [any Vapor.Middleware] {
4673
self.init(middleware: middleware, content: content)
4774
}
4875

4976
@inlinable
50-
public init(
77+
init(
5178
middleware: Middlewares,
5279
@RouteBuilder content: () -> Content
5380
) {
@@ -57,7 +84,7 @@ extension Group {
5784

5885
// MARK: Boot
5986

60-
public func boot(routes: any RoutesBuilder) throws {
87+
func boot(routes: any RoutesBuilder) throws {
6188
let routes = routes.grouped(Array(middleware))
6289
if let route = content as? Route {
6390
routes.add(route)

Sources/VaporRouteBuilder/RouteComponents/Groups/Group+Path.swift

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,69 @@
2323
import Vapor
2424

2525
extension Group {
26+
27+
// MARK: Initializers
28+
29+
/// Creates a group, nesting the provided `content` underneath the provided `path`.
30+
///
31+
/// This can be useful when you have a series of endpoints that all exist under a given parent path.
32+
/// For example, take an api that serves up content about books and movies. You might have the following endpoints...
33+
///
34+
/// - `/movies/latest`
35+
/// - `/movies/upcoming`
36+
/// - `/books/latest`
37+
/// - `/books/upcoming`
38+
///
39+
/// ```swift
40+
/// Group("movies") {
41+
/// Route("latest", on .GET) { ... }
42+
/// Route("upcoming", on .GET) { ... }
43+
/// }
44+
/// Group("books") {
45+
/// Route("latest", on .GET) { ... }
46+
/// Route("upcoming", on .GET) { ... }
47+
/// }
48+
/// ```
49+
///
50+
@inlinable
51+
public init<C: RouteComponent>(
52+
_ path: PathComponent...,
53+
@RouteBuilder content: () -> C
54+
) where Content == Group<C>.Path<[PathComponent]> {
55+
self.init(path, content: content)
56+
}
57+
58+
/// Creates a group, nesting the provided `content` underneath the provided `path`.
59+
///
60+
/// This can be useful when you have a series of endpoints that all exist under a given parent path.
61+
/// For example, take an api that serves up content about books and movies. You might have the following endpoints...
62+
///
63+
/// - `/movies/latest`
64+
/// - `/movies/upcoming`
65+
/// - `/books/latest`
66+
/// - `/books/upcoming`
67+
///
68+
/// ```swift
69+
/// Group("movies") {
70+
/// Route("latest", on .GET) { ... }
71+
/// Route("upcoming", on .GET) { ... }
72+
/// }
73+
/// Group("books") {
74+
/// Route("latest", on .GET) { ... }
75+
/// Route("upcoming", on .GET) { ... }
76+
/// }
77+
/// ```
78+
///
79+
@inlinable
80+
public init<C: RouteComponent, P: Collection<PathComponent>>(
81+
_ path: P,
82+
@RouteBuilder content: () -> C
83+
) where Content == Group<C>.Path<P> {
84+
self.content = Content(path: path, content: content)
85+
}
86+
87+
// MARK: Path
88+
2689
/// Groups content under a provided path.
2790
///
2891
/// See ``Group.init(path:content:)`` for more info.

0 commit comments

Comments
 (0)