| 1 | module veb |
| 2 | |
| 3 | import net.urllib |
| 4 | |
| 5 | type ControllerHandler = fn (ctx &Context, mut url urllib.URL, host string) &Context |
| 6 | |
| 7 | pub struct ControllerPath { |
| 8 | pub: |
| 9 | path string |
| 10 | handler ControllerHandler = unsafe { nil } |
| 11 | pub mut: |
| 12 | host string |
| 13 | } |
| 14 | |
| 15 | interface ControllerInterface { |
| 16 | controllers []&ControllerPath |
| 17 | } |
| 18 | |
| 19 | pub struct Controller { |
| 20 | pub mut: |
| 21 | controllers []&ControllerPath |
| 22 | } |
| 23 | |
| 24 | // register_controller adds a new Controller to your app |
| 25 | pub 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 |
| 30 | pub 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 |
| 71 | pub 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 |
| 76 | pub 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 | |
| 82 | fn 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 | |
| 107 | fn 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 | |