diff --git a/cmd/tools/modules/testing/common.v b/cmd/tools/modules/testing/common.v
index 6a5422bbae..302dcb4c4a 100644
--- a/cmd/tools/modules/testing/common.v
+++ b/cmd/tools/modules/testing/common.v
@@ -274,8 +274,6 @@ pub fn new_test_session(_vargs string, will_compile bool) TestSession {
if !os.exists('/usr/local/include/wkhtmltox/pdf.h') {
skip_files << 'examples/c_interop_wkhtmltopdf.v' // needs installation of wkhtmltopdf from https://github.com/wkhtmltopdf/packaging/releases
}
- skip_files << 'vlib/vweb/vweb_app_test.v' // imports the `sqlite` module, which in turn includes sqlite3.h
- skip_files << 'vlib/x/vweb/tests/vweb_app_test.v' // imports the `sqlite` module, which in turn includes sqlite3.h
skip_files << 'vlib/veb/tests/veb_app_test.v' // imports the `sqlite` module, which in turn includes sqlite3.h
}
$if !macos {
diff --git a/cmd/tools/vtest-self.v b/cmd/tools/vtest-self.v
index 5fc8a31144..08fc5656ec 100644
--- a/cmd/tools/vtest-self.v
+++ b/cmd/tools/vtest-self.v
@@ -299,8 +299,6 @@ const skip_on_ubuntu_musl = [
'vlib/v/tests/websocket_logger_interface_should_compile_test.v',
'vlib/v/tests/fns/fn_literal_type_test.v',
'vlib/x/sessions/tests/db_store_test.v',
- 'vlib/x/vweb/tests/vweb_test.v',
- 'vlib/x/vweb/tests/vweb_app_test.v',
'vlib/veb/tests/veb_app_test.v',
]
const skip_on_linux = [
diff --git a/vlib/v/checker/checker.v b/vlib/v/checker/checker.v
index d64a823d57..ac5a2cca98 100644
--- a/vlib/v/checker/checker.v
+++ b/vlib/v/checker/checker.v
@@ -2767,6 +2767,8 @@ fn (mut c Checker) hash_stmt(mut node ast.HashStmt) {
fn (mut c Checker) import_stmt(node ast.Import) {
if node.mod == 'x.vweb' {
println('`x.vweb` is now `veb`. The module is no longer experimental. Simply `import veb` instead of `import x.vweb`.')
+ } else if node.mod == 'vweb' {
+ println('`vweb` has been deprecated. Please use the more stable and fast `veb`has been deprecated. Please use the more stable and fast `veb`` instead of `import x.vweb`.')
}
c.check_valid_snake_case(node.alias, 'module alias', node.pos)
for sym in node.syms {
diff --git a/vlib/x/sessions/README.md b/vlib/x/sessions/README.md
index e3c3066e59..8a1e0869d5 100644
--- a/vlib/x/sessions/README.md
+++ b/vlib/x/sessions/README.md
@@ -8,7 +8,7 @@ The sessions module provides an implementation for [session stores](#custom-stor
The session store handles the saving, storing and retrieving of data. You can
either use a store directly yourself, or you can use the `session.Sessions` struct
which is easier to use since it also handles session verification and integrates nicely
-with vweb.
+with veb.
If you want to use `session.Sessions` in your web app the session id's will be
stored using cookies. The best way to get started is to follow the
@@ -18,10 +18,10 @@ Otherwise have a look at the [advanced usage](#advanced-usage) section.
## Getting Started
-The examples in this section use `x.vweb`. See the [advanced usage](#advanced-usage) section
-for examples without `x.vweb`.
+The examples in this section use `x.veb`. See the [advanced usage](#advanced-usage) section
+for examples without `x.veb`.
-To start using sessions in vweb embed `sessions.CurrentSession` on the
+To start using sessions in veb embed `sessions.CurrentSession` on the
Context struct and add `sessions.Sessions` to the app struct. We must also pass the type
of our session data.
@@ -30,7 +30,7 @@ For any further example code we will use the `User` struct.
```v ignore
import x.sessions
-import x.vweb
+import veb
pub struct User {
pub mut:
@@ -39,7 +39,7 @@ pub mut:
}
pub struct Context {
- vweb.Context
+ veb.Context
// By embedding the CurrentSession struct we can directly access the current session id
// and any associated session data. Set the session data type to `User`
sessions.CurrentSession[User]
@@ -48,18 +48,18 @@ pub struct Context {
pub struct App {
pub mut:
// this struct contains the store that holds all session data it also provides
- // an easy way to manage sessions in your vweb app. Set the session data type to `User`
+ // an easy way to manage sessions in your veb app. Set the session data type to `User`
sessions &sessions.Sessions[User]
}
```
Next we need to create the `&sessions.Sessions[User]` instance for our app. This
-struct provides functionality to easier manage sessions in a vweb app.
+struct provides functionality to easier manage sessions in a veb app.
### Session Stores
To create `sessions.Sessions` We must specify a "store" which handles the session data.
-Currently vweb provides two options for storing session data:
+Currently veb provides two options for storing session data:
1. The `MemoryStore[T]` stores session data in memory only using the `map` datatype.
2. The `DBStore[T]` stores session data in a database by encoding the session data to JSON.
@@ -81,13 +81,13 @@ fn main() {
secret: 'my secret'.bytes()
}
- vweb.run[App, Context](mut app, 8080)
+ veb.run[App, Context](mut app, 8080)
}
```
### Middleware
-The `sessions.vweb2_middleware` module provides a middleware handler. This handler will execute
+The `sessions.veb2_middleware` module provides a middleware handler. This handler will execute
before your own route handlers and will verify the current session and fetch any associated
session data and load it into `sessions.CurrentSession`, which is embedded on the Context struct.
@@ -99,14 +99,14 @@ session data and load it into `sessions.CurrentSession`, which is embedded on th
```v ignore
// add this import at the top of your file
-import x.sessions.vweb2_middleware
+import x.sessions.veb2_middleware
pub struct App {
- // embed the Middleware struct from vweb
- vweb.Middleware[Context]
+ // embed the Middleware struct from veb
+ veb.Middleware[Context]
pub mut:
// this struct contains the store that holds all session data it also provides
- // an easy way to manage sessions in your vweb app. Set the session data type to `User`
+ // an easy way to manage sessions in your veb app. Set the session data type to `User`
sessions &sessions.Sessions[User]
}
@@ -118,13 +118,13 @@ fn main() {
}
// register the sessions middleware
- app.use(vweb2_middleware.create[User, Context](mut app.sessions))
+ app.use(veb2_middleware.create[User, Context](mut app.sessions))
- vweb.run[App, Context](mut app, 8080)
+ veb.run[App, Context](mut app, 8080)
}
```
-You can now start using sessions with vweb!
+You can now start using sessions with veb!
### Usage in endpoint handlers
@@ -137,7 +137,7 @@ if no data is set.
**Example:**
```v ignore
-pub fn (app &App) index(mut ctx Context) vweb.Result {
+pub fn (app &App) index(mut ctx Context) veb.Result {
// check if a user is logged in
if user := ctx.session_data {
return ctx.text('Welcome ${user.name}! Verification status: ${user.verified}')
@@ -160,7 +160,7 @@ method. This method will save the data and *always* set a new session id.
**Example:**
```v ignore
-pub fn (mut app App) login(mut ctx Context) vweb.Result {
+pub fn (mut app App) login(mut ctx Context) veb.Result {
// set a session id cookie and save data for the new user
app.sessions.save(mut ctx, User{
name: '[no name provided]'
@@ -179,7 +179,7 @@ query parameter is not passed an error 400 (bad request) is returned.
**Example:**
```v ignore
-pub fn (mut app App) save(mut ctx Context) vweb.Result {
+pub fn (mut app App) save(mut ctx Context) veb.Result {
// check if there is a session
app.sessions.get(ctx) or { return ctx.request_error('You are not logged in :(') }
@@ -205,7 +205,7 @@ method.
**Example:**
```v ignore
-pub fn (mut app App) logout(mut ctx Context) vweb.Result {
+pub fn (mut app App) logout(mut ctx Context) veb.Result {
app.sessions.logout(mut ctx) or { return ctx.server_error('could not logout, please try again') }
return ctx.text('You are now logged out!')
}
@@ -249,7 +249,7 @@ in, you can set a new session id with `resave`..
## Advanced Usage
If you want to store session id's in another manner than cookies, or if you want
-to use this sessions module outside of vweb, the easiest way is to create an
+to use this sessions module outside of veb, the easiest way is to create an
instance of a `Store` and directly interact with it.
First we create an instance of the `MemoryStore` and pass the user struct as data type.
diff --git a/vlib/x/templating/dtm/README.md b/vlib/x/templating/dtm/README.md
index f7e2f85ae7..6ed97ef9ab 100644
--- a/vlib/x/templating/dtm/README.md
+++ b/vlib/x/templating/dtm/README.md
@@ -62,7 +62,7 @@ HTML tags are always escaped in text file : @html_section
### 2. Minimal Vweb example:
```v
-import x.vweb
+import x.veb
import x.templating.dtm
import os
@@ -72,7 +72,7 @@ pub mut:
}
pub struct Context {
- vweb.Context
+ veb.Context
}
fn main() {
@@ -96,11 +96,11 @@ fn main() {
)
*/
- vweb.run[App, Context](mut app, 18081)
+ veb.run[App, Context](mut app, 18081)
}
@['/']
-pub fn (mut app App) index(mut ctx Context) vweb.Result {
+pub fn (mut app App) index(mut ctx Context) veb.Result {
mut tmpl_var := map[string]dtm.DtmMultiTypeMap{}
tmpl_var['title'] = 'The true title'
html_content := app.dtmi.expand('index.html', placeholders: &tmpl_var)
@@ -125,7 +125,7 @@ pub fn (mut app App) index(mut ctx Context) vweb.Result {
```
You have a ready-to-view demonstration available
-[here](https://github.com/vlang/v/tree/master/vlib/vweb/tests/dynamic_template_manager_test_server).
+[here](https://github.com/vlang/v/tree/master/vlib/veb/tests/dynamic_template_manager_test_server).
## Available Options
diff --git a/vlib/x/vweb/README.md b/vlib/x/vweb/README.md
deleted file mode 100644
index 78838760b5..0000000000
--- a/vlib/x/vweb/README.md
+++ /dev/null
@@ -1,916 +0,0 @@
-# vweb - the V Web Server
-
-A simple yet powerful web server with built-in routing, parameter handling, templating, and other
-features.
-
-## Features
-
-- **Very fast** performance of C on the web.
-- **Templates are precompiled** all errors are visible at compilation time, not at runtime.
-- **Middleware** functionality similar to other big frameworks.
-- **Controllers** to split up your apps logic.
-- **Easy to deploy** just one binary file that also includes all templates. No need to install any
- dependencies.
-
-## Quick Start
-
-Run your vweb app with a live reload via `v -d vweb_livereload watch run .`
-
-Now modifying any file in your web app (whether it's a .v file with the backend logic
-or a compiled .html template file) will result in an instant refresh of your app
-in the browser. No need to quit the app, rebuild it, and refresh the page in the browser!
-
-## Deploying vweb apps
-
-All the code, including HTML templates, is in one binary file. That's all you need to deploy.
-Use the `-prod` flag when building for production.
-
-## Getting Started
-
-To start, you must import the module `x.vweb` and define a structure which will
-represent your app and a structure which will represent the context of a request.
-These structures must be declared with the `pub` keyword.
-
-**Example:**
-
-```v
-module main
-
-import x.vweb
-
-pub struct User {
-pub mut:
- name string
- id int
-}
-
-// Our context struct must embed `vweb.Context`!
-pub struct Context {
- vweb.Context
-pub mut:
- // In the context struct we store data that could be different
- // for each request. Like a User struct or a session id
- user User
- session_id string
-}
-
-pub struct App {
-pub:
- // In the app struct we store data that should be accessible by all endpoints.
- // For example, a database or configuration values.
- secret_key string
-}
-
-// This is how endpoints are defined in vweb. This is the index route
-pub fn (app &App) index(mut ctx Context) vweb.Result {
- return ctx.text('Hello V! The secret key is "${app.secret_key}"')
-}
-
-fn main() {
- mut app := &App{
- secret_key: 'secret'
- }
- // Pass the App and context type and start the web server on port 8080
- vweb.run[App, Context](mut app, 8080)
-}
-```
-
-You can use the `App` struct for data you want to keep during the lifetime of your program,
-or for data that you want to share between different routes.
-
-A new `Context` struct is created every time a request is received,
-so it can contain different data for each request.
-
-## Defining endpoints
-
-To add endpoints to your web server, you must extend the `App` struct.
-For routing you can either use auto-mapping of function names or specify the path as an attribute.
-The function expects a parameter of your Context type and a response of the type `vweb.Result`.
-
-**Example:**
-
-```v ignore
-// This endpoint can be accessed via http://server:port/hello
-pub fn (app &App) hello(mut ctx Context) vweb.Result {
- return ctx.text('Hello')
-}
-
-// This endpoint can be accessed via http://server:port/foo
-@['/foo']
-pub fn (app &App) world(mut ctx Context) vweb.Result {
- return ctx.text('World')
-}
-```
-
-### HTTP verbs
-
-To use any HTTP verbs (or methods, as they are properly called),
-such as `@[post]`, `@[get]`, `@[put]`, `@[patch]` or `@[delete]`
-you can simply add the attribute before the function definition.
-
-**Example:**
-
-```v ignore
-// only GET requests to http://server:port/world are handled by this method
-@[get]
-pub fn (app &App) world(mut ctx Context) vweb.Result {
- return ctx.text('World')
-}
-
-// only POST requests to http://server:port/product/create are handled by this method
-@['/product/create'; post]
-pub fn (app &App) create_product(mut ctx Context) vweb.Result {
- return ctx.text('product')
-}
-```
-
-By default, endpoints are marked as GET requests only. It is also possible to
-add multiple HTTP verbs per endpoint.
-
-**Example:**
-
-```v ignore
-// only GET and POST requests to http://server:port/login are handled by this method
-@['/login'; get; post]
-pub fn (app &App) login(mut ctx Context) vweb.Result {
- if ctx.req.method == .get {
- // show the login page on a GET request
- return ctx.html('
Login page
todo: make form
')
- } else {
- // request method is POST
- password := ctx.form['password']
- // validate password length
- if password.len < 12 {
- return ctx.text('password is too weak!')
- } else {
- // we receive a POST request, so we want to explicitly tell the browser
- // to send a GET request to the profile page.
- return ctx.redirect('/profile')
- }
- }
-}
-```
-
-### Routes with Parameters
-
-Parameters are passed directly to an endpoint route using the colon sign `:`. The route
-parameters are passed as arguments. V will cast the parameter to any of V's primitive types
-(`string`, `int` etc,).
-
-To pass a parameter to an endpoint, you simply define it inside an attribute, e. g.
-`@['/hello/:user]`.
-After it is defined in the attribute, you have to add it as a function parameter.
-
-**Example:**
-
-```v ignore
-// V will pass the parameter 'user' as a string
- vvvv
-@['/hello/:user'] vvvv
-pub fn (app &App) hello_user(mut ctx Context, user string) vweb.Result {
- return ctx.text('Hello ${user}')
-}
-
-// V will pass the parameter 'id' as an int
- vv
-@['/document/:id'] vv
-pub fn (app &App) get_document(mut ctx Context, id int) vweb.Result {
- return ctx.text('Hello ${user}')
-}
-```
-
-If we visit http://localhost:port/hello/vaesel we would see the text `Hello vaesel`.
-
-### Routes with Parameter Arrays
-
-If you want multiple parameters in your route and if you want to parse the parameters
-yourself, or you want a wildcard route, you can add `...` after the `:` and name,
-e.g. `@['/:path...']`.
-
-This will match all routes after `'/'`. For example, the url `/path/to/test` would give
-`path = '/path/to/test'`.
-
-```v ignore
- vvv
-@['/:path...'] vvvv
-pub fn (app &App) wildcard(mut ctx Context, path string) vweb.Result {
- return ctx.text('URL path = "${path}"')
-}
-```
-
-### Query, Form and Files
-
-You have direct access to query values by accessing the `query` field on your context struct.
-You are also able to access any formdata or files that were sent
-with the request with the fields `.form` and `.files` respectively.
-
-In the following example, visiting http://localhost:port/user?name=vweb we
-will see the text `Hello vweb!`. And if we access the route without the `name` parameter,
-http://localhost:port/user, we will see the text `no user was found`,
-
-**Example:**
-
-```v ignore
-@['/user'; get]
-pub fn (app &App) get_user_by_id(mut ctx Context) vweb.Result {
- user_name := ctx.query['name'] or {
- // we can exit early and send a different response if no `name` parameter was passed
- return ctx.text('no user was found')
- }
-
- return ctx.text('Hello ${user_name}!')
-}
-```
-
-### Host
-
-To restrict an endpoint to a specific host, you can use the `host` attribute
-followed by a colon `:` and the host name. You can test the Host feature locally
-by adding a host to the "hosts" file of your device.
-
-**Example:**
-
-```v ignore
-@['/'; host: 'example.com']
-pub fn (app &App) hello_web(mut ctx Context) vweb.Result {
- return app.text('Hello World')
-}
-
-@['/'; host: 'api.example.org']
-pub fn (app &App) hello_api(mut ctx Context) vweb.Result {
- return ctx.text('Hello API')
-}
-
-// define the handler without a host attribute last if you have conflicting paths.
-@['/']
-pub fn (app &App) hello_others(mut ctx Context) vweb.Result {
- return ctx.text('Hello Others')
-}
-```
-
-You can also [create a controller](#controller-with-hostname) to handle all requests from a specific
-host in one app struct.
-
-### Route Matching Order
-
-vweb will match routes in the order that you define endpoints.
-
-**Example:**
-
-```v ignore
-@['/:path']
-pub fn (app &App) with_parameter(mut ctx Context, path string) vweb.Result {
- return ctx.text('from with_parameter, path: "${path}"')
-}
-
-@['/normal']
-pub fn (app &App) normal(mut ctx Context) vweb.Result {
- return ctx.text('from normal')
-}
-```
-
-In this example we defined an endpoint with a parameter first. If we access our app
-on the url http://localhost:port/normal we will not see `from normal`, but
-`from with_parameter, path: "normal"`.
-
-### Custom not found page
-
-You can implement a `not_found` endpoint that is called when a request is made, and no
-matching route is found to replace the default HTTP 404 not found page. This route
-has to be defined on our Context struct.
-
-**Example:**
-
-```v ignore
-pub fn (mut ctx Context) not_found() vweb.Result {
- // set HTTP status 404
- ctx.res.set_status(.not_found)
- return ctx.html('
Page not found!
')
-}
-```
-
-## Static files and website
-
-vweb also provides a way of handling static files. We can mount a folder at the root
-of our web app, or at a custom route. To start using static files we have to embed
-`vweb.StaticHandler` on our app struct.
-
-**Example:**
-
-Let's say you have the following file structure:
-
-```
-.
-├── static/
-│ ├── css/
-│ │ └── main.css
-│ └── js/
-│ └── main.js
-└── main.v
-```
-
-If we want all the documents inside the `static` sub-directory to be publicly accessible, we can
-use `handle_static`.
-
-> **Note:**
-> vweb will recursively search the folder you mount; all the files inside that folder
-> will be publicly available.
-
-_main.v_
-
-```v
-module main
-
-import x.vweb
-
-pub struct Context {
- vweb.Context
-}
-
-pub struct App {
- vweb.StaticHandler
-}
-
-fn main() {
- mut app := &App{}
-
- app.handle_static('static', false)!
-
- vweb.run[App, Context](mut app, 8080)
-}
-```
-
-If we start the app with `v run main.v` we can access our `main.css` file at
-http://localhost:8080/static/css/main.css
-
-### Mounting folders at specific locations
-
-In the previous example the folder `static` was mounted at `/static`. We could also choose
-to mount the static folder at the root of our app: everything inside the `static` folder
-is available at `/`.
-
-**Example:**
-
-```v ignore
-// change the second argument to `true` to mount a folder at the app root
-app.handle_static('static', true)!
-```
-
-We can now access `main.css` directly at http://localhost:8080/css/main.css.
-
-If a request is made to the root of a static folder, vweb will look for an
-`index.html` or `ìndex.htm` file and serve it if available.
-Thus, it's also a good way to host a complete website.
-An example is available [here](/examples/vweb/static_website).
-
-It is also possible to mount the `static` folder at a custom path.
-
-**Example:**
-
-```v ignore
-// mount the folder 'static' at path '/public', the path has to start with '/'
-app.mount_static_folder_at('static', '/public')
-```
-
-If we run our app the `main.css` file is available at http://localhost:8080/public/main.css
-
-### Adding a single static asset
-
-If you don't want to mount an entire folder, but only a single file, you can use `serve_static`.
-
-**Example:**
-
-```v ignore
-// serve the `main.css` file at '/path/main.css'
-app.serve_static('/path/main.css', 'static/css/main.css')!
-```
-
-### Dealing with MIME types
-
-By default, vweb will map the extension of a file to a MIME type. If any of your static file's
-extensions do not have a default MIME type in vweb, vweb will throw an error and you
-have to add your MIME type to `.static_mime_types` yourself.
-
-**Example:**
-
-Let's say you have the following file structure:
-
-```
-.
-├── static/
-│ └── file.what
-└── main.v
-```
-
-```v ignore
-app.handle_static('static', true)!
-```
-
-This code will throw an error, because vweb has no default MIME type for a `.what` file extension.
-
-```
-unknown MIME type for file extension ".what"
-```
-
-To fix this we have to provide a MIME type for the `.what` file extension:
-
-```v ignore
-app.static_mime_types['.what'] = 'txt/plain'
-app.handle_static('static', true)!
-```
-
-## Middleware
-
-Middleware in web development is (loosely defined) a hidden layer that sits between
-what a user requests (the HTTP Request) and what a user sees (the HTTP Response).
-We can use this middleware layer to provide "hidden" functionality to our apps endpoints.
-
-To use vweb's middleware we have to embed `vweb.Middleware` on our app struct and provide
-the type of which context struct should be used.
-
-**Example:**
-
-```v ignore
-pub struct App {
- vweb.Middleware[Context]
-}
-```
-
-### Use case
-
-We could, for example, get the cookies for an HTTP request and check if the user has already
-accepted our cookie policy. Let's modify our Context struct to store whether the user has
-accepted our policy or not.
-
-**Example:**
-
-```v ignore
-pub struct Context {
- vweb.Context
-pub mut:
- has_accepted_cookies bool
-}
-```
-
-In vweb middleware functions take a `mut` parameter with the type of your context struct
-and must return `bool`. We have full access to modify our Context struct!
-
-The return value indicates to vweb whether it can continue or has to stop. If we send a
-response to the client in a middleware function vweb has to stop, so we return `false`.
-
-**Example:**
-
-```v ignore
-pub fn check_cookie_policy(mut ctx Context) bool {
- // get the cookie
- cookie_value := ctx.get_cookie('accepted_cookies') or { '' }
- // check if the cookie has been set
- if cookie_value == 'true' {
- ctx.has_accepted_cookies = true
- }
- // we don't send a response, so we must return true
- return true
-}
-```
-
-We can check this value in an endpoint and return a different response.
-
-**Example:**
-
-```v ignore
-@['/only-cookies']
-pub fn (app &App) only_cookie_route(mut ctx Context) vweb.Result {
- if ctx.has_accepted_cookies {
- return ctx.text('Welcome!')
- } else {
- return ctx.text('You must accept the cookie policy!')
- }
-}
-```
-
-There is one thing left for our middleware to work: we have to register our `only_cookie_route`
-function as middleware for our app. We must do this after the app is created and before the
-app is started.
-
-**Example:**
-
-```v ignore
-fn main() {
- mut app := &App{}
-
- // register middleware for all routes
- app.use(handler: only_cookie_route)
-
- // Pass the App and context type and start the web server on port 8080
- vweb.run[App, Context](mut app, 8080)
-}
-```
-
-### Types of middleware
-
-In the previous example we used so called "global" middleware. This type of middleware
-applies to every endpoint defined on our app struct; global. It is also possible
-to register middleware for only a certain route(s).
-
-**Example:**
-
-```v ignore
-// register middleware only for the route '/auth'
-app.route_use('/auth', handler: auth_middleware)
-// register middleware only for the route '/documents/' with a parameter
-// e.g. '/documents/5'
-app.route_use('/documents/:id')
-// register middleware with a parameter array. The middleware will be registered
-// for all routes that start with '/user/' e.g. '/user/profile/update'
-app.route_use('/user/:path...')
-```
-
-### Evaluation moment
-
-By default, the registered middleware functions are executed *before* a method on your
-app struct is called. You can also change this behaviour to execute middleware functions
-*after* a method on your app struct is called, but before the response is sent!
-
-**Example:**
-
-```v ignore
-pub fn modify_headers(mut ctx Context) bool {
- // add Content-Language: 'en-US' header to each response
- ctx.res.header.add(.content_language, 'en-US')
- return true
-}
-```
-
-```v ignore
-app.use(handler: modify_headers, after: true)
-```
-
-#### When to use which type
-
-You could use "before" middleware to check and modify the HTTP request and you could use
-"after" middleware to validate the HTTP response that will be sent or do some cleanup.
-
-Anything you can do in "before" middleware, you can do in "after" middleware.
-
-### Evaluation order
-
-vweb will handle requests in the following order:
-
-1. Execute global "before" middleware
-2. Execute "before" middleware that matches the requested route
-3. Execute the endpoint handler on your app struct
-4. Execute global "after" middleware
-5. Execute "after" middleware that matches the requested route
-
-In each step, except for step `3`, vweb will evaluate the middleware in the order that
-they are registered; when you call `app.use` or `app.route_use`.
-
-### Early exit
-
-If any middleware sends a response (and thus must return `false`) vweb will not execute any
-other middleware, or the endpoint method, and immediately send the response.
-
-**Example:**
-
-```v ignore
-pub fn early_exit(mut ctx Context) bool {
- ctx.text('early exit')
- // we send a response from middleware, so we have to return false
- return false
-}
-
-pub fn logger(mut ctx Context) bool {
- println('received request for "${ctx.req.url}"')
- return true
-}
-```
-
-```v ignore
-app.use(handler: early_exit)
-app.use(handler: logger)
-```
-
-Because we register `early_exit` before `logger` our logging middleware will never be executed!
-
-## Controllers
-
-Controllers can be used to split up your app logic so you are able to have one struct
-per "route group". E.g. a struct `Admin` for urls starting with `'/admin'` and a struct `Foo`
-for urls starting with `'/foo'`.
-
-To use controllers we have to embed `vweb.Controller` on
-our app struct and when we register a controller we also have to specify
-what the type of the context struct will be. That means that it is possible
-to have a different context struct for each controller and the main app struct.
-
-**Example:**
-
-```v
-module main
-
-import x.vweb
-
-pub struct Context {
- vweb.Context
-}
-
-pub struct App {
- vweb.Controller
-}
-
-// this endpoint will be available at '/'
-pub fn (app &App) index(mut ctx Context) vweb.Result {
- return ctx.text('from app')
-}
-
-pub struct Admin {}
-
-// this endpoint will be available at '/admin/'
-pub fn (app &Admin) index(mut ctx Context) vweb.Result {
- return ctx.text('from admin')
-}
-
-pub struct Foo {}
-
-// this endpoint will be available at '/foo/'
-pub fn (app &Foo) index(mut ctx Context) vweb.Result {
- return ctx.text('from foo')
-}
-
-fn main() {
- mut app := &App{}
-
- // register the controllers the same way as how we start a vweb app
- mut admin_app := &Admin{}
- app.register_controller[Admin, Context]('/admin', mut admin_app)!
-
- mut foo_app := &Foo{}
- app.register_controller[Foo, Context]('/foo', mut foo_app)!
-
- vweb.run[App, Context](mut app, 8080)
-}
-```
-
-You can do everything with a controller struct as with a regular `App` struct.
-Register middleware, add static files and you can even register other controllers!
-
-### Routing
-
-Any route inside a controller struct is treated as a relative route to its controller namespace.
-
-```v ignore
-@['/path']
-pub fn (app &Admin) path(mut ctx Context) vweb.Result {
- return ctx.text('Admin')
-}
-```
-
-When we registered the controller with
-`app.register_controller[Admin, Context]('/admin', mut admin_app)!`
-we told vweb that the namespace of that controller is `'/admin'` so in this example we would
-see the text "Admin" if we navigate to the url `'/admin/path'`.
-
-vweb doesn't support duplicate routes, so if we add the following
-route to the example the code will produce an error.
-
-```v ignore
-@['/admin/path']
-pub fn (app &App) admin_path(mut ctx Context) vweb.Result {
- return ctx.text('Admin overwrite')
-}
-```
-
-There will be an error, because the controller `Admin` handles all routes starting with
-`'/admin'`: the endpoint `admin_path` is unreachable.
-
-### Controller with hostname
-
-You can also set a host for a controller. All requests coming to that host will be handled
-by the controller.
-
-**Example:**
-
-```v ignore
-struct Example {}
-
-// You can only access this route at example.com: http://example.com/
-pub fn (app &Example) index(mut ctx Context) vweb.Result {
- return ctx.text('Example')
-}
-```
-
-```v ignore
-mut example_app := &Example{}
-// set the controllers hostname to 'example.com' and handle all routes starting with '/',
-// we handle requests with any route to 'example.com'
-app.register_controller[Example, Context]('example.com', '/', mut example_app)!
-```
-
-## Context Methods
-
-vweb has a number of utility methods that make it easier to handle requests and send responses.
-These methods are available on `vweb.Context` and directly on your own context struct if you
-embed `vweb.Context`. Below are some of the most used methods, look at the
-[standard library documentation](https://modules.vlang.io/) to see them all.
-
-### Request methods
-
-You can directly access the HTTP request on the `.req` field.
-
-#### Get request headers
-
-**Example:**
-
-```v ignore
-pub fn (app &App) index(mut ctx Context) vweb.Result {
- content_length := ctx.get_header(.content_length) or { '0' }
- // get custom header
- custom_header := ctx.get_custom_header('X-HEADER') or { '' }
- // ...
-}
-```
-
-#### Get a cookie
-
-**Example:**
-
-```v ignore
-pub fn (app &App) index(mut ctx Context) vweb.Result {
- cookie_val := ctx.get_cookie('token') or { '' }
- // ...
-}
-```
-
-### Response methods
-
-You can directly modify the HTTP response by changing the `res` field,
-which is of the type `http.Response`.
-
-#### Send response with different MIME types
-
-```v ignore
-// send response HTTP_OK with content-type `text/html`
-ctx.html('
Hello world!
')
-// send response HTTP_OK with content-type `text/plain`
-ctx.text('Hello world!')
-// stringify the object and send response HTTP_OK with content-type `application/json`
-ctx.json(User{
- name: 'test'
- age: 20
-})
-```
-
-#### Sending files
-
-**Example:**
-
-```v ignore
-pub fn (app &App) file_response(mut ctx Context) vweb.Result {
- // send the file 'image.png' in folder 'data' to the user
- return ctx.file('data/image.png')
-}
-```
-
-#### Set response headers
-
-**Example:**
-
-```v ignore
-pub fn (app &App) index(mut ctx Context) vweb.Result {
- ctx.set_header(.accept, 'text/html')
- // set custom header
- ctx.set_custom_header('X-HEADER', 'my-value')!
- // ...
-}
-```
-
-#### Set a cookie
-
-**Example:**
-
-```v ignore
-pub fn (app &App) index(mut ctx Context) vweb.Result {
- ctx.set_cookie(http.Cookie{
- name: 'token'
- value: 'true'
- path: '/'
- secure: true
- http_only: true
- })
- // ...
-}
-```
-
-#### Redirect
-
-You must pass the type of redirect to vweb:
-
-- `moved_permanently` HTTP code 301
-- `found` HTTP code 302
-- `see_other` HTTP code 303
-- `temporary_redirect` HTTP code 307
-- `permanent_redirect` HTTP code 308
-
-**Common use cases:**
-
-If you want to change the request method, for example when you receive a post request and
-want to redirect to another page via a GET request, you should use `see_other`. If you want
-the HTTP method to stay the same, you should use `found` generally speaking.
-
-**Example:**
-
-```v ignore
-pub fn (app &App) index(mut ctx Context) vweb.Result {
- token := ctx.get_cookie('token') or { '' }
- if token == '' {
- // redirect the user to '/login' if the 'token' cookie is not set
- // we explicitly tell the browser to send a GET request
- return ctx.redirect('/login', typ: .see_other)
- } else {
- return ctx.text('Welcome!')
- }
-}
-```
-
-#### Sending error responses
-
-**Example:**
-
-```v ignore
-pub fn (app &App) login(mut ctx Context) vweb.Result {
- if username := ctx.form['username'] {
- return ctx.text('Hello "${username}"')
- } else {
- // send an HTTP 400 Bad Request response with a message
- return ctx.request_error('missing form value "username"')
- }
-}
-```
-
-You can also use `ctx.server_error(msg string)` to send an HTTP 500 internal server
-error with a message.
-
-## Advanced usage
-
-If you need more control over the TCP connection with a client, for example when
-you want to keep the connection open. You can call `ctx.takeover_conn`.
-
-When this function is called you are free to do anything you want with the TCP
-connection and vweb will not interfere. This means that we are responsible for
-sending a response over the connection and closing it.
-
-### Empty Result
-
-Sometimes you want to send the response in another thread, for example when using
-[Server Sent Events](sse/README.md). When you are sure that a response will be sent
-over the TCP connection you can return `vweb.no_result()`. This function does nothing
-and returns an empty `vweb.Result` struct, letting vweb know that we sent a response ourselves.
-
-> **Note:**
-> It is important to call `ctx.takeover_conn` before you spawn a thread
-
-**Example:**
-
-```v
-module main
-
-import net
-import time
-import x.vweb
-
-pub struct Context {
- vweb.Context
-}
-
-pub struct App {}
-
-pub fn (app &App) index(mut ctx Context) vweb.Result {
- return ctx.text('hello!')
-}
-
-@['/long']
-pub fn (app &App) long_response(mut ctx Context) vweb.Result {
- // let vweb know that the connection should not be closed
- ctx.takeover_conn()
- // use spawn to handle the connection in another thread
- // if we don't the whole web server will block for 10 seconds,
- // since vweb is singlethreaded
- spawn handle_connection(mut ctx.conn)
- // we will send a custom response ourselves, so we can safely return an empty result
- return vweb.no_result()
-}
-
-fn handle_connection(mut conn net.TcpConn) {
- defer {
- conn.close() or {}
- }
- // block for 10 second
- time.sleep(time.second * 10)
- conn.write_string('HTTP/1.1 200 OK\r\nContent-type: text/html\r\nContent-length: 15\r\n\r\nHello takeover!') or {}
-}
-
-fn main() {
- mut app := &App{}
- vweb.run[App, Context](mut app, 8080)
-}
-```
diff --git a/vlib/x/vweb/assets/README.md b/vlib/x/vweb/assets/README.md
deleted file mode 100644
index 2dc6553114..0000000000
--- a/vlib/x/vweb/assets/README.md
+++ /dev/null
@@ -1,177 +0,0 @@
-# Assets
-
-The asset manager for vweb. You can use this asset manager to minify CSS and JavaScript files,
-combine them into a single file and to make sure the asset you're using exists.
-
-## Usage
-
-Add `AssetManager` to your App struct to use the asset manager.
-
-**Example:**
-
-```v
-module main
-
-import x.vweb
-import x.vweb.assets
-
-pub struct Context {
- vweb.Context
-}
-
-pub struct App {
-pub mut:
- am assets.AssetManager
-}
-
-fn main() {
- mut app := &App{}
- vweb.run[App, Context](mut app, 8080)
-}
-```
-
-### Including assets
-
-If you want to include an asset in your templates you can use the `include` method.
-First pass the type of asset (css or js), then specify the "include name" of an asset.
-
-**Example:**
-
-```html
-@{app.am.include(.css, 'main.css')}
-```
-
-Will generate
-
-```html
-
-```
-
-### Adding assets
-
-To add an asset use the `add` method. You must specify the path of the asset and what its
-include name will be: the name that you will use in templates.
-
-**Example:**
-
-```v ignore
-// add a css file at the path "css/main.css" and set its include name to "main.css"
-app.am.add(.css, 'css/main.css', 'main.css')
-```
-
-### Minify and Combine assets
-
-If you want to minify each asset you must set the `minify` field and specify the cache
-folder. Each assest you add is minifed and outputted in `cache_dir`.
-
-**Example:**
-
-```v ignore
-pub struct App {
-pub mut:
- am assets.AssetManager = assets.AssetManager{
- cache_dir: 'dist'
- minify: true
- }
-}
-```
-
-To combine the all currently added assets into a single file you must call the `combine` method
-and specify which asset type you want to combine.
-
-**Example:**
-
-```v ignore
-// `combine` returns the path of the minified file
-minified_file := app.am.combine(.css)!
-```
-
-### Handle folders
-
-You can use the asset manger in combination with vweb's `StaticHandler` to serve
-assets in a folder as static assets.
-
-**Example:**
-
-```v ignore
-pub struct App {
- vweb.StaticHandler
-pub mut:
- am assets.AssetManager
-}
-```
-
-Let's say we have the following folder structure:
-
-```
-assets/
-├── css/
-│ └── main.css
-└── js/
- └── main.js
-```
-
-We can tell the asset manager to add all assets in the `static` folder
-
-**Example:**
-
-```v ignore
-fn main() {
- mut app := &App{}
- // add all assets in the "assets" folder
- app.am.handle_assets('assets')!
- // serve all files in the "assets" folder as static files
- app.handle_static('assets', false)!
- // start the app
- vweb.run[App, Context](mut app, 8080)
-}
-```
-
-The include name of each minified asset will be set to its relative path,
-so if you want to include `main.css` in your template you would write
-`@{app.am.include('css/main.css')}`
-
-#### Minify
-
-If you add an asset folder and want to minify those assets you can call the
-`cleanup_cache` method to remove old files from the cache folder
-that are no longer needed.
-
-**Example:**
-
-```v ignore
-pub struct App {
- vweb.StaticHandler
-pub mut:
- am assets.AssetManager = assets.AssetManager{
- cache_dir: 'dist'
- minify: true
- }
-}
-
-fn main() {
- mut app := &App{}
- // add all assets in the "assets" folder
- app.am.handle_assets('assets')!
- // remove all old cached files from the cache folder
- app.am.cleanup_cache()!
- // serve all files in the "assets" folder as static files
- app.handle_static('assets', false)!
- // start the app
- vweb.run[App, Context](mut app, 8080)
-}
-```
-
-#### Prefix the include name
-
-You can add a custom prefix to the include name of assets when adding a folder.
-
-**Example:**
-
-```v ignore
-// add all assets in the "assets" folder
-app.am.handle_assets_at('assets', 'static')!
-```
-
-Now if you want to include `main.css` you would write
-``@{app.am.include('static/css/main.css')}`
diff --git a/vlib/x/vweb/assets/assets.v b/vlib/x/vweb/assets/assets.v
deleted file mode 100644
index 477cc1f158..0000000000
--- a/vlib/x/vweb/assets/assets.v
+++ /dev/null
@@ -1,311 +0,0 @@
-module assets
-
-import crypto.md5
-import os
-import strings
-import time
-import x.vweb
-
-pub enum AssetType {
- css
- js
- all
-}
-
-pub struct Asset {
-pub:
- kind AssetType
- file_path string
- last_modified time.Time
- include_name string
-}
-
-pub struct AssetManager {
-mut:
- css []Asset
- js []Asset
- cached_file_names []string
-pub mut:
- // when true assets will be minified
- minify bool
- // the directory to store the cached/combined files
- cache_dir string
- // how a combined file should be named. For example for css the extension '.css'
- // will be added to the end of `combined_file_name`
- combined_file_name string = 'combined'
-}
-
-fn (mut am AssetManager) add_asset_directory(directory_path string, traversed_path string) ! {
- files := os.ls(directory_path)!
- if files.len > 0 {
- for file in files {
- full_path := os.join_path(directory_path, file)
- relative_path := os.join_path(traversed_path, file)
-
- if os.is_dir(full_path) {
- am.add_asset_directory(full_path, relative_path)!
- } else {
- ext := os.file_ext(full_path)
- match ext {
- '.css' { am.add(.css, full_path, relative_path)! }
- '.js' { am.add(.js, full_path, relative_path)! }
- // ignore non css/js files
- else {}
- }
- }
- }
- }
-}
-
-// handle_assets recursively walks `directory_path` and adds any assets to the asset manager
-pub fn (mut am AssetManager) handle_assets(directory_path string) ! {
- return am.add_asset_directory(directory_path, '')
-}
-
-// handle_assets_at recursively walks `directory_path` and adds any assets to the asset manager.
-// The include name of assets are prefixed with `prepend`
-pub fn (mut am AssetManager) handle_assets_at(directory_path string, prepend string) ! {
- // remove trailing '/'
- return am.add_asset_directory(directory_path, prepend.trim_right('/'))
-}
-
-// get all assets of type `asset_type`
-pub fn (am AssetManager) get_assets(asset_type AssetType) []Asset {
- return match asset_type {
- .css {
- am.css
- }
- .js {
- am.js
- }
- .all {
- mut assets := []Asset{}
- assets << am.css
- assets << am.js
- assets
- }
- }
-}
-
-// add an asset to the asset manager
-pub fn (mut am AssetManager) add(asset_type AssetType, file_path string, include_name string) ! {
- if asset_type == .all {
- return error('cannot minify asset of type "all"')
- }
- if !os.exists(file_path) {
- return error('cnanot add asset: file "${file_path}" does not exist')
- }
-
- last_modified_unix := os.file_last_mod_unix(file_path)
-
- mut real_path := file_path
-
- if am.minify {
- // minify and cache file if it was modified
- output_path, is_cached := am.minify_and_cache(asset_type, real_path, last_modified_unix,
- include_name)!
-
- if is_cached == false && am.exists(asset_type, include_name) {
- // file was not modified between the last call to `add`
- // and the file was already in the asset manager, so we don't need to
- // add it again
- return
- }
-
- real_path = output_path
- }
-
- asset := Asset{
- kind: asset_type
- file_path: real_path
- last_modified: time.unix(last_modified_unix)
- include_name: include_name
- }
-
- match asset_type {
- .css { am.css << asset }
- .js { am.js << asset }
- else {}
- }
-}
-
-fn (mut am AssetManager) minify_and_cache(asset_type AssetType, file_path string, last_modified i64, include_name string) !(string, bool) {
- if asset_type == .all {
- return error('cannot minify asset of type "all"')
- }
-
- if am.cache_dir == '' {
- return error('cannot minify asset: cache directory is not valid')
- } else if !os.exists(am.cache_dir) {
- os.mkdir_all(am.cache_dir)!
- }
-
- cache_key := am.get_cache_key(file_path, last_modified)
- output_file := '${cache_key}.${asset_type}'
- output_path := os.join_path(am.cache_dir, output_file)
-
- if os.exists(output_path) {
- // the output path already exists, this means that the file has
- // been minifed and cached before and hasn't changed in the meantime
- am.cached_file_names << output_file
- return output_path, false
- } else {
- // check if the file has been minified before, but is modified.
- // if that's the case we remove the old cached file
- cached_files := os.ls(am.cache_dir)!
- hash := cache_key.all_before('-')
- for file in cached_files {
- if file.starts_with(hash) {
- os.rm(os.join_path(am.cache_dir, file))!
- }
- }
- }
-
- txt := os.read_file(file_path)!
- minified := match asset_type {
- .css { minify_css(txt) }
- .js { minify_js(txt) }
- else { '' }
- }
- os.write_file(output_path, minified)!
-
- am.cached_file_names << output_file
- return output_path, true
-}
-
-fn (mut am AssetManager) get_cache_key(file_path string, last_modified i64) string {
- abs_path := if os.is_abs_path(file_path) { file_path } else { os.resource_abs_path(file_path) }
- hash := md5.sum(abs_path.bytes())
- return '${hash.hex()}-${last_modified}'
-}
-
-// cleanup_cache removes all files in the cache directory that aren't cached at the time
-// this function is called
-pub fn (mut am AssetManager) cleanup_cache() ! {
- if am.cache_dir == '' {
- return error('[vweb.assets]: cache directory is not valid')
- }
- cached_files := os.ls(am.cache_dir)!
-
- // loop over all the files in the cache directory. If a file isn't cached, remove it
- for file in cached_files {
- ext := os.file_ext(file)
- if ext !in ['.css', '.js'] || file in am.cached_file_names {
- continue
- } else if !file.starts_with(am.combined_file_name) {
- os.rm(os.join_path(am.cache_dir, file))!
- }
- }
-}
-
-// check if an asset is already added to the asset manager
-pub fn (am AssetManager) exists(asset_type AssetType, include_name string) bool {
- assets := am.get_assets(asset_type)
-
- return assets.any(it.include_name == include_name)
-}
-
-// include css/js files in your vweb app from templates
-// Example:
-// ```html
-// @{app.am.include(.css, 'main.css')}
-// ```
-pub fn (am AssetManager) include(asset_type AssetType, include_name string) vweb.RawHtml {
- assets := am.get_assets(asset_type)
- for asset in assets {
- if asset.include_name == include_name {
- // always add link/src from root of web server ('/css/main.css'),
- // but leave absolute paths intact
- mut real_path := asset.file_path
- if real_path[0] != `/` && !os.is_abs_path(real_path) {
- real_path = '/${asset.file_path}'
- }
-
- return match asset_type {
- .css {
- ''
- }
- .js {
- ''
- }
- else {
- eprintln('[vweb.assets] can only include css or js assets')
- ''
- }
- }
- }
- }
- eprintln('[vweb.assets] no asset with include name "${include_name}" exists!')
- return ''
-}
-
-// combine assets of type `asset_type` into a single file and return the outputted file path.
-// If you call `combine` with asset type `all` the function will return an empty string,
-// the minified files will be available at `combined_file_name`.`asset_type`
-pub fn (mut am AssetManager) combine(asset_type AssetType) !string {
- if asset_type == .all {
- am.combine(.css)!
- am.combine(.js)!
- return ''
- }
- if am.cache_dir == '' {
- return error('cannot combine assets: cache directory is not valid')
- } else if !os.exists(am.cache_dir) {
- os.mkdir_all(am.cache_dir)!
- }
-
- assets := am.get_assets(asset_type)
- combined_file_path := os.join_path(am.cache_dir, '${am.combined_file_name}.${asset_type}')
- mut f := os.create(combined_file_path)!
-
- for asset in assets {
- bytes := os.read_bytes(asset.file_path)!
- f.write(bytes)!
- f.write_string('\n')!
- }
-
- f.close()
-
- return combined_file_path
-}
-
-// TODO: implement proper minification
-@[manualfree]
-pub fn minify_css(css string) string {
- mut lines := css.split('\n')
- // estimate arbitrary number of characters for a line of css
- mut sb := strings.new_builder(lines.len * 20)
- defer {
- unsafe { sb.free() }
- }
-
- for line in lines {
- trimmed := line.trim_space()
- if trimmed != '' {
- sb.write_string(trimmed)
- }
- }
-
- return sb.str()
-}
-
-// TODO: implement proper minification
-@[manualfree]
-pub fn minify_js(js string) string {
- mut lines := js.split('\n')
- // estimate arbitrary number of characters for a line of js
- mut sb := strings.new_builder(lines.len * 40)
- defer {
- unsafe { sb.free() }
- }
-
- for line in lines {
- trimmed := line.trim_space()
- if trimmed != '' {
- sb.write_string(trimmed)
- sb.write_u8(` `)
- }
- }
-
- return sb.str()
-}
diff --git a/vlib/x/vweb/assets/assets_test.v b/vlib/x/vweb/assets/assets_test.v
deleted file mode 100644
index 747f0237db..0000000000
--- a/vlib/x/vweb/assets/assets_test.v
+++ /dev/null
@@ -1,190 +0,0 @@
-import x.vweb.assets
-import os
-
-const base_cache_dir = os.join_path(os.vtmp_dir(), 'xvweb_assets_test_cache')
-
-fn testsuite_begin() {
- os.mkdir_all(base_cache_dir) or {}
-}
-
-fn testsuite_end() {
- os.rmdir_all(base_cache_dir) or {}
-}
-
-// clean_cache_dir used before and after tests that write to a cache directory.
-// Because of parallel compilation and therefore test running,
-// unique cache dirs are needed per test function.
-fn clean_cache_dir(dir string) {
- os.rmdir_all(dir) or {}
-}
-
-fn cache_dir(test_name string) string {
- return os.join_path(base_cache_dir, test_name)
-}
-
-fn get_test_file_path(file string) string {
- path := os.join_path(base_cache_dir, file)
- os.rm(path) or {}
- os.write_file(path, get_test_file_contents(file)) or { panic(err) }
- return path
-}
-
-fn get_test_file_contents(file string) string {
- contents := match file {
- 'test1.js' { '{"one": 1}\n' }
- 'test2.js' { '{"two": 2}\n' }
- 'test1.css' { '.one {\n\tcolor: #336699;\n}\n' }
- 'test2.css' { '.two {\n\tcolor: #996633;\n}\n' }
- else { 'wibble\n' }
- }
- return contents
-}
-
-fn test_add() {
- mut am := assets.AssetManager{}
-
- mut errored := false
- am.add(.css, 'test.css', 'test.css') or { errored = true }
- assert errored == true, 'am.add should error'
-
- errored = false
- am.add(.css, get_test_file_path('test1.css'), 'included.css') or {
- eprintln(err)
- errored = true
- }
- assert errored == false, 'am.add should not error'
-
- css_assets := am.get_assets(.css)
- assert css_assets.len == 1
- assert css_assets[0].file_path == get_test_file_path('test1.css')
- assert css_assets[0].include_name == 'included.css'
-}
-
-fn test_add_minify_missing_cache_dir() {
- mut am := assets.AssetManager{
- minify: true
- }
- mut errored := false
- am.add(.js, get_test_file_path('test1.css'), 'included.js') or {
- assert err.msg() == 'cannot minify asset: cache directory is not valid'
- errored = true
- }
-
- assert errored == true, 'am.add should return an error'
-}
-
-fn test_add_minified() {
- mut am := assets.AssetManager{
- minify: true
- cache_dir: cache_dir('test_add_minified')
- }
- clean_cache_dir(am.cache_dir)
-
- am.add(.js, get_test_file_path('test1.js'), 'included.js')!
-
- js_assets := am.get_assets(.js)
- assert js_assets.len == 1
- assert js_assets[0].file_path.starts_with(am.cache_dir) == true
-}
-
-fn test_combine() {
- mut am := assets.AssetManager{
- cache_dir: cache_dir('test_combine')
- }
- clean_cache_dir(am.cache_dir)
-
- am.add(.css, get_test_file_path('test1.css'), 'test1.css')!
- am.add(.css, get_test_file_path('test2.css'), 'test2.css')!
-
- combined_path := am.combine(.css)!
- combined := os.read_file(combined_path)!
-
- expected := get_test_file_contents('test1.css') + '\n' + get_test_file_contents('test2.css') +
- '\n'
- assert combined == expected
-}
-
-fn test_combine_minified() {
- // minify test is simple for now, because assets are not properly minified yet
- mut am := assets.AssetManager{
- cache_dir: cache_dir('test_combine_minified')
- minify: true
- }
- clean_cache_dir(am.cache_dir)
-
- am.add(.css, get_test_file_path('test1.css'), 'test1.css')!
- am.add(.css, get_test_file_path('test2.css'), 'test2.css')!
-
- combined_path := am.combine(.css)!
- combined := os.read_file(combined_path)!
-
- // minified version should be 2 lines + one extra newline
- assert combined.split('\n').len == 3
-}
-
-fn test_minify_cache_last_modified() {
- mut am := assets.AssetManager{
- minify: true
- cache_dir: cache_dir('test_cache_last_modified')
- }
- clean_cache_dir(am.cache_dir)
-
- // first we write the file and add it
- am.add(.js, get_test_file_path('test1.js'), 'included.js')!
- mut js_assets := am.get_assets(.js)
- assert js_assets.len == 1
- old_cached_path := js_assets[0].file_path
-
- // then we only add the file, the file is not modified so the "last modified is the same".
- // we expect that the asset manager doesn't cache a minified file if it hasn't been changed
- // the last time it was added
- am.add(.js, os.join_path(base_cache_dir, 'test1.js'), 'included.js')!
-
- js_assets = am.get_assets(.js)
- // check if the file isn't added twice
- assert js_assets.len == 1
- // if the file path was not modified, vweb.assets didn't overwrite the file
- assert js_assets[0].file_path == old_cached_path
-}
-
-fn test_cleanup_cache() {
- mut am := assets.AssetManager{
- minify: true
- cache_dir: cache_dir('test_cleanup_cache')
- }
- clean_cache_dir(am.cache_dir)
- // manually make the cache dir
- os.mkdir_all(am.cache_dir) or {}
-
- // write a file to the cache dir isn't added to the asset manager to represent
- // a previously cached file
- path1 := os.join_path(am.cache_dir, 'test1.css')
- os.write_file(path1, 'h1 { color: red; }')!
- assert os.exists(path1) == true
-
- // add a file to the asset manager and write it
- am.add(.css, get_test_file_path('test2.css'), 'test2.css')!
- css_assets := am.get_assets(.css)
- // get the cached path
- assert css_assets.len == 1
- path2 := css_assets[0].file_path
- assert os.exists(path2) == true
-
- am.cleanup_cache()!
-
- // the first asset wasn't added to the asset manager, so it should not exist
- assert os.exists(path1) == false
- assert os.exists(path2) == true
-}
-
-fn test_include() {
- mut am := assets.AssetManager{}
-
- css_path := get_test_file_path('test1.css')
- js_path := get_test_file_path('test1.js')
- am.add(.css, css_path, 'other.css')!
- am.add(.js, js_path, 'js/test.js')!
-
- assert am.include(.css, 'other.css') == ''
- assert am.include(.js, 'js/test.js') == ''
-}
diff --git a/vlib/x/vweb/context.v b/vlib/x/vweb/context.v
deleted file mode 100644
index 54fd2e36db..0000000000
--- a/vlib/x/vweb/context.v
+++ /dev/null
@@ -1,312 +0,0 @@
-module vweb
-
-import json
-import net
-import net.http
-import os
-
-enum ContextReturnType {
- normal
- file
-}
-
-pub enum RedirectType {
- found = int(http.Status.found)
- moved_permanently = int(http.Status.moved_permanently)
- see_other = int(http.Status.see_other)
- temporary_redirect = int(http.Status.temporary_redirect)
- permanent_redirect = int(http.Status.permanent_redirect)
-}
-
-// The Context struct represents the Context which holds the HTTP request and response.
-// It has fields for the query, form, files and methods for handling the request and response
-@[heap]
-pub struct Context {
-mut:
- // vweb will try to infer the content type base on file extension,
- // and if `content_type` is not empty the `Content-Type` header will always be
- // set to this value
- content_type string
- // done is set to true when a response can be sent over `conn`
- done bool
- // if true the response should not be sent and the connection should be closed
- // manually.
- takeover bool
- // how the http response should be handled by vweb's backend
- return_type ContextReturnType = .normal
- return_file string
- // If the `Connection: close` header is present the connection should always be closed
- client_wants_to_close bool
-pub:
- // TODO: move this to `handle_request`
- // time.ticks() from start of vweb connection handle.
- // You can use it to determine how much time is spent on your request.
- page_gen_start i64
-pub mut:
- req http.Request
- custom_mime_types map[string]string
- // TCP connection to client. Only for advanced usage!
- conn &net.TcpConn = unsafe { nil }
- // Map containing query params for the route.
- // http://localhost:3000/index?q=vpm&order_by=desc => { 'q': 'vpm', 'order_by': 'desc' }
- query map[string]string
- // Multipart-form fields.
- form map[string]string
- // Files from multipart-form.
- files map[string][]http.FileData
- res http.Response
- // use form_error to pass errors from the context to your frontend
- form_error string
- livereload_poll_interval_ms int = 250
-}
-
-// returns the request header data from the key
-pub fn (ctx &Context) get_header(key http.CommonHeader) !string {
- return ctx.req.header.get(key)!
-}
-
-// returns the request header data from the key
-pub fn (ctx &Context) get_custom_header(key string) !string {
- return ctx.req.header.get_custom(key)!
-}
-
-// set a header on the response object
-pub fn (mut ctx Context) set_header(key http.CommonHeader, value string) {
- ctx.res.header.set(key, value)
-}
-
-// set a custom header on the response object
-pub fn (mut ctx Context) set_custom_header(key string, value string) ! {
- ctx.res.header.set_custom(key, value)!
-}
-
-// send_response_to_client finalizes the response headers and sets Content-Type to `mimetype`
-// and the response body to `response`
-pub fn (mut ctx Context) send_response_to_client(mimetype string, response string) Result {
- if ctx.done && !ctx.takeover {
- eprintln('[vweb] a response cannot be sent twice over one connection')
- return Result{}
- }
- // ctx.done is only set in this function, so in order to sent a response over the connection
- // this value has to be set to true. Assuming the user doesn't use `ctx.conn` directly.
- ctx.done = true
- ctx.res.body = response
- $if vweb_livereload ? {
- if mimetype == 'text/html' {
- ctx.res.body = response.replace('