routetree

package
v0.2.3 Latest Latest
Warning

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

Go to latest
Published: Jul 29, 2026 License: Apache-2.0 Imports: 18 Imported by: 0

Documentation

Overview

Package routetree discovers a filesystem route tree and derives the stdlib ServeMux patterns, ancestor layout chains, and Go package names for it.

The tree is an opt-in directory (conventionally pages) whose subdirectories are both URL segments and Go packages. A directory name carries its own segment kind: a trailing underscore marks one dynamic segment, and two mark a catch-all. Bracketed spellings are impossible because a directory holding Go source must be a legal import path element; see the package documentation of ValidateDirName for what the toolchain accepts.

Index

Constants

View Source
const (
	PageComponentName     = "Page"
	LayoutComponentName   = "Layout"
	DocumentComponentName = "Document"
)

Reserved component names. decision:html-route-file-conventions ties each reserved file to one declaration name, so a handler can predict every symbol its route package exposes without opening the template.

View Source
const (
	RouteParamsType = "RouteParams"
	DecodeRouteFunc = "DecodeRoute"
)

Names of the symbols the default templates produce in a route package.

View Source
const (
	CodeInvalidPath  = "invalid_path_parameter"
	CodeInvalidQuery = "invalid_query_parameter"
	CodeMissingPath  = "missing_path_parameter"
)

Error codes the default decoder template reports.

View Source
const (
	DefaultDecoderOutput  = "route_gen.go"
	DefaultRegistryOutput = "routes_gen.go"
	// ComponentSuffix is appended to a template's base name to form its
	// generated file, so page.tb.html and layout.tb.html in one directory do not
	// claim the same output.
	ComponentSuffix = "_gen.go"
)

Default base names of the files Generate produces.

View Source
const (
	RegisterFunc   = "Register"
	MuxFunc        = "NewServeMux"
	TableVar       = "Routes"
	ActionTableVar = "Actions"
)

Default names of the registry's generated declarations.

View Source
const (
	DefaultPageFile     = "page.tb.html"
	DefaultLayoutFile   = "layout.tb.html"
	DefaultDocumentFile = "document.tb.html"
	DefaultLogicFile    = "page.go"
	DefaultRootDir      = "pages"
)

Default file names for the reserved roles of a route directory.

View Source
const ActionHashLength = 12

ActionHashLength is how many hexadecimal characters of the digest an endpoint carries. It is fixed rather than configurable.

View Source
const DefaultActionPrefix = "/_action"

DefaultActionPrefix is the reserved path every server function endpoint hangs below.

It is safe by construction only because Discover ignores directories beginning with an underscore, so a route tree can never produce this path. A configured prefix has no such guarantee, which is why ValidateActionPrefix takes the discovered routes.

View Source
const GeneratedHeader = "// Code generated by tinybind; DO NOT EDIT."

GeneratedHeader is the first line of every file this package emits.

View Source
const PageFuncName = "Load"

PageFuncName is the reserved name of the optional Go entry point beside a page template.

It is Load rather than Page because the template compiler already emits a func Page for the component into the same package, and two declarations cannot share a name. The file stays page.go and the component stays Page; only the Go entry point moves aside.

View Source
const RenderFunc = "Render"

RenderFunc is the name of the generated composer.

View Source
const SlotParamName = "children"

SlotParamName is the parameter a layout must declare to become a chain wrapper. The template compiler emits a Bind wrapper only for an exported component carrying an html parameter with this name, so the convention is the compiler's rather than this package's.

View Source
const TemplateComposer = "composer"

TemplateComposer is the name of the template that renders a route composer. Pass it to Emitter.Parse to replace the emitted shape.

View Source
const TemplateDecoder = "decoder"

TemplateDecoder is the name of the template that renders a route decoder. Pass it to Emitter.Parse to replace the emitted shape.

View Source
const TemplateRegistry = "registry"

TemplateRegistry is the name of the template that renders the integrated ServeMux. Pass it to Emitter.Parse to replace the emitted shape.

Variables

This section is empty.

Functions

func ActionHash added in v0.2.3

func ActionHash(relDir, name string) string

