veb: implicit context

This commit is contained in:
Alexander Medvednikov 2024-09-14 00:40:14 +03:00
parent cc7665ff32
commit 26ab7d4fe1
13 changed files with 65 additions and 86 deletions

View file

@ -24,9 +24,8 @@ pub struct App {
// time is a simple POST request handler, that returns the current time. It should be available
// to JS scripts, running on arbitrary other origins/domains.
// pub fn (app &App) time() veb.Result {
@[post]
pub fn (app &App) time(mut ctx Context) veb.Result {
pub fn (app &App) time() veb.Result {
return ctx.json({
'time': time.now().format_ss_milli()
})
@ -34,7 +33,7 @@ pub fn (app &App) time(mut ctx Context) veb.Result {
fn main() {
println("
To test, if CORS works, copy this JS snippet, then go to for example https://stackoverflow.com/ ,
To test if CORS works, copy this JS snippet, then go to for example https://stackoverflow.com/ ,
press F12, then paste the snippet in the opened JS console. You should see the veb server's time:
var xhr = new XMLHttpRequest();
xhr.onload = function(data) {

View file

@ -34,7 +34,7 @@ pub fn (mut app App) user_endpoint(mut ctx Context, user string) veb.Result {
})
}
pub fn (mut app App) index(mut ctx Context) veb.Result {
pub fn (mut app App) index() veb.Result {
mut c := 0
lock app.state {
app.state.cnt++

View file

@ -1,70 +0,0 @@
module main
import vweb
import rand
const port = 8082
struct State {
mut:
cnt int
}
struct App {
vweb.Context
mut:
state shared State
}
pub fn (app &App) before_request() {
$if trace_before_request ? {
eprintln('[vweb] before_request: ${app.req.method} ${app.req.url}')
}
}
@['/users/:user']
pub fn (mut app App) user_endpoint(user string) vweb.Result {
id := rand.intn(100) or { 0 }
return app.json({
user: id
})
}
pub fn (mut app App) index() vweb.Result {
mut c := 0
lock app.state {
app.state.cnt++
c = app.state.cnt
//
$if trace_address_of_app_state_cnt ? {
dump(ptr_str(app.state.cnt))
}
}
show := true
hello := 'Hello world from vweb, request number: ${c}'
numbers := [1, 2, 3]
return $vweb.html()
}
pub fn (mut app App) custom_template() vweb.Result {
return $vweb.html('custom.html')
}
pub fn (mut app App) show_text() vweb.Result {
return app.text('Hello world from vweb')
}
pub fn (mut app App) cookie() vweb.Result {
app.set_cookie(name: 'cookie', value: 'test')
return app.text('Response Headers\n${app.header}')
}
@[post]
pub fn (mut app App) post() vweb.Result {
return app.text('Post body: ${app.req.data}')
}
fn main() {
println('vweb example')
vweb.run(&App{}, port)
}

View file

@ -30,7 +30,9 @@ pub mut:
used_fns map[string]bool // filled in by the checker, when pref.skip_unused = true;
used_consts map[string]bool // filled in by the checker, when pref.skip_unused = true;
used_globals map[string]bool // filled in by the checker, when pref.skip_unused = true;
used_vweb_types []Type // vweb context types, filled in by checker, when pref.skip_unused = true;
used_veb_types []Type // veb context types, filled in by checker, when pref.skip_unused = true;
veb_res_idx_cache int // Cache of `veb.Result` type
veb_ctx_idx_cache int // Cache of `veb.Context` type
used_maps int // how many times maps were used, filled in by checker, when pref.skip_unused = true;
panic_handler FnPanicHandler = default_table_panic_handler
panic_userdata voidptr = unsafe { nil } // can be used to pass arbitrary data to panic_handler;
@ -72,7 +74,7 @@ pub fn (mut t Table) free() {
t.used_fns.free()
t.used_consts.free()
t.used_globals.free()
t.used_vweb_types.free()
t.used_veb_types.free()
}
}
@ -233,6 +235,16 @@ pub fn (mut t TypeSymbol) register_method(new_fn Fn) int {
return t.methods.len - 1
}
pub fn (mut t TypeSymbol) update_method(f Fn) int {
for i, m in t.methods {
if m.name == f.name {
t.methods[i] = f
return i
}
}
return -1
}
pub fn (t &Table) register_aggregate_method(mut sym TypeSymbol, name string) !Fn {
if sym.kind != .aggregate {
t.panic('table.register_aggregate_method: sym.name: ${sym.name}, sym.kind: ${sym.kind} is not an aggregate, name: ${name}')
@ -2550,3 +2562,12 @@ pub fn (t &Table) get_attrs(sym TypeSymbol) []Attr {
}
}
}
pub fn (mut t Table) get_veb_result_type_idx() int {
if t.veb_res_idx_cache > 0 {
return t.veb_res_idx_cache
}
t.veb_res_idx_cache = t.find_type_idx('veb.Result')
return t.veb_res_idx_cache
}

View file

@ -616,7 +616,7 @@ fn (mut c Checker) verify_all_vweb_routes() {
if c.vweb_gen_types.len == 0 {
return
}
c.table.used_vweb_types = c.vweb_gen_types
c.table.used_veb_types = c.vweb_gen_types
typ_vweb_result := c.table.find_type_idx('vweb.Result')
old_file := c.file
for vgt in c.vweb_gen_types {

View file

@ -434,11 +434,11 @@ fn (mut c Checker) fn_decl(mut node ast.FnDecl) {
}
c.fn_scope = node.scope
// Register implicit context var
typ_veb_result := c.table.find_type_idx('veb.Result')
typ_veb_result := c.table.get_veb_result_type_idx() // c.table.find_type_idx('veb.Result')
if node.return_type == typ_veb_result {
typ_veb_context := c.table.find_type_idx('veb.Context')
typ_veb_context := ast.Type(u32(c.table.find_type_idx('veb.Context'))).set_nr_muls(1)
// No `ctx` param? Add it
if !node.params.any(it.name == 'ctx') && node.params.len > 1 {
if !node.params.any(it.name == 'ctx') && node.params.len >= 1 {
params := node.params.clone()
ctx_param := ast.Param{
name: 'ctx'
@ -449,11 +449,19 @@ fn (mut c Checker) fn_decl(mut node ast.FnDecl) {
node.params << params[1..]
// println('new params ${node.name}')
// println(node.params)
// We've added ctx to the FnDecl node.
// Now update the existing method, already registered in Table.
mut rec_sym := c.table.sym(node.receiver.typ)
if mut m := c.table.find_method(rec_sym, node.name) {
m.params << ctx_param
rec_sym.update_method(m)
}
}
// sym := c.table.sym(typ_veb_context)
// println('reging ${typ_veb_context} ${sym}')
// println(c.fn_scope)
// println(node.params)
// Finally add ctx to the scope
c.fn_scope.register(ast.Var{
name: 'ctx'
typ: typ_veb_context

View file

@ -169,10 +169,27 @@ fn (mut g Gen) comptime_call(mut node ast.ComptimeCall) {
if !has_decompose {
// do not generate anything if the argument lengths don't match
g.writeln('/* skipping ${sym.name}.${m.name} due to mismatched arguments list: node.args=${node.args.len} m.params=${m.params.len} */')
// g.writeln('println(_SLIT("skipping ${node.sym.name}.$m.name due to mismatched arguments list"));')
// eprintln('info: skipping ${node.sym.name}.$m.name due to mismatched arguments list\n' +
//'method.params: $m.params, args: $node.args\n\n')
// verror('expected ${m.params.len - 1} arguments to method ${node.sym.name}.${m.name}, but got ${node.args.len}')
// Adding a println(_SLIT(...)) like this breaks options
/*
g.writeln(
'println(_SLIT("comptime: ${sym.name}.${m.name} has a mismatched arguments list.' +
' The method has ${m.params.len - 1} parameters, but ${node.args.len} arguments were ' +
'provided. \\nargs: ${node.args}\\n${g.file.path}:${node.pos.line_nr}"));')
*/
/*
if true {
println(node.args)
verror('comptime: ${sym.name}.${m.name} has a mismatched arguments list.' +
' The method has ${m.params.len - 1} parameters, but ${node.args.len} arguments were ' +
'provided. ${g.file.path}:${node.pos.line_nr}\n')
}
if true {
eprintln(
'comptime: skipping ${sym.name}.${m.name} due to mismatched arguments list\n' +
'method.params: ${m.params}, args: ${node.args}\n\n')
// verror('expected ${m.params.len - 1} arguments to method ${node.sym.name}.${m.name}, but got ${node.args.len}')
}
*/
return
}
}

View file

@ -449,7 +449,7 @@ fn handle_vweb(mut table ast.Table, mut all_fn_root_names []string, result_name
all_fn_root_names << filter_name
typ_vweb_context := ast.idx_to_type(table.find_type_idx(context_name)).set_nr_muls(1)
all_fn_root_names << '${int(typ_vweb_context)}.html'
for vgt in table.used_vweb_types {
for vgt in table.used_veb_types {
sym_app := table.sym(vgt)
for m in sym_app.methods {
mut skip := true

View file

@ -223,6 +223,8 @@ fn ev_callback[A, X](mut pv picoev.Picoev, fd int, events int) {
$if trace_picoev_callback ? {
eprintln('> read event on file descriptor ${fd}')
}
// TODO figure out the POST repeat in ev_callback
// println('ev_callback fd=${fd} params.routes=${params.routes.len}')
handle_read[A, X](mut pv, mut params, fd)
} else {
// should never happen
@ -323,7 +325,7 @@ fn handle_write_string(mut pv picoev.Picoev, mut params RequestParams, fd int) {
// handle_read reads data from the connection and if the request is complete
// it calls `handle_route` and closes the connection.
// If the request is not complete it stores the incomplete request in `params`
// If the request is not complete, it stores the incomplete request in `params`
// and the connection stays open until it is ready to read again
@[direct_array_access; manualfree]
fn handle_read[A, X](mut pv picoev.Picoev, mut params RequestParams, fd int) {
@ -592,6 +594,7 @@ fn handle_request[A, X](mut conn net.TcpConn, req http.Request, params &RequestP
}
fn handle_route[A, X](mut app A, mut user_context X, url urllib.URL, host string, routes &map[string]Route) {
// println('\n\nHANDLE ROUTE')
mut route := Route{}
mut middleware_has_sent_response := false
mut not_found := false
@ -771,6 +774,7 @@ fn handle_route[A, X](mut app A, mut user_context X, url urllib.URL, host string
}
fn route_matches(url_words []string, route_words []string) ?[]string {
// println('route_matches(url_words:${url_words} route_words:${route_words}')
// URL path should be at least as long as the route path
// except for the catchall route (`/:path...`)
if route_words.len == 1 && route_words[0].starts_with(':') && route_words[0].ends_with('...') {