Skip to content

Commit 0a4e3bd

Browse files
committed
Chapter 15 hooks
1 parent e07026c commit 0a4e3bd

17 files changed

+261
-28
lines changed
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
//
2+
// File.swift
3+
//
4+
//
5+
// Created by Tibor Bodecs on 2022. 01. 08..
6+
//
7+
8+
struct AnyHookFunction: HookFunction {
9+
10+
private let functionBlock: HookFunctionSignature<Any>
11+
12+
/// anonymous hooks can be initialized using a function block
13+
init(_ functionBlock: @escaping HookFunctionSignature<Any>) {
14+
self.functionBlock = functionBlock
15+
}
16+
17+
/// since they are hook functions they can be invoked with a given argument
18+
func invoke(_ args: HookArguments) -> Any {
19+
functionBlock(args)
20+
}
21+
}
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
//
2+
// File.swift
3+
//
4+
//
5+
// Created by Tibor Bodecs on 2022. 01. 08..
6+
//
7+
8+
import Vapor
9+
10+
extension Application {
11+
12+
private struct HookStorageKey: StorageKey {
13+
typealias Value = HookStorage
14+
}
15+
16+
var hooks: HookStorage {
17+
get {
18+
if let existing = storage[HookStorageKey.self] {
19+
return existing
20+
}
21+
let new = HookStorage()
22+
storage[HookStorageKey.self] = new
23+
return new
24+
}
25+
set {
26+
storage[HookStorageKey.self] = newValue
27+
}
28+
}
29+
}
30+
31+
extension Application {
32+
33+
func invoke<ReturnType>(_ name: String, args: HookArguments = [:]) -> ReturnType? {
34+
let ctxArgs = args.merging(["app": self]) { (_, new) in new }
35+
return hooks.invoke(name, args: ctxArgs)
36+
}
37+
38+
func invokeAll<ReturnType>(_ name: String, args: HookArguments = [:]) -> [ReturnType] {
39+
let ctxArgs = args.merging(["app": self]) { (_, new) in new }
40+
return hooks.invokeAll(name, args: ctxArgs)
41+
}
42+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
//
2+
// File.swift
3+
//
4+
//
5+
// Created by Tibor Bodecs on 2022. 01. 08..
6+
//
7+
8+
typealias HookArguments = [String: Any]
9+
10+
protocol HookFunction {
11+
func invoke(_: HookArguments) -> Any
12+
}
13+
14+
typealias HookFunctionSignature<T> = (HookArguments) -> T
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
//
2+
// File.swift
3+
//
4+
//
5+
// Created by Tibor Bodecs on 2022. 01. 08..
6+
//
7+
8+
final class HookFunctionPointer<Pointer> {
9+
10+
var name: String
11+
var pointer: Pointer
12+
var returnType: Any.Type
13+
14+
init(name: String, function: Pointer, returnType: Any.Type) {
15+
self.name = name
16+
self.pointer = function
17+
self.returnType = returnType
18+
}
19+
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
//
2+
// File.swift
3+
//
4+
//
5+
// Created by Tibor Bodecs on 2022. 01. 08..
6+
//
7+
8+
final class HookStorage {
9+
10+
private var pointers: [HookFunctionPointer<HookFunction>]
11+
12+
init() {
13+
self.pointers = []
14+
}
15+
16+
func register<ReturnType>(_ name: String, use block: @escaping HookFunctionSignature<ReturnType>) {
17+
let function = AnyHookFunction { args -> Any in
18+
block(args)
19+
}
20+
let pointer = HookFunctionPointer<HookFunction>(name: name, function: function, returnType: ReturnType.self)
21+
pointers.append(pointer)
22+
}
23+
24+
func invoke<ReturnType>(_ name: String, args: HookArguments = [:]) -> ReturnType? {
25+
pointers.first { $0.name == name && $0.returnType == ReturnType.self }?.pointer.invoke(args) as? ReturnType
26+
}
27+
28+
func invokeAll<ReturnType>(_ name: String, args: HookArguments = [:]) -> [ReturnType] {
29+
let fn = pointers.filter { $0.name == name && $0.returnType == ReturnType.self }
30+
return fn.compactMap { $0.pointer.invoke(args) as? ReturnType }
31+
}
32+
}
33+
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
//
2+
// File.swift
3+
//
4+
//
5+
// Created by Tibor Bodecs on 2022. 01. 08..
6+
//
7+
8+
import Vapor
9+
10+
extension Request {
11+
12+
func invoke<ReturnType>(_ name: String, args: HookArguments = [:]) -> ReturnType? {
13+
let ctxArgs = args.merging(["req": self]) { (_, new) in new }
14+
return application.invoke(name, args: ctxArgs)
15+
}
16+
17+
func invokeAll<ReturnType>(_ name: String, args: HookArguments = [:]) -> [ReturnType] {
18+
let ctxArgs = args.merging(["req": self]) { (_, new) in new }
19+
return application.invokeAll(name, args: ctxArgs)
20+
}
21+
}

Chapter 15/myProject/Sources/App/Framework/ModuleInterface.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,12 @@ public protocol ModuleInterface {
1212
static var identifier: String { get }
1313

1414
func boot(_ app: Application) throws
15+
func setUp(_ app: Application) throws
1516
}
1617

1718
public extension ModuleInterface {
1819
func boot(_ app: Application) throws {}
20+
func setUp(_ app: Application) throws {}
1921

2022
static var identifier: String { String(describing: self).dropLast(6).lowercased() }
2123

Chapter 15/myProject/Sources/App/Modules/Admin/AdminModule.swift

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,5 +13,11 @@ struct AdminModule: ModuleInterface {
1313

1414
func boot(_ app: Application) throws {
1515
try router.boot(routes: app.routes)
16+
17+
app.hooks.register("admin-routes", use: router.adminRoutesHook)
18+
}
19+
20+
func setUp(_ app: Application) throws {
21+
try router.setUpRoutesHooks(app: app)
1622
}
1723
}

Chapter 15/myProject/Sources/App/Modules/Admin/AdminRouter.swift

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,19 @@ struct AdminRouter: RouteCollection {
1111

1212
let controller = AdminFrontendController()
1313

14-
func boot(routes: RoutesBuilder) throws {
15-
routes
14+
func boot(routes: RoutesBuilder) throws {}
15+
16+
func setUpRoutesHooks(app: Application) throws {
17+
let adminRoutes = app.routes
1618
.grouped(AuthenticatedUser.redirectMiddleware(path: "/sign-in/"))
17-
.get("admin", use: controller.dashboardView)
19+
.grouped("admin")
20+
21+
let _: [Void] = app.invokeAll("admin-routes", args: ["routes": adminRoutes])
22+
}
23+
24+
func adminRoutesHook(_ args: HookArguments) -> Void {
25+
let routes = args["routes"] as! RoutesBuilder
26+
27+
routes.get(use: controller.dashboardView)
1828
}
1929
}

Chapter 15/myProject/Sources/App/Modules/Admin/Templates/Html/AdminDashboardTemplate.swift

Lines changed: 5 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -25,20 +25,12 @@ struct AdminDashboardTemplate: TemplateRepresentable {
2525
H1(context.title)
2626
P(context.message)
2727
}
28-
29-
Nav {
30-
H2("Blog")
31-
Ul {
32-
Li {
33-
A("Posts")
34-
.href("/admin/blog/posts/")
35-
}
36-
Li {
37-
A("Categories")
38-
.href("/admin/blog/categories/")
39-
}
40-
}
28+
29+
Div {
30+
let widgets: [TemplateRepresentable] = req.invokeAll("admin-widget")
31+
widgets.map { $0.render(req) }
4132
}
33+
.class("widgets")
4234
}
4335
.id("dashboard")
4436
.class("container")

0 commit comments

Comments
 (0)