ActionHash derives the stable half of an endpoint URL from the declaring package's route-relative directory and the handler name.

The mount prefix is deliberately not hashed, so remounting an application changes the URL without changing the identity underneath. There is no build salt either: regenerating an unchanged project reproduces the same value, so a client that cached the URL keeps working across a deploy.

The declaring directory rather than the serving route path is what goes in, because a layout is compiled once and its handler must have one URL no matter which page renders it.

func ActionPath added in v0.2.3

func ActionPath(prefix, hash, name string) string

ActionPath builds the endpoint path for one handler.

func EmitDecoder

func EmitDecoder(route Route, inputs []Value) ([]byte, error)

EmitDecoder renders the typed route decoder for one route using the default emitter. See Emitter.Decoder to customize the output.

func ExportedName

func ExportedName(name string) string

ExportedName converts a declared input name to its generated struct field name. It splits on underscores and case boundaries so id, user_id, and userID all reach the field a Go author would have written by hand.

func PackageName

func PackageName(dir string) string

PackageName derives the Go package clause for a route directory. A name that is already an identifier is used unchanged, which is what makes the common id_ form read naturally; anything else is sanitized, because a URL segment such as sign-in is legal on disk but not as a package clause.

func Validate

func Validate(route Route, fn *PageFunc, componentParams []Value) []error

Validate cross-checks a typed Page against the route it serves. It returns every problem it finds so one run reports more than the first.

componentParams is the page component's declared parameter list, which a typed Page must reproduce as its results. Passing nil skips that half of the check, which is what a caller does before the template has been compiled.

func ValidateActionPrefix added in v0.2.3

func ValidateActionPrefix(prefix string, tree *Tree) error

ValidateActionPrefix reports whether a configured endpoint prefix is usable with the discovered routes.

The default prefix cannot collide, because a route directory beginning with an underscore is ignored by discovery. A configured one has no such protection, so it is checked against every discovered pattern here rather than surfacing as a ServeMux panic at startup.

func ValidateDirName

func ValidateDirName(name string) error

ValidateDirName reports whether a directory name can hold Go source as part of this module.

A route directory is also a Go package, so its name must be a legal import path element. The Go toolchain rejects an illegal element while matching package patterns, before build constraints are considered, so a single offending directory breaks go build ./... for the whole module rather than for that package alone. That is why bracketed Next.js spellings such as [id] cannot be used here.

func Write

func Write(files []Generated) error

Write writes generated files to disk, creating directories as needed.

Types

type Action added in v0.2.3

type Action struct {
	// Name is the exported Go function name.
	Name string
	// RelDir is the declaring package's directory relative to the route root,
	// using slashes. The route root package itself has an empty RelDir.
	RelDir string
	// Package and ImportPath name the declaring Go package. ImportPath is empty
	// unless Config.ImportBase was set.
	Package    string
	ImportPath string
	// File and Line locate the declaration, for diagnostics.
	File string
	Line int
	// Hash is the first ActionHashLength hexadecimal characters of the digest of
	// the declaring directory and the name.
	Hash string
	// Path is the endpoint path, such as /_action/9f3c2ab1e4d7/Rename.
	Path string
}

Action is one server function reachable from client code.

Every exported function in a route package with the handler signature becomes one, whether or not a template references it. A route package is imported by nothing but the generated registry, so an exported symbol in it is that route's surface rather than a general API; lower-casing the function is what keeps it private, because generated code in another package cannot reach an unexported symbol.

func DiscoverActions added in v0.2.3

func DiscoverActions(dir, relDir, pkg, importPath, prefix string) ([]Action, error)

DiscoverActions reads the Go sources of one route or layout package and returns its server functions, ordered by name.

dir is the package directory, relDir its path relative to the route root, and prefix the reserved endpoint prefix; an empty prefix uses DefaultActionPrefix.

func (Action) Pattern added in v0.2.3

func (a Action) Pattern() string

Pattern returns the stdlib ServeMux pattern for the endpoint, which is always POST.

type Analysis

