From 774f2aa1e037cf768eadb57518227f597e733a67 Mon Sep 17 00:00:00 2001 From: Alexander Medvednikov Date: Wed, 15 Apr 2026 15:52:37 +0300 Subject: [PATCH] veb: fix nested structure c compilation error (fixes #25415) --- vlib/veb/middleware.v | 83 +++++++++++++++++-- vlib/veb/static_handler.v | 19 +++++ ...sted_embedded_controller_regression_test.v | 58 +++++++++++++ vlib/veb/veb.v | 48 +++++------ 4 files changed, 176 insertions(+), 32 deletions(-) create mode 100644 vlib/veb/tests/nested_embedded_controller_regression_test.v diff --git a/vlib/veb/middleware.v b/vlib/veb/middleware.v index ee0f2d228..6d1f18bf1 100644 --- a/vlib/veb/middleware.v +++ b/vlib/veb/middleware.v @@ -6,14 +6,11 @@ import net.http pub type MiddlewareHandler[T] = fn (mut T) bool -// TODO: get rid of this `voidptr` interface check when generic embedded -// interfaces work properly, related: #19968 interface MiddlewareApp { -mut: - global_handlers []voidptr - global_handlers_after []voidptr - route_handlers []RouteMiddleware - route_handlers_after []RouteMiddleware + get_handlers_for_route(route_path string) []RouteMiddleware + get_handlers_for_route_after(route_path string) []RouteMiddleware + get_global_handlers() []voidptr + get_global_handlers_after() []voidptr } struct RouteMiddleware { @@ -110,6 +107,78 @@ fn (m &Middleware[T]) get_global_handlers_after() []voidptr { return m.global_handlers_after } +fn app_route_handlers[A](app &A, route_path string) []RouteMiddleware { + $if A is $struct { + $for field in A.fields { + $if field.is_embed { + $if field.name == 'Middleware' { + return app.$(field.name).get_handlers_for_route(route_path) + } $else $if field.typ is $struct { + handlers := app_route_handlers(app.$(field.name), route_path) + if handlers.len > 0 { + return handlers + } + } + } + } + } + return []RouteMiddleware{} +} + +fn app_route_handlers_after[A](app &A, route_path string) []RouteMiddleware { + $if A is $struct { + $for field in A.fields { + $if field.is_embed { + $if field.name == 'Middleware' { + return app.$(field.name).get_handlers_for_route_after(route_path) + } $else $if field.typ is $struct { + handlers := app_route_handlers_after(app.$(field.name), route_path) + if handlers.len > 0 { + return handlers + } + } + } + } + } + return []RouteMiddleware{} +} + +fn app_global_handlers[A](app &A) []voidptr { + $if A is $struct { + $for field in A.fields { + $if field.is_embed { + $if field.name == 'Middleware' { + return app.$(field.name).get_global_handlers() + } $else $if field.typ is $struct { + handlers := app_global_handlers(app.$(field.name)) + if handlers.len > 0 { + return handlers + } + } + } + } + } + return []voidptr{} +} + +fn app_global_handlers_after[A](app &A) []voidptr { + $if A is $struct { + $for field in A.fields { + $if field.is_embed { + $if field.name == 'Middleware' { + return app.$(field.name).get_global_handlers_after() + } $else $if field.typ is $struct { + handlers := app_global_handlers_after(app.$(field.name)) + if handlers.len > 0 { + return handlers + } + } + } + } + } + return []voidptr{} +} + fn validate_middleware[T](mut ctx T, raw_handlers []voidptr) bool { for handler in raw_handlers { func := MiddlewareHandler[T](handler) diff --git a/vlib/veb/static_handler.v b/vlib/veb/static_handler.v index 76ee817c4..46cef0630 100644 --- a/vlib/veb/static_handler.v +++ b/vlib/veb/static_handler.v @@ -145,3 +145,22 @@ pub fn (mut sh StaticHandler) host_serve_static(host string, url string, file_pa sh.static_files[url] = file_path sh.static_hosts[url] = host } + +fn app_static_handler[A](app &A) StaticHandler { + $if A is $struct { + $for field in A.fields { + $if field.is_embed { + $if field.name == 'StaticHandler' { + return app.$(field.name) + } $else $if field.typ is $struct { + return app_static_handler(app.$(field.name)) + } + } + } + } + return StaticHandler{ + static_files: map[string]string{} + static_mime_types: map[string]string{} + static_hosts: map[string]string{} + } +} diff --git a/vlib/veb/tests/nested_embedded_controller_regression_test.v b/vlib/veb/tests/nested_embedded_controller_regression_test.v new file mode 100644 index 000000000..3b14fa15c --- /dev/null +++ b/vlib/veb/tests/nested_embedded_controller_regression_test.v @@ -0,0 +1,58 @@ +import veb + +pub struct NestedEmbeddedContext { + veb.Context +} + +pub struct NestedEmbeddedApp { + veb.Middleware[NestedEmbeddedContext] + veb.Controller + veb.StaticHandler +} + +struct NestedEmbeddedAliasApp { + NestedEmbeddedApp +} + +struct NestedEmbeddedBase { + veb.Middleware[NestedEmbeddedContext] +} + +fn test_nested_embedded_controller_registration() { + mut app := &NestedEmbeddedAliasApp{} + app.routes_base() + routes := veb.generate_routes[NestedEmbeddedAliasApp, NestedEmbeddedContext](app) or { + panic(err) + } + assert app.controllers.len == 1 + assert app.controllers[0].path == '/base' + assert 'index' in routes +} + +fn exercise_nested_embedded_run_path() { + mut app := &NestedEmbeddedAliasApp{} + app.routes_base() + veb.run_at[NestedEmbeddedAliasApp, NestedEmbeddedContext](mut app, + port: 0 + show_startup_message: false + family: .ip + timeout_in_seconds: 1 + ) or { panic(err) } +} + +fn (mut app NestedEmbeddedAliasApp) routes_base() { + mut base_app := &NestedEmbeddedBase{} + app.register_controller[NestedEmbeddedBase, NestedEmbeddedContext]('/base', mut base_app) or { + panic(err) + } +} + +@['/'; get] +fn (mut app NestedEmbeddedAliasApp) index(mut ctx NestedEmbeddedContext) veb.Result { + return ctx.text('index success') +} + +@['/get'; get] +fn (mut app NestedEmbeddedBase) base(mut ctx NestedEmbeddedContext) veb.Result { + return ctx.text('base success') +} diff --git a/vlib/veb/veb.v b/vlib/veb/veb.v index 40b6dd79b..495aef0fd 100644 --- a/vlib/veb/veb.v +++ b/vlib/veb/veb.v @@ -52,8 +52,8 @@ fn generate_routes[A, X](app &A) !map[string]Route { } $if A is MiddlewareApp { - route.middlewares = app.Middleware.get_handlers_for_route[X](route_path) - route.after_middlewares = app.Middleware.get_handlers_for_route_after[X](route_path) + route.middlewares = app_route_handlers(app, route_path) + route.after_middlewares = app_route_handlers_after(app, route_path) } routes[method.name] = route @@ -465,8 +465,7 @@ fn handle_route[A, X](mut app A, mut user_context X, url urllib.URL, host string // no need to check the result of `validate_middleware`, since a response has to be sent // anyhow. This function makes sure no further middleware is executed. - validate_middleware[X](mut user_context, - app.Middleware.get_global_handlers_after[X]()) + validate_middleware[X](mut user_context, app_global_handlers_after(app)) // skip route-specific after-middleware if global already sent a response if !user_context.Context.done { validate_middleware[X](mut user_context, get_handlers_for_method(route.after_middlewares, @@ -513,17 +512,15 @@ fn handle_route[A, X](mut app A, mut user_context X, url urllib.URL, host string // then execute global middleware functions $if A is MiddlewareApp { - if validate_middleware[X](mut user_context, app.Middleware.get_global_handlers[X]()) == false { + if validate_middleware[X](mut user_context, app_global_handlers(app)) == false { middleware_has_sent_response = true return } } - $if A is StaticApp { - if serve_if_static[A, X](app, mut user_context, url, host) { - // successfully served a static file - return - } + if serve_if_static[A, X](&app, mut user_context, url, host) { + // successfully served a static file + return } // Route matching and match route specific middleware as last step @@ -672,9 +669,10 @@ fn route_matches(url_words []string, route_words []string) ?[]string { fn serve_if_static[A, X](app &A, mut user_context X, url urllib.URL, host string) bool { // TODO: handle url parameters properly - for now, ignore them mut asked_path := url.path + static_handler := app_static_handler(app) // Content negotiation for markdown files (if enabled) - if app.enable_markdown_negotiation { + if static_handler.enable_markdown_negotiation { accept_header := user_context.req.header.get(.accept) or { '' } if accept_header.contains('text/markdown') { // Try markdown variants in order of priority @@ -685,7 +683,7 @@ fn serve_if_static[A, X](app &A, mut user_context X, url urllib.URL, host string ] for variant in markdown_variants { - if app.static_files[variant] != '' { + if static_handler.static_files[variant] != '' { asked_path = variant break } @@ -700,29 +698,29 @@ fn serve_if_static[A, X](app &A, mut user_context X, url urllib.URL, host string if asked_path.ends_with('/') { // Check for markdown index first if Accept header requests it and feature is enabled - if app.enable_markdown_negotiation { + if static_handler.enable_markdown_negotiation { accept_header := user_context.req.header.get(.accept) or { '' } if accept_header.contains('text/markdown') - && app.static_files[asked_path + 'index.html.md'] != '' { + && static_handler.static_files[asked_path + 'index.html.md'] != '' { asked_path += 'index.html.md' - } else if app.static_files[asked_path + 'index.html'] != '' { + } else if static_handler.static_files[asked_path + 'index.html'] != '' { asked_path += 'index.html' - } else if app.static_files[asked_path + 'index.htm'] != '' { + } else if static_handler.static_files[asked_path + 'index.htm'] != '' { asked_path += 'index.htm' } - } else if app.static_files[asked_path + 'index.html'] != '' { + } else if static_handler.static_files[asked_path + 'index.html'] != '' { asked_path += 'index.html' - } else if app.static_files[asked_path + 'index.htm'] != '' { + } else if static_handler.static_files[asked_path + 'index.htm'] != '' { asked_path += 'index.htm' } } - static_file := app.static_files[asked_path] or { return false } + static_file := static_handler.static_files[asked_path] or { return false } // StaticHandler ensures that the mime type exists on either the App or in veb ext := os.file_ext(static_file).to_lower() - mut mime_type := app.static_mime_types[ext] or { mime_types[ext] } + mut mime_type := static_handler.static_mime_types[ext] or { mime_types[ext] } - static_host := app.static_hosts[asked_path] or { '' } + static_host := static_handler.static_hosts[asked_path] or { '' } if static_file == '' || mime_type == '' { return false } @@ -731,12 +729,12 @@ fn serve_if_static[A, X](app &A, mut user_context X, url urllib.URL, host string } // Configure static file compression settings - user_context.set_static_compression_config(app.enable_static_gzip, app.enable_static_zstd, - app.enable_static_compression, if app.static_compression_max_size >= 0 { - app.static_compression_max_size + user_context.set_static_compression_config(static_handler.enable_static_gzip, + static_handler.enable_static_zstd, static_handler.enable_static_compression, if static_handler.static_compression_max_size >= 0 { + static_handler.static_compression_max_size } else { 1048576 // Default: 1MB - }, app.static_compression_mime_types.clone()) + }, static_handler.static_compression_mime_types.clone()) user_context.send_file(mime_type, static_file) return true -- 2.39.5