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:
parent
7a9c885b31
commit
21d9730cde
@ -65,12 +65,41 @@ pub interface Any {}
|
||||
pub interface Context {
|
||||
deadline() ?time.Time
|
||||
value(key Key) ?Any
|
||||
str() string
|
||||
mut:
|
||||
done() chan int
|
||||
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 {
|
||||
return typeof(ctx)
|
||||
}
|
||||
|
@ -2,7 +2,7 @@ module context
|
||||
|
||||
fn test_background() {
|
||||
ctx := background()
|
||||
assert 'context.Background' == ctx.str()
|
||||
assert '&context.Background' == ctx.str()
|
||||
if _ := ctx.value('') {
|
||||
panic('This should never happen')
|
||||
}
|
||||
@ -10,7 +10,7 @@ fn test_background() {
|
||||
|
||||
fn test_todo() {
|
||||
ctx := todo()
|
||||
assert 'context.TODO' == ctx.str()
|
||||
assert '&context.TODO' == ctx.str()
|
||||
if _ := ctx.value('') {
|
||||
panic('This should never happen')
|
||||
}
|
||||
|
@ -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
|
||||
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
|
||||
|
||||
Used when you want be redirected to an url
|
||||
|
@ -239,6 +239,13 @@ fn test_redirect_middleware() {
|
||||
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() {
|
||||
// This test is guaranteed to be called last.
|
||||
// It sends a request to the server to shutdown.
|
||||
|
@ -46,6 +46,7 @@ fn main() {
|
||||
'/admin/': [middleware1]
|
||||
'/other/': [middleware1, middleware2]
|
||||
'/redirect': [middleware_redirect]
|
||||
'/with-context': [context1]
|
||||
}
|
||||
}
|
||||
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!')
|
||||
}
|
||||
|
||||
['/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:
|
||||
|
||||
pub fn (mut app App) before_request() {
|
||||
@ -256,6 +263,11 @@ fn middleware_redirect(mut ctx vweb.Context) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
fn context1(mut ctx vweb.Context) bool {
|
||||
ctx.set_value('a', 'b')
|
||||
return true
|
||||
}
|
||||
|
||||
// utility functions:
|
||||
|
||||
pub fn (mut app App) shutdown() vweb.Result {
|
||||
|
@ -12,6 +12,7 @@ import net.urllib
|
||||
import time
|
||||
import json
|
||||
import encoding.html
|
||||
import context
|
||||
|
||||
// A type which don't get filtered inside templates
|
||||
pub type RawHtml = string
|
||||
@ -144,6 +145,7 @@ pub struct Context {
|
||||
mut:
|
||||
content_type string = 'text/plain'
|
||||
status string = '200 OK'
|
||||
ctx context.Context = context.EmptyContext{}
|
||||
pub:
|
||||
// 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 { '' }
|
||||
}
|
||||
|
||||
// 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
|
||||
|
||||
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
|
||||
ctx := Context{
|
||||
ctx: context.background()
|
||||
req: req
|
||||
page_gen_start: page_gen_start
|
||||
conn: conn
|
||||
|
Loading…
Reference in New Issue
Block a user