type Analysis struct {
	Route     Route
	Component ComponentSignature
	// Page describes the Go entry point. It is never nil; a route with no
	// page.go still reports RungTemplateOnly.
	Page *PageFunc
	// Inputs is what [Emitter.Decoder] binds for this route. Which declaration
	// supplies it depends on the rung; see [Analyze].
	Inputs []Value
}

Analysis is one route checked end to end: its template, its optional Go entry point, and the input list a generated decoder must bind.

func Analyze

func Analyze(route Route) (Analysis, error)

Analyze reads one route's template and logic file and checks them against each other.

The input list a decoder binds comes from a different declaration at each rung, because a different declaration is the thing the request reaches first:

  • RungTemplateOnly: the component parameters, since the request renders the component directly.
  • RungTypedPage: the func Page parameters, since the request reaches Go first and the component parameters are that function's results.
  • RungHandlerPage: the route's dynamic segments as strings. The handler owns decoding, so the generated decoder is a convenience covering only what the filesystem already knows; anything else the handler reads itself.

Every problem found is reported, so one run surfaces more than the first.

type ComponentSignature

type ComponentSignature struct {
	Name string
	// Inputs are the ordinary parameters, in declaration order.
	Inputs []Value
	// Slots are the html parameters, which a wrapper fills rather than a
	// caller passing data.
	Slots []Value
}

ComponentSignature is one template declaration, split into the values a caller supplies and the slots composition fills.

func LayoutComponent

func LayoutComponent(path string) (ComponentSignature, error)

LayoutComponent reads a layout template and returns its reserved declaration.

func PageComponent

func PageComponent(path string) (ComponentSignature, error)

PageComponent reads a page template and returns its reserved declaration.

type ComposerArg

type ComposerArg struct {
	// Field is the layout parameter struct field.
	Field string
	// From is the route parameter struct field it reads.
	From string
}

ComposerArg is one layout input filled from the decoded route.

type ComposerLayout

type ComposerLayout struct {
	// Selector is the qualifier for symbols of this layout's package, such as
	// "users." It is empty when the layout lives in the page's own package.
	Selector string
	// Binder is the generated wrapper constructor, such as BindLayout.
	Binder string
	// ParamsType is the generated parameter struct, such as LayoutParams.
	ParamsType string
	// Args maps each declared layout input to the route field feeding it.
	Args []ComposerArg
}

ComposerLayout is one wrapper in a generated composer, already resolved to the selector, binder, and argument list the template writes.

type ComposerModel

type ComposerModel struct {
	Header     string
	Package    string
	Pattern    string
	Imports    []Import
	ParamsType string
	RenderFunc string
	// Component is the page component name, which also prefixes its generated
	// parameter struct.
	Component string
	Layouts   []ComposerLayout
	Symbols   Symbols
}

ComposerModel is the data the composer template renders.

type Config

type Config struct {
	// Root is the route root directory. Empty uses DefaultRootDir relative to
	// the working directory.
	Root string
	// ImportBase is the Go import path of the route root directory. Setting it
	// lets discovery compute an ImportPath for every route and layout, which is
	// what a generated registry needs to import them. It cannot be derived,
	// because the route root's position inside its module is not visible from
	// the directory alone.
	ImportBase string
	// PageFile, LayoutFile, DocumentFile, and LogicFile override the reserved
	// names. Empty values use the defaults.
	PageFile     string
	LayoutFile   string
	DocumentFile string
	LogicFile    string
}

Config selects the route root and the reserved file names. A zero Config uses the defaults.

type DecoderField

type DecoderField struct {
	// Go is the generated struct field name.
	Go string
	// Key is the path wildcard or query key it reads.
	Key string
	// Type is the declared Go type.
	Type string
	// IsQuery reads from the query string rather than the path.
	IsQuery bool
	// Required rejects an empty value. A catch-all is not required, because an
	// empty remainder is a legal match.
	Required bool
	// Parse is the call that converts raw into v, empty for a string.
	Parse string
	// Convert narrows the parse result, empty when the types already agree.
	Convert string
	// ErrCode and ErrMessage describe the failure of Parse.
	ErrCode    string
	ErrMessage string
	// MissingMessage describes an absent required path value.
	MissingMessage string
}

