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
- func EmitDecoder(route Route, inputs []Value) ([]byte, error)
- func ExportedName(name string) string
- func PackageName(dir string) string
- func Validate(route Route, fn *PageFunc, componentParams []Value) []error
- func ValidateDirName(name string) error
- func Write(files []Generated) error
- type Analysis
- type ComponentSignature
- type ComposerArg
- type ComposerLayout
- type ComposerModel
- type Config
- type DecoderField
- type DecoderModel
- type Emitter
- func (e *Emitter) Clone() (*Emitter, error)
- func (e *Emitter) Composer(route Route, layouts []ComponentSignature) ([]byte, error)
- func (e *Emitter) Decoder(route Route, inputs []Value) ([]byte, error)
- func (e *Emitter) Parse(name, text string) error
- func (e *Emitter) Registry(tree *Tree, rootPackage string, analyses []Analysis, ...) ([]byte, error)
- type Error
- type GenerateOptions
- type Generated
- type Import
- type Layout
- type PageFunc
- type RegistryModel
- type RegistryRoute
- type Route
- type Rung
- type Segment
- type SegmentKind
- type Symbols
- type Tree
- type Value
Constants ¶
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.
const ( RouteParamsType = "RouteParams" DecodeRouteFunc = "DecodeRoute" )
Names of the symbols the default templates produce in a route package.
const ( CodeInvalidPath = "invalid_path_parameter" CodeInvalidQuery = "invalid_query_parameter" CodeMissingPath = "missing_path_parameter" )
Error codes the default decoder template reports.
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.
const ( RegisterFunc = "Register" MuxFunc = "NewServeMux" TableVar = "Routes" )
Default names of the registry's generated declarations.
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.
const GeneratedHeader = "// Code generated by tinybind; DO NOT EDIT."
GeneratedHeader is the first line of every file this package emits.
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.
const RenderFunc = "Render"
RenderFunc is the name of the generated composer.
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.
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.
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.
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 EmitDecoder ¶
EmitDecoder renders the typed route decoder for one route using the default emitter. See Emitter.Decoder to customize the output.
func ExportedName ¶
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 ¶
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 ¶
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 ValidateDirName ¶
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.
Types ¶
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 ¶
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
// 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 ¶
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 ¶
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 ¶
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) ([]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, and layouts maps a layout's RelDir to its signature.
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.
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 ¶
InspectLogic reads one page.go and classifies its Page declaration. An empty path, or a file declaring no Page, yields RungTemplateOnly.
type RegistryModel ¶
type RegistryModel struct {
Header string
Package string
Imports []Import
RegisterFunc string
MuxFunc string
TableVar string
DecodeFunc string
Routes []RegistryRoute
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 ¶
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 )
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 ¶
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.