Documentation
¶
Overview ¶
Package web serves an app's pages. Pages are templates under pages/, and aicoded generate turns them into code that uses this package for routing, access rules, forms, live values and page calls. App code uses the rest: the Request and ResponseWriter its hooks get, the errors that end a request, and SafeHTML.
Each page has a data provider, DP in its dataprovider.go. For a request, the hooks of the pages on the path run root first, in this order: the access rules of <ssr:access>, then every Guard, then, on a post, the check of the form's token, and then Init<Form>, Process<Form> (only for the posted form, and only when all its fields are valid) and Data of the layouts on the path and of the page itself. A live connection and a page call pass the same access rules and Guards first.
A hook ends a request with NotFound, Forbidden, Error or Redirect. Only the message of an Error reaches the viewer. Any other error answers 500 "Something went wrong." and is logged, with its text under aicoded dev in environment `dev` and only its kinds and codes anywhere else. Redirect goes only to a path on this site (E-WEB-002).
{{ }} in a template escapes a value for where it lands: text, an attribute or a URL. {{$ }} writes markup as it is and takes only a SafeHTML, made with HTMLConst, EscapeHTML or JoinHTML. Every response carries a strict Content-Security-Policy and the other security headers, pages are never cached, and a form posted from another site or without its token is refused with 403.
Read more in the guides docs/guides/web-api.md, docs/guides/request-pipeline.md, docs/guides/routing.md and docs/guides/access.md, which aicoded explain and the MCP tool howto print as guides/web-api, guides/request-pipeline, guides/routing and guides/access.
Index ¶
- Constants
- func CheckNoGuard(dp any, path string)
- func DecodeArgs(raw json.RawMessage, v any) error
- func Error(status int, message string) error
- func Forbidden() error
- func LogError(ctx context.Context, msg string, err error, attrs ...any)
- func New(routes map[string]Route, opts Options) http.Handler
- func NotFound() error
- func Redirect(path string) error
- type Access
- type Caller
- type Frame
- type HTTPError
- type Options
- type Reactive
- type Request
- type ResponseWriter
- type Route
- type RouteState
- type SafeHTML
Examples ¶
Constants ¶
const ( CSRFField = "_aicoded_csrf" FormField = "_aicoded_form" )
Names of the hidden inputs every generated form carries.
Variables ¶
This section is empty.
Functions ¶
func CheckNoGuard ¶
CheckNoGuard panics with E-WEB-007 when dp, the data provider of the route at path, has a method named Guard, of any signature, its own or one from an embedded type, while the route's <ssr:access> has no guard="true", so the framework would never call it. Generated NewRoute calls it; apps do not.
Generated code only.
func DecodeArgs ¶
func DecodeArgs(raw json.RawMessage, v any) error
DecodeArgs reads the arguments of a page call into v. Unknown fields and trailing data are refused, so a page cannot send more than the call declares.
Generated code only.
func Error ¶
Error ends the request with status, from 400 to 599, and shows message to the viewer. Never put internal details in message.
Example ¶
A hook returns web.Error to show the viewer a message. In Validate<Name>, which checks a value the page writes, the message reaches the page's ssr.onError.
package main
import (
"fmt"
"net/http"
"strings"
"unicode/utf8"
"aicoded.dev/framework/web"
)
func main() {
validateDisplayName := func(name string) (string, error) {
if utf8.RuneCountInString(name) > 50 {
return "", web.Error(http.StatusUnprocessableEntity, "Use at most 50 characters.")
}
return name, nil
}
_, err := validateDisplayName(strings.Repeat("a", 51))
fmt.Println(err)
}
Output: 422 Use at most 50 characters.
func LogError ¶
LogError logs msg at the error level with attrs, and err as "err": its text under aicoded dev in environment `dev`, and only its kinds and codes anywhere else.
Generated code only.
func New ¶
New returns the handler for an app's pages. routes maps paths such as "/notes/n_id" to their generated routes; generated code calls it. New panics if a folder has two parameter folders.
Generated code only.
func Redirect ¶
Redirect sends the viewer to path, a path on this site such as "/notes/42".
Example ¶
A Process hook sends the viewer on after a post with return web.Redirect("/notes/42"). Redirect goes only to a path on this site and refuses any other target.
package main
import (
"fmt"
"aicoded.dev/framework/web"
)
func main() {
err := web.Redirect("https://elsewhere.example/")
fmt.Println(err)
}
Output: E-WEB-002: web.Redirect got a target that is not a path on this site fix: redirect to a path that starts with a single "/", such as "/notes/42" docs: https://aicoded.dev/docs/errors/E-WEB-002
Types ¶
type Access ¶
type Access struct {
Roles []string
}
Access is a route's <ssr:access> rule: the viewer needs one of Roles. The role "*" admits every viewer the company login lets into the app.
type Caller ¶
type Caller interface {
RouteKey() string
// Call decodes args and runs the route's Call<Name> hook.
Call(ctx context.Context, r *Request, name string, args json.RawMessage) (any, error)
}
Caller is implemented by the state of a route with <ssr:call> functions.
type Frame ¶
type Frame struct {
// contains filtered or unexported fields
}
Frame joins a route's state to the route rendered inside it. Generated states embed it.
func (*Frame) WriteAssets ¶
WriteAssets writes the asset tags of this route and of every route below it, each once.
type Options ¶
type Options struct {
// Assets holds the scripts, styles and images served under /_aicoded/assets/.
Assets fs.FS
}
Options configure the pages handler.
type Reactive ¶
type Reactive interface {
RouteKey() string
// Snapshot returns the current value of every live site of the route.
Snapshot(ctx context.Context) map[string]reactive.Binding
// Subscribe runs the route's Subscribe hook for as long as the connection lives.
Subscribe(ctx context.Context, r *Request, conn *reactive.Conn) error
// HandleWrite applies a value the page wrote to one of the route's variables.
HandleWrite(ctx context.Context, r *Request, conn *reactive.Conn, msg reactive.WriteMsg)
}
Reactive is implemented by the state of a route with live values.
type Request ¶
Request is a request as the hooks of a page see it.
func (*Request) CSRFToken ¶
CSRFToken returns the token the forms on this page carry. Generated code writes it.
Generated code only.
func (*Request) URLParam ¶
URLParam returns the part of the URL that the folder s_name or n_name matched, or "".
func (*Request) URLParamInt ¶
URLParamInt returns the number the folder n_name matched. It returns 0 when the path has no such parameter or its value is not a number.
type ResponseWriter ¶
ResponseWriter lets hooks set response headers; the framework writes the page. Set a cookie with w.Header().Add("Set-Cookie", c.String()). On a live connection, Data gets a ResponseWriter whose headers are never sent.
type Route ¶
type Route interface {
// Access returns the route's own <ssr:access> rule; the zero Access means it has none.
Access() Access
// Layout reports whether the route's template has <ssr:content/>, so that the routes below
// it render inside it. A route with routes below it that is not a layout is a gate: its
// access rule and Guard apply to every route below it, and it renders only as its own page.
Layout() bool
// NewState returns the route's part of one request.
NewState() RouteState
}
Route is the code aicoded generate writes for one folder under pages/. Apps do not implement it.
type RouteState ¶
type RouteState interface {
Guard(ctx context.Context, r *Request) error
InitForms(ctx context.Context, r *Request, w ResponseWriter) error
// SubmitForm validates and processes the posted form id if this route renders it, and
// reports whether it does.
SubmitForm(ctx context.Context, r *Request, w ResponseWriter, id string) (bool, error)
Data(ctx context.Context, r *Request, w ResponseWriter) error
DefaultRoute(ctx context.Context, r *Request) (string, error)
Write(w io.Writer) error
// contains filtered or unexported methods
}
RouteState is one route's part of a request. The framework calls Guard for every route on the path, root first, and then InitForms, SubmitForm, Data and Write for the routes that render the page: the layouts on the path and the page itself. Generated states embed Frame.
type SafeHTML ¶
type SafeHTML struct {
// contains filtered or unexported fields
}
SafeHTML is markup that a template writes as is with {{$ expr }}. {{ expr }} escapes it like any other value.
func EscapeHTML ¶
EscapeHTML returns markup that shows exactly text.
func HTMLConst ¶
func HTMLConst(markup stringConstant) SafeHTML
HTMLConst returns markup written in the source code. Call it directly with a string constant: a string variable passed to it does not compile.
func JoinHTML ¶
JoinHTML concatenates markup.
Example ¶
A template writes a web.SafeHTML as it is with {{$ greeting }}. Build one from markup in the source code and from text escaped for it, so that what a viewer typed never becomes markup.
package main
import (
"fmt"
"aicoded.dev/framework/web"
)
func main() {
name := `Ann <script>alert(1)</script>` // as a viewer typed it
greeting := web.JoinHTML(web.HTMLConst("<strong>Hello</strong>, "), web.EscapeHTML(name))
fmt.Println(greeting)
}
Output: <strong>Hello</strong>, Ann <script>alert(1)</script>
Source Files
¶
Directories
¶
| Path | Synopsis |
|---|---|
|
Package form holds the typed fields of a page's forms.
|
Package form holds the typed fields of a page's forms. |
|
Package reactive carries live page values between the server and the browser over one WebSocket per page.
|
Package reactive carries live page values between the server and the browser over one WebSocket per page. |
|
client
Package client holds the browser side of live pages as TypeScript.
|
Package client holds the browser side of live pages as TypeScript. |
|
Package render writes template values into a page, each with the escaper of the HTML context it lands in.
|
Package render writes template values into a page, each with the escaper of the HTML context it lands in. |