DecoderField is one input of a generated decoder, already lowered to everything the template needs to write it out.

type DecoderModel

type DecoderModel struct {
	Header      string
	Package     string
	Pattern     string
	Imports     []Import
	ParamsType  string
	DecodeFunc  string
	Fields      []DecoderField
	HasQuery    bool
	MissingCode string
	Symbols     Symbols
}

DecoderModel is the data the decoder template renders.

type Emitter

type Emitter struct {
	// Symbols are the identities the templates call.
	Symbols Symbols
	// ParamsType, DecodeFunc, and RenderFunc name the generated declarations, so
	// a framework can publish its own vocabulary without replacing a template.
	ParamsType   string
	DecodeFunc   string
	RenderFunc   string
	RegisterFunc string
	MuxFunc      string
	TableVar     string
	// ActionPrefix is the reserved path every server function endpoint hangs
	// below. Empty uses [DefaultActionPrefix]. A framework mounting under a
	// sub-path or owning its own URL namespace sets it, which is why it is not
	// the fixed value the endpoint hash is.
	ActionPrefix string
	// ActionTableVar names the generated list of endpoints. Every server
	// function is reachable whether or not a template references it, so the
	// list is what makes that surface inspectable.
	ActionTableVar string
	// ActionAttr is the attribute a lowered server-action writes in compiled
	// templates. Empty uses the htmlbind default.
	ActionAttr string
	// contains filtered or unexported fields
}

Emitter renders generated Go source. Its zero value is not usable; build one with NewEmitter.

func NewEmitter

func NewEmitter() *Emitter

NewEmitter returns an emitter with the built-in templates and the httpbind symbols.

func (*Emitter) Clone

func (e *Emitter) Clone() (*Emitter, error)

Clone returns an independent emitter, so one configured base can be specialized per generation run without sharing template state.

func (*Emitter) Composer

func (e *Emitter) Composer(route Route, layouts []ComponentSignature) ([]byte, error)

Composer renders the per-route Render function.

layouts must hold one signature per entry of route.Layouts, in the same order. A layout that declares no slot parameter is rejected, because the template compiler emits no wrapper binder for it and the generated call would not compile.

func (*Emitter) Decoder

func (e *Emitter) Decoder(route Route, inputs []Value) ([]byte, error)

Decoder renders the typed route decoder for one route.

inputs is the page's declared input list: the route's dynamic segments in order, followed by the query parameters. It is the component parameter list at RungTemplateOnly and the func Page parameter list at RungTypedPage. The caller has already checked the shape with Validate; Decoder assumes it.

func (*Emitter) Parse

func (e *Emitter) Parse(name, text string) error

Parse replaces one named template. It returns an error when the text does not parse, leaving the emitter unchanged.

The replacement is compiled into a copy of the template set, so a framework may override the whole file shape with TemplateDecoder or only one nested block such as "error" or "convert".

func (*Emitter) Registry

func (e *Emitter) Registry(tree *Tree, rootPackage string, analyses []Analysis, layouts map[string]ComponentSignature, actions []Action) ([]byte, error)

Registry renders the integrated ServeMux for a whole tree, into the route root package.

Putting it in the root is what makes every generated import point down the tree: the registry reaches route and layout packages below it, and none of them reaches back up. A per-route composer in a leaf package would have to import its ancestors, which with a registry in the root is a cycle.

analyses must hold one entry per route of the tree, layouts maps a layout's RelDir to its signature, and actions are the server functions discovered across the tree.

type Error

type Error struct {
	// Path is the offending file or directory.
	Path string
	// Message states what is wrong and, where possible, what to do instead.
	Message string
}

Error is one discovery failure, anchored at the file or directory that caused it.

func (*Error) Error

func (e *Error) Error() string

type GenerateOptions

type GenerateOptions struct {
	// Config selects the route root and reserved names.
	Config Config
	// RootPackage is the Go package clause of the route root directory, which is
	// where the registry is emitted. Empty derives it from the directory name.
	RootPackage string
	// Emitter renders the Go source. Nil uses [NewEmitter].
	Emitter *Emitter
	// ComponentSuffix, DecoderOutput, and RegistryOutput override the generated
	// file names.
	ComponentSuffix string
	DecoderOutput   string
	RegistryOutput  string
}

