mirror of
https://github.com/vlang/v.git
synced 2025-08-03 09:47:15 -04:00
113 lines
3.6 KiB
V
113 lines
3.6 KiB
V
module veb
|
|
|
|
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 veb'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
|
|
}
|