/
controller.v
112 lines (96 loc) · 3.56 KB
/
controller.v
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
module vweb
import net.urllib
type ControllerHandler = fn (ctx &Context, mut url urllib.URL, host string) &Context
pub struct ControllerPath {
pub:
path string
handler ControllerHandler = unsafe { nil }
pub mut:
host string
}
interface ControllerInterface {
controllers []&ControllerPath
}
pub struct Controller {
pub mut:
controllers []&ControllerPath
}
// register_controller adds a new Controller to your app
pub fn (mut c Controller) register_controller[A, X](path string, mut global_app A) ! {
c.controllers << controller[A, X](path, mut global_app)!
}
// controller generates a new Controller for the main app
pub fn controller[A, X](path string, mut global_app A) !&ControllerPath {
routes := generate_routes[A, X](global_app) or { panic(err.msg()) }
controllers_sorted := check_duplicate_routes_in_controllers[A](global_app, routes)!
// generate struct with closure so the generic type is encapsulated in the closure
// no need to type `ControllerHandler` as generic since it's not needed for closures
return &ControllerPath{
path: path
handler: fn [mut global_app, path, routes, controllers_sorted] [A, X](ctx &Context, mut url urllib.URL, host string) &Context {
// transform the url
url.path = url.path.all_after_first(path)
// match controller paths
$if A is ControllerInterface {
if completed_context := handle_controllers[X](controllers_sorted, ctx, mut
url, host)
{
return completed_context
}
}
// create a new user context and pass the vweb's context
mut user_context := X{}
user_context.Context = ctx
handle_route[A, X](mut global_app, mut user_context, url, host, &routes)
// we need to explicitly tell the V compiler to return a reference
return &user_context.Context
}
}
}
// register_controller adds a new Controller to your app
pub fn (mut c Controller) register_host_controller[A, X](host string, path string, mut global_app A) ! {
c.controllers << controller_host[A, X](host, path, mut global_app)!
}
// controller_host generates a controller which only handles incoming requests from the `host` domain
pub fn controller_host[A, X](host string, path string, mut global_app A) &ControllerPath {
mut ctrl := controller[A, X](path, mut global_app)
ctrl.host = host
return ctrl
}
fn check_duplicate_routes_in_controllers[T](global_app &T, routes map[string]Route) ![]&ControllerPath {
mut controllers_sorted := []&ControllerPath{}
$if T is ControllerInterface {
mut paths := []string{}
controllers_sorted = global_app.controllers.clone()
controllers_sorted.sort(a.path.len > b.path.len)
for controller in controllers_sorted {
if controller.host == '' {
if controller.path in paths {
return error('conflicting paths: duplicate controller handling the route "${controller.path}"')
}
paths << controller.path
}
}
for method_name, route in routes {
for controller_path in paths {
if route.path.starts_with(controller_path) {
return error('conflicting paths: method "${method_name}" with route "${route.path}" should be handled by the Controller of path "${controller_path}"')
}
}
}
}
return controllers_sorted
}
fn handle_controllers[X](controllers []&ControllerPath, ctx &Context, mut url urllib.URL, host string) ?&Context {
for controller in controllers {
// skip controller if the hosts don't match
if controller.host != '' && host != controller.host {
continue
}
if url.path.len >= controller.path.len && url.path.starts_with(controller.path) {
// pass route handling to the controller
return controller.handler(ctx, mut url, host)
}
}
return none
}