web

package
v0.1.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 7, 2026 License: Apache-2.0 Imports: 34 Imported by: 0

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

Examples

Constants

View Source
const (
	CSRFField = "_aicoded_csrf"
	FormField = "_aicoded_form"
)

Names of the hidden inputs every generated form carries.

Variables

This section is empty.

Functions

func CheckNoGuard

func CheckNoGuard(dp any, path string)

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

func Error(status int, message string) 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 Forbidden

func Forbidden() error

Forbidden ends the request with 403.

func LogError

func LogError(ctx context.Context, msg string, err error, attrs ...any)

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

func New(routes map[string]Route, opts Options) http.Handler

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 NotFound

func NotFound() error

NotFound ends the request with 404.

func Redirect

func Redirect(path string) error

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) SetAssets

func (f *Frame) SetAssets(tags []string)

SetAssets sets the tags <ssr:assets/> writes for this route.

func (*Frame) WriteAssets

func (f *Frame) WriteAssets(w io.Writer) error

WriteAssets writes the asset tags of this route and of every route below it, each once.

func (*Frame) WriteChild

func (f *Frame) WriteChild(w io.Writer) error

WriteChild writes the route below this one, where <ssr:content/> stands.

type HTTPError

type HTTPError struct {
	Status  int
	Message string
}

HTTPError ends a request with Status and shows Message to the viewer.

func (*HTTPError) Error

func (e *HTTPError) Error() string

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

type Request struct {
	*http.Request
	// contains filtered or unexported fields
}

Request is a request as the hooks of a page see it.

func (*Request) CSRFToken

func (r *Request) CSRFToken() string

CSRFToken returns the token the forms on this page carry. Generated code writes it.

Generated code only.

func (*Request) URLParam

func (r *Request) URLParam(name string) string

URLParam returns the part of the URL that the folder s_name or n_name matched, or "".

func (*Request) URLParamInt

func (r *Request) URLParamInt(name string) int64

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

type ResponseWriter interface {
	Header() http.Header
}

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

func EscapeHTML(text string) SafeHTML

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

func JoinHTML(parts ...SafeHTML) SafeHTML

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 &lt;script&gt;alert(1)&lt;/script&gt;

func (SafeHTML) String

func (h SafeHTML) String() string

String returns the markup.

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.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL