1
0
mirror of https://github.com/vlang/v.git synced 2023-08-10 21:13:21 +03:00

context, vweb: add ability to set and get values on vweb.Context (#18564)

This commit is contained in:
Casper Kuethe 2023-06-27 00:25:45 +02:00 committed by GitHub
parent 7a9c885b31
commit 21d9730cde
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 153 additions and 3 deletions

View File

@ -65,12 +65,41 @@ pub interface Any {}
pub interface Context { pub interface Context {
deadline() ?time.Time deadline() ?time.Time
value(key Key) ?Any value(key Key) ?Any
str() string
mut: mut:
done() chan int done() chan int
err() IError err() IError
} }
// str returns the `str` method of the corresponding Context struct
pub fn (ctx &Context) str() string {
// since `Context` is an interface we have to manually match every possible
// type that implements `Context` if we want to use a `Context` as a field in a struct
// since the `Context` interface has to implement its own `str` method.
match ctx {
BackgroundContext {
return ctx.str()
}
EmptyContext {
return ctx.str()
}
TodoContext {
return ctx.str()
}
CancelContext {
return ctx.str()
}
TimerContext {
return ctx.str()
}
ValueContext {
return ctx.str()
}
else {
return context_name(ctx)
}
}
}
fn context_name(ctx Context) string { fn context_name(ctx Context) string {
return typeof(ctx) return typeof(ctx)
} }

View File

@ -2,7 +2,7 @@ module context
fn test_background() { fn test_background() {
ctx := background() ctx := background()
assert 'context.Background' == ctx.str() assert '&context.Background' == ctx.str()
if _ := ctx.value('') { if _ := ctx.value('') {
panic('This should never happen') panic('This should never happen')
} }
@ -10,7 +10,7 @@ fn test_background() {
fn test_todo() { fn test_todo() {
ctx := todo() ctx := todo()
assert 'context.TODO' == ctx.str() assert '&context.TODO' == ctx.str()
if _ := ctx.value('') { if _ := ctx.value('') {
panic('This should never happen') panic('This should never happen')
} }

View File

@ -377,6 +377,81 @@ If any function of step 2 or 3 returns `false` the middleware functions that wou
come after it are not executed and the app handler will also not be executed. You come after it are not executed and the app handler will also not be executed. You
can think of it as a chain. can think of it as a chain.
### Context values
You can store a value pair in vweb's context. It is especially usefull for passing variables
from a middleware function to the route handler.
**Example**:
```v oksyntax
module main
import vweb
struct App {
vweb.Context
middlewares map[string][]vweb.Middleware
}
pub fn (mut app App) index() vweb.Result {
// get the user or return HTTP 401
user := app.get_value[User]('user') or {
app.set_status(401, '')
return app.text('HTTP 401: Unauthorized')
}
return app.text('welcome ${user.name}')
}
fn main() {
vweb.run(&App{
middlewares: {
'/': [get_session]
}
}, 8080)
}
struct User {
session_id string
name string
}
fn get_session(mut ctx vweb.Context) bool {
// impelement your own logic to get the user
user := User{
session_id: '123456'
name: 'Vweb'
}
// set the user
ctx.set_value('user', user)
return true
}
```
When you visit the index page the middleware function `get_session` will run first
This function sets a `User` value to a key `'user'`.
We get this key in `index` and display it to the user if the `'user'` key exists.
#### Changing Context values
By default context values are immutable when retrieved with `get_value`. If you want to
change the value later you have to set it again with `set_value`.
**Example:**
```v ignore
fn change_user(mut ctx vweb.Context) bool {
user := User{
session_id: '654321'
name: 'tester'
}
// set the user
ctx.set_value('user', user)
return true
}
```
### Redirect ### Redirect
Used when you want be redirected to an url Used when you want be redirected to an url

View File

@ -239,6 +239,13 @@ fn test_redirect_middleware() {
assert received.ends_with('302 Found') assert received.ends_with('302 Found')
} }
// Context's
fn test_middleware_with_context() {
x := http.get('http://${localserver}/with-context') or { panic(err) }
assert x.body == 'b'
}
fn testsuite_end() { fn testsuite_end() {
// This test is guaranteed to be called last. // This test is guaranteed to be called last.
// It sends a request to the server to shutdown. // It sends a request to the server to shutdown.

View File

@ -46,6 +46,7 @@ fn main() {
'/admin/': [middleware1] '/admin/': [middleware1]
'/other/': [middleware1, middleware2] '/other/': [middleware1, middleware2]
'/redirect': [middleware_redirect] '/redirect': [middleware_redirect]
'/with-context': [context1]
} }
} }
eprintln('>> webserver: pid: ${os.getpid()}, started on http://localhost:${http_port}/ , with maximum runtime of ${app.timeout} milliseconds.') eprintln('>> webserver: pid: ${os.getpid()}, started on http://localhost:${http_port}/ , with maximum runtime of ${app.timeout} milliseconds.')
@ -220,6 +221,12 @@ pub fn (mut app App) redirect_route() vweb.Result {
return app.text('${result}should_never_reach!') return app.text('${result}should_never_reach!')
} }
['/with-context']
pub fn (mut app App) with_context() vweb.Result {
a := app.get_value[string]('a') or { 'none' }
return app.text(a)
}
// middleware functions: // middleware functions:
pub fn (mut app App) before_request() { pub fn (mut app App) before_request() {
@ -256,6 +263,11 @@ fn middleware_redirect(mut ctx vweb.Context) bool {
return false return false
} }
fn context1(mut ctx vweb.Context) bool {
ctx.set_value('a', 'b')
return true
}
// utility functions: // utility functions:
pub fn (mut app App) shutdown() vweb.Result { pub fn (mut app App) shutdown() vweb.Result {

View File

@ -12,6 +12,7 @@ import net.urllib
import time import time
import json import json
import encoding.html import encoding.html
import context
// A type which don't get filtered inside templates // A type which don't get filtered inside templates
pub type RawHtml = string pub type RawHtml = string
@ -144,6 +145,7 @@ pub struct Context {
mut: mut:
content_type string = 'text/plain' content_type string = 'text/plain'
status string = '200 OK' status string = '200 OK'
ctx context.Context = context.EmptyContext{}
pub: pub:
// HTTP Request // HTTP Request
req http.Request req http.Request
@ -377,6 +379,30 @@ pub fn (ctx &Context) get_header(key string) string {
return ctx.req.header.get_custom(key) or { '' } return ctx.req.header.get_custom(key) or { '' }
} }
// set_value sets a value on the context
pub fn (mut ctx Context) set_value(key context.Key, value context.Any) {
ctx.ctx = context.with_value(ctx.ctx, key, value)
}
// get_value gets a value from the context
pub fn (ctx &Context) get_value[T](key context.Key) ?T {
if val := ctx.ctx.value(key) {
match val {
T {
// `context.value()` always returns a reference
// if we send back `val` the returntype becomes `?&T` and this can be problematic
// for end users since they won't be able to do something like
// `app.get_value[string]('a') or { '' }
// since V expects the value in the or block to be of type `&string`.
// And if a reference was allowed it would enable mutating the context directly
return *val
}
else {}
}
}
return none
}
pub type DatabasePool[T] = fn (tid int) T pub type DatabasePool[T] = fn (tid int) T
interface DbPoolInterface { interface DbPoolInterface {
@ -642,6 +668,7 @@ fn handle_conn[T](mut conn net.TcpConn, global_app &T, routes &map[string]Route,
// Create Context with request data // Create Context with request data
ctx := Context{ ctx := Context{
ctx: context.background()
req: req req: req
page_gen_start: page_gen_start page_gen_start: page_gen_start
conn: conn conn: conn