Skip to content

Commit

Permalink
context, vweb: add ability to set and get values on vweb.Context (#18564
Browse files Browse the repository at this point in the history
)
  • Loading branch information
Casper64 committed Jun 26, 2023
1 parent 7a9c885 commit 21d9730
Show file tree
Hide file tree
Showing 6 changed files with 153 additions and 3 deletions.
31 changes: 30 additions & 1 deletion vlib/context/context.v
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
4 changes: 2 additions & 2 deletions vlib/context/empty_test.v
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,15 @@ 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')
}
}

fn test_todo() {
ctx := todo()
assert 'context.TODO' == ctx.str()
assert '&context.TODO' == ctx.str()
if _ := ctx.value('') {
panic('This should never happen')
}
Expand Down
75 changes: 75 additions & 0 deletions vlib/vweb/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
7 changes: 7 additions & 0 deletions vlib/vweb/tests/middleware_test.v
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
12 changes: 12 additions & 0 deletions vlib/vweb/tests/middleware_test_server.v
Original file line number Diff line number Diff line change
Expand Up @@ -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.')
Expand Down Expand Up @@ -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() {
Expand Down Expand Up @@ -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 {
Expand Down
27 changes: 27 additions & 0 deletions vlib/vweb/vweb.v
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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
Expand Down

0 comments on commit 21d9730

Please sign in to comment.