GenerateOptions configures one whole-tree generation run.

type Generated

type Generated struct {
	// Path is the absolute destination.
	Path string
	// Source is the formatted Go source.
	Source []byte
}

Generated is one emitted file and where it belongs.

func Generate

func Generate(options GenerateOptions) ([]Generated, error)

Generate discovers the tree and emits every file it needs: the compiled components for each template, a typed decoder per route, and one registry in the route root carrying the integrated ServeMux.

Nothing is written to disk; the caller owns that, which is what lets a framework post-process or redirect the output.

type Import

type Import struct {
	Path  string
	Alias string
	// Group starts a new import group before this line, which is how the
	// standard library and the runtime end up separated.
	Group bool
}

Import is one import line of a generated file.

type Layout

type Layout struct {
	// RelDir is the layout directory relative to the route root, using slashes.
	// The root layout has an empty RelDir.
	RelDir string
	// File is the absolute path of the layout template.
	File string
	// Package is the Go package name of the layout directory.
	Package string
	// ImportPath is the Go import path of the layout directory. It is empty
	// unless Config.ImportBase was set.
	ImportPath string
	// Params are the dynamic segments in scope at this level, outermost first.
	// A layout may only depend on segments at or above its own directory.
	Params []Segment
}

Layout is one ancestor wrapper contributing to a page.

type PageFunc

type PageFunc struct {
	Rung Rung
	// File is the page.go path, empty at RungTemplateOnly with no file.
	File string
	// Line is the line of the func Page declaration, zero when absent.
	Line int
	// Params and Results are populated at RungTypedPage only. Results excludes
	// the trailing error.
	Params  []Value
	Results []Value
}

PageFunc describes the Go entry point of one route.

func InspectLogic

func InspectLogic(path string) (*PageFunc, error)

InspectLogic reads one page.go and classifies its Page declaration. An empty path, or a file declaring no Page, yields RungTemplateOnly.

type RegistryAction added in v0.2.3

type RegistryAction struct {
	Pattern string
	Path    string
	Hash    string
	Name    string
	RelDir  string
	// Selector qualifies the handler's package, such as "id_." It is empty for
	// a handler in the root package itself.
	Selector string
}

RegistryAction is one server function endpoint lowered to what the registry template writes.

type RegistryModel

type RegistryModel struct {
	Header         string
	Package        string
	Imports        []Import
	RegisterFunc   string
	MuxFunc        string
	TableVar       string
	ActionTableVar string
	DecodeFunc     string
	Routes         []RegistryRoute
	Actions        []RegistryAction
	Symbols        Symbols
}

RegistryModel is the data the registry template renders.

type RegistryRoute

type RegistryRoute struct {
	Pattern string
	Path    string
	RelDir  string
	// ParamNames are the dynamic segment names, in route order.
	ParamNames []string
	// Selector qualifies symbols of the route's package, such as "id_." It is
	// empty for a route in the root package itself.
	Selector string
	// Raw registers the route's own handler directly and generates no body.
	Raw bool
	// Call is set when a typed func Page must run before rendering.
	Call bool
	// CallResults is the assignment prefix for that call, such as "u, err :=".
	CallResults string
	// CallArgs is the argument list for that call, read from the decoded route.
	CallArgs string
	// PageFields fills the page component parameter struct.
	PageFields []ComposerArg
	// Layouts are the ancestor wrappers, outermost first.
	Layouts []ComposerLayout
}

RegistryRoute is one route lowered to what the registry template writes.

type Route

type Route struct {
	// RelDir is the page directory relative to the route root, using slashes.
	// The root page has an empty RelDir.
	RelDir string
	// Dir is the absolute page directory.
	Dir string
	// PageFile is the absolute path of the page template.
	PageFile string
	// LogicFile is the absolute path of page.go, or empty when absent.
	LogicFile string
	// Package is the Go package name for the page directory.
	Package string
	// ImportPath is the Go import path of the page directory. It is empty
	// unless Config.ImportBase was set.
	ImportPath string
	// Path is the URL path pattern, such as /users/{id}.
	Path string
	// Segments are every directory from the route root to the page directory.
	Segments []Segment
	// Params are the dynamic and catch-all segments only, in route order.
	Params []Segment
	// Layouts are the ancestor layouts, outermost first.
	Layouts []Layout
}

