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