Route is one discovered page.

func (Route) Pattern

func (r Route) Pattern() string

Pattern returns the stdlib ServeMux pattern for the page, which is always a GET route; see decision:route-handler-shape.

The root page registers as /{$} rather than /, because a bare / is a prefix pattern in the standard library and would answer every unmatched path instead of letting it be a 404.

type Rung

type Rung uint8

Rung is how much of the request path a route hands to Go, from a template with no Go file at all up to a plain net/http handler.

const (
	// RungTemplateOnly is a route with no page.go, or one declaring no Page.
	// The whole handler is generated and the template's own external calls
	// supply the data.
	RungTemplateOnly Rung = iota + 1
	// RungTypedPage is a Page taking the route's inputs and returning the
	// values the template renders, followed by an error.
	RungTypedPage
	// RungHandlerPage is a Page that is an ordinary http.HandlerFunc and owns
	// the whole response.
	RungHandlerPage
)

func (Rung) String

func (r Rung) String() string

type Segment

type Segment struct {
	// Dir is the directory name exactly as it appears on disk.
	Dir string
	// Name is the parameter name for a dynamic or catch-all segment, and the
	// literal URL segment for a static one.
	Name string
	Kind SegmentKind
}

Segment is one directory of the route tree.

func ParseSegment

func ParseSegment(dir string) Segment

ParseSegment reads the segment kind and parameter name out of a directory name. Two trailing underscores mark a catch-all, one marks a dynamic segment, and anything else is static.

type SegmentKind

type SegmentKind uint8

SegmentKind classifies how a directory contributes to the URL.

const (
	// StaticSegment contributes the directory name literally.
	StaticSegment SegmentKind = iota
	// DynamicSegment binds one path element to a named parameter.
	DynamicSegment
	// CatchAllSegment binds the remainder of the path to a named parameter.
	CatchAllSegment
)

func (SegmentKind) String

func (k SegmentKind) String() string

type Symbols

type Symbols struct {
	// HTTPImport and HTTPAlias name the package providing Request and
	// PathValue. The alias is what generated source writes.
	HTTPImport string
	HTTPAlias  string
	// ErrorImport and ErrorAlias name the package providing the constructors
	// below. An empty ErrorImport suppresses the import, which is what a
	// template that reports errors some other way wants.
	ErrorImport string
	ErrorAlias  string
	// BadRequest and Problem are selectors on ErrorAlias.
	BadRequest string
	Problem    string
	// StrconvImport is the package providing the scalar parsers. It is only
	// imported when a route actually declares a non-string input.
	StrconvImport string
	StrconvAlias  string
	// RuntimeImport and RuntimeAlias name the HTML rendering runtime the
	// composer calls.
	RuntimeImport string
	RuntimeAlias  string
}

Symbols names the identities generated code calls. Repointing them is the cheap half of customization: a framework that only wants its own error constructor changes these and keeps every default template.

func DefaultSymbols

func DefaultSymbols() Symbols

DefaultSymbols targets the httpbind runtime this module ships.

type Tree

type Tree struct {
	// Root is the absolute route root directory.
	Root string
	// DocumentFile is the absolute path of the root document shell, or empty.
	DocumentFile string
	// Routes are the discovered pages, ordered by path.
	Routes []Route
}

Tree is a discovered route tree.

func Discover

func Discover(cfg Config) (*Tree, error)

Discover walks the configured route root and returns its routes. Every problem it finds is reported; the returned error joins them so one run surfaces more than the first mistake. A non-nil Tree is still returned alongside errors so a caller may report and continue.

type Value

type Value struct {
	// Name is the declared identifier. A result is usually unnamed, leaving
	// this empty.
	Name string
	// Type is the source text of the type expression, such as string or
	// []Order.
	Type string
}

Value is one declared parameter or result of a typed Page.

Jump to

Keyboard shortcuts

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