Documentation
¶
Overview ¶
Package routing decides which handler answers a request, and what the address of a handler is called.
It is a thin shell over http.ServeMux, which since Go 1.22 already matches methods and path parameters. What this package adds is everything the standard mux has no opinion about: groups with an inherited prefix, name and middleware; a route table that survives registration so a view can build a URL from a name instead of a string literal; the seven REST routes of a resource controller; and the two middlewares that decide whether a request reaches a route at all.
One handler type ¶
A route dispatches to an http.Handler. There is one registration path and one handler type on it -- Get, Post, Put, Patch, Delete, Match, Any and Fallback all take the same thing -- because the shape this replaced had two: Get took an http.HandlerFunc and a controller arrived through a second method, Action, whose job was to build a handler out of a controller method and translate what it returned. That second path was never a second way to route. It was a way to construct a handler, and everything it touched -- the request context, the renderer, the flash, a rejected form -- belongs to the request layer above this one. Constructing the handler happens there now, and a controller action reaches a route the same way any other handler does:
r.Get("/dashboard", adapt(dashboard.Index)).Name("dashboard")
where adapt is the adapter that layer supplies. This package neither declares it nor names one, because the type it builds is the one this package must not import.
Resource takes the adaptation as an argument for the same reason, which is what lets this package register controller routes without importing the type a controller receives. See Adapter.
Names, not paths ¶
A hardcoded "/invoices/"+id compiles and keeps compiling after the route moves. Routes.Route does not: an unknown name or a wrong number of parameters is an error the caller sees, not a 404 the person sees.
Composing middleware is hesape/pipeline, generic over what it wraps, and this package uses it rather than declaring a second one. The request context belongs to hesape/http, which owns what an answer looks like; the half of the signed-URL story that belongs here is SignedRoute and middleware.ValidateSignature, over the Signer in hesape/encryption.
Index ¶
- Variables
- func ContextWithRoute(ctx context.Context, route *Route) context.Context
- func FormatRoutes(routes []*Route) string
- func Merge(attributes, old map[string]any, prependExistingPrefix ...bool) map[string]any
- func SignedRoute(t *Routes, s *encryption.Signer, name string, ttl time.Duration, ...) (string, error)
- func UniqueMiddleware(middleware []pipeline.Middleware[http.Handler]) []pipeline.Middleware[http.Handler]
- func VerifySignature(s *encryption.Signer, r *http.Request) error
- type Adapter
- type BinaryFileResponse
- type BindingFunc
- type Creator
- type Destroyer
- type Editor
- type Group
- type ImplicitRouteBinding
- type Indexer
- type MissingModelFunc
- type Redirect
- func (r *Redirect) ExceptInput(keys ...string) *Redirect
- func (r *Redirect) GetTargetURL() string
- func (r *Redirect) OnlyInput(keys ...string) *Redirect
- func (r *Redirect) SetTargetURL(u string)
- func (r *Redirect) With(key string, value any) *Redirect
- func (r *Redirect) WithErrors(errors any) *Redirect
- func (r *Redirect) WithFragment(fragment string) *Redirect
- func (r *Redirect) WithInput(input map[string]any) *Redirect
- func (r *Redirect) WithoutFragment() *Redirect
- func (r *Redirect) WithoutInput() *Redirect
- type Redirector
- func (r *Redirector) Action(action string, parameters map[string]any, status int, headers http.Header) (*Redirect, error)
- func (r *Redirector) Away(path string, status int, headers http.Header) *Redirect
- func (r *Redirector) Back(status int, headers http.Header, fallback string) *Redirect
- func (r *Redirector) GetIntendedUrl() string
- func (r *Redirector) GetUrlGenerator() *UrlGenerator
- func (r *Redirector) Guest(path string, status int, headers http.Header, secure *bool) *Redirect
- func (r *Redirector) Home(status int, headers http.Header) *Redirect
- func (r *Redirector) Intended(def string, status int, headers http.Header, secure *bool) *Redirect
- func (r *Redirector) Refresh(status int, headers http.Header) *Redirect
- func (r *Redirector) Route(name string, parameters map[string]any, status int, headers http.Header) (*Redirect, error)
- func (r *Redirector) Secure(path string, status int, headers http.Header) *Redirect
- func (r *Redirector) SetIntendedUrl(u string) *Redirector
- func (r *Redirector) SetSession(s SessionStore)
- func (r *Redirector) SignedRoute(name string, parameters map[string]any, expiration time.Time, status int, ...) (*Redirect, error)
- func (r *Redirector) TemporarySignedRoute(name string, expiration time.Time, parameters map[string]any, status int, ...) (*Redirect, error)
- func (r *Redirector) To(path string, status int, headers http.Header, secure *bool) *Redirect
- type Requirement
- type ResponseFactory
- func (f *ResponseFactory) Download(path, name string, headers http.Header, disposition string) *BinaryFileResponse
- func (f *ResponseFactory) EventStream(callback iter.Seq[any], headers http.Header, endStreamWith ...any) *StreamedResponse
- func (f *ResponseFactory) File(path string, headers http.Header) *BinaryFileResponse
- func (f *ResponseFactory) JSON(data any, status int, headers http.Header, options int) (*hhttp.JsonResponse, error)
- func (f *ResponseFactory) JSONP(callback string, data any, status int, headers http.Header, options int) (*hhttp.JsonResponse, error)
- func (f *ResponseFactory) Make(content any, status int, headers http.Header) (*hhttp.Response, error)
- func (f *ResponseFactory) NoContent(status int, headers http.Header) (*hhttp.Response, error)
- func (f *ResponseFactory) RedirectGuest(path string, status int, headers http.Header, secure *bool) (*Redirect, error)
- func (f *ResponseFactory) RedirectTo(path string, status int, headers http.Header, secure *bool) (*Redirect, error)
- func (f *ResponseFactory) RedirectToAction(action string, parameters map[string]any, status int, headers http.Header) (*Redirect, error)
- func (f *ResponseFactory) RedirectToIntended(def string, status int, headers http.Header, secure *bool) (*Redirect, error)
- func (f *ResponseFactory) RedirectToRoute(route string, parameters map[string]any, status int, headers http.Header) (*Redirect, error)
- func (f *ResponseFactory) Stream(callback StreamCallback, status int, headers http.Header) *StreamedResponse
- func (f *ResponseFactory) StreamDownload(callback StreamCallback, name string, headers http.Header, disposition string) *StreamedResponse
- func (f *ResponseFactory) StreamJSON(data any, status int, headers http.Header, encodingOptions int) *StreamedResponse
- func (f *ResponseFactory) View(view string, data any, status int, headers http.Header) (*hhttp.Response, error)
- type Route
- func (rt *Route) Action(action string) *Route
- func (rt *Route) AllowsTrashedBindings() bool
- func (rt *Route) Bind(req *http.Request) *http.Request
- func (rt *Route) BindingFieldFor(param string) string
- func (rt *Route) BindingFields() map[string]string
- func (rt *Route) Block(lockSeconds, waitSeconds *int) *Route
- func (rt *Route) Can(action auth.Action) *Route
- func (rt *Route) Defaults(key string, value any) *Route
- func (rt *Route) Deprecated(since, sunset time.Time) *Route
- func (rt *Route) Domain(domain string) *Route
- func (rt *Route) EnforcesScopedBindings() bool
- func (rt *Route) ExcludedMiddleware() []pipeline.Middleware[http.Handler]
- func (rt *Route) ForgetParameter(req *http.Request, name string)
- func (rt *Route) GatherMiddleware() []pipeline.Middleware[http.Handler]
- func (rt *Route) GetAction(key ...string) any
- func (rt *Route) GetActionMethod() string
- func (rt *Route) GetActionName() string
- func (rt *Route) GetController() http.Handler
- func (rt *Route) GetControllerClass() string
- func (rt *Route) GetDefaults(key ...string) any
- func (rt *Route) GetDeprecation() (since, sunset time.Time)
- func (rt *Route) GetDomain() string
- func (rt *Route) GetMissing() http.Handler
- func (rt *Route) GetName() string
- func (rt *Route) GetOptionalParameterNames() map[string]any
- func (rt *Route) GetPrefix() string
- func (rt *Route) GetURI() string
- func (rt *Route) GetValidators() []matching.ValidatorInterface
- func (rt *Route) GetWheres() map[string]string
- func (rt *Route) HasParameter(name string) bool
- func (rt *Route) HasParameters() bool
- func (rt *Route) HttpOnly() bool
- func (rt *Route) HttpsOnly() bool
- func (rt *Route) IsDeprecated() bool
- func (rt *Route) IsFallback() bool
- func (rt *Route) LocksFor() *int
- func (rt *Route) Matches(req *http.Request, includeMethod bool) bool
- func (rt *Route) Methods() []string
- func (rt *Route) Middleware(mws ...pipeline.Middleware[http.Handler]) *Route
- func (rt *Route) Missing(h http.Handler) *Route
- func (r *Route) Name(name string) *Route
- func (rt *Route) Named(patterns ...string) bool
- func (rt *Route) OriginalParameter(req *http.Request, name string, def ...string) string
- func (rt *Route) OriginalParameters(req *http.Request) map[string]string
- func (rt *Route) Parameter(req *http.Request, name string, def ...any) any
- func (rt *Route) ParameterNames() []string
- func (rt *Route) Parameters(req *http.Request) map[string]any
- func (rt *Route) ParametersWithoutNulls(req *http.Request) map[string]any
- func (rt *Route) ParentOfParameter(req *http.Request, name string) any
- func (rt *Route) Prefix(prefix string) *Route
- func (rt *Route) PreventsScopedBindings() bool
- func (rt *Route) RequiredAction() auth.Action
- func (r *Route) RouteName() string
- func (rt *Route) Run(w http.ResponseWriter, req *http.Request)
- func (rt *Route) Scheme(scheme string) *Route
- func (rt *Route) ScopeBindings() *Route
- func (rt *Route) Secure() bool
- func (rt *Route) ServeHTTP(w http.ResponseWriter, req *http.Request)
- func (rt *Route) SetAction(action map[string]any) *Route
- func (r *Route) SetActionName(action string)
- func (rt *Route) SetBindingFields(fields map[string]string) *Route
- func (rt *Route) SetBindingFieldsFromUri() string
- func (r *Route) SetControllerMethod(controller, method string)
- func (rt *Route) SetDefaults(defaults map[string]any) *Route
- func (rt *Route) SetFallback(fallback bool) *Route
- func (rt *Route) SetParameter(req *http.Request, name string, value any)
- func (rt *Route) SetRouter(router *Router) *Route
- func (rt *Route) SetURI(uri string) *Route
- func (rt *Route) SetWheres(wheres map[string]string) *Route
- func (rt *Route) ToResponse(w http.ResponseWriter, req *http.Request)
- func (rt *Route) URI() string
- func (rt *Route) Uses(action string) *Route
- func (rt *Route) WaitsFor() *int
- func (rt *Route) Where(name, pattern string) *Route
- func (rt *Route) WhereAlpha(params ...string) *Route
- func (rt *Route) WhereAlphaNumeric(params ...string) *Route
- func (rt *Route) WhereIn(param string, values ...string) *Route
- func (rt *Route) WhereMap(wheres map[string]string) *Route
- func (rt *Route) WhereNumber(params ...string) *Route
- func (rt *Route) WhereUlid(params ...string) *Route
- func (rt *Route) WhereUuid(params ...string) *Route
- func (rt *Route) WithTrashed(with ...bool) *Route
- func (rt *Route) WithoutBlocking() *Route
- func (rt *Route) WithoutMiddleware(mws ...pipeline.Middleware[http.Handler]) *Route
- func (rt *Route) WithoutScopedBindings() *Route
- type RouteBindings
- type RouteNotFoundError
- type RouteParameterBinder
- type RouteRegistrar
- func (reg *RouteRegistrar) Any(pattern string, h http.Handler, mws ...pipeline.Middleware[http.Handler]) *Route
- func (reg *RouteRegistrar) Attribute(key string, value any) *RouteRegistrar
- func (reg *RouteRegistrar) Delete(pattern string, h http.Handler, mws ...pipeline.Middleware[http.Handler]) *Route
- func (reg *RouteRegistrar) Domain(domain string) *RouteRegistrar
- func (reg *RouteRegistrar) Get(pattern string, h http.Handler, mws ...pipeline.Middleware[http.Handler]) *Route
- func (reg *RouteRegistrar) Group(fn func(*Router))
- func (reg *RouteRegistrar) Match(methods []string, pattern string, h http.Handler, ...) *Route
- func (reg *RouteRegistrar) Middleware(mws ...pipeline.Middleware[http.Handler]) *RouteRegistrar
- func (reg *RouteRegistrar) Name(name string) *RouteRegistrar
- func (reg *RouteRegistrar) Namespace(ns string) *RouteRegistrar
- func (reg *RouteRegistrar) Options(pattern string, h http.Handler, mws ...pipeline.Middleware[http.Handler]) *Route
- func (reg *RouteRegistrar) Patch(pattern string, h http.Handler, mws ...pipeline.Middleware[http.Handler]) *Route
- func (reg *RouteRegistrar) Post(pattern string, h http.Handler, mws ...pipeline.Middleware[http.Handler]) *Route
- func (reg *RouteRegistrar) Prefix(prefix string) *RouteRegistrar
- func (reg *RouteRegistrar) Put(pattern string, h http.Handler, mws ...pipeline.Middleware[http.Handler]) *Route
- func (reg *RouteRegistrar) Where(name, pattern string) *RouteRegistrar
- func (reg *RouteRegistrar) WhereAlpha(params ...string) *RouteRegistrar
- func (reg *RouteRegistrar) WhereAlphaNumeric(params ...string) *RouteRegistrar
- func (reg *RouteRegistrar) WhereIn(param string, values ...string) *RouteRegistrar
- func (reg *RouteRegistrar) WhereNumber(params ...string) *RouteRegistrar
- func (reg *RouteRegistrar) WhereUlid(params ...string) *RouteRegistrar
- func (reg *RouteRegistrar) WhereUuid(params ...string) *RouteRegistrar
- type RouteUri
- type RouteUrlGenerator
- type Router
- func (r *Router) AddRoute(methods []string, uri string, action http.Handler, ...) *Route
- func (r *Router) AliasMiddleware(name string, mw pipeline.Middleware[http.Handler]) *Router
- func (r *Router) Any(pattern string, h http.Handler, mws ...pipeline.Middleware[http.Handler]) *Route
- func (r *Router) Bind(key string, binder BindingFunc)
- func (r *Router) Current(req *http.Request) *Route
- func (r *Router) CurrentRouteAction(req *http.Request) string
- func (r *Router) CurrentRouteName(req *http.Request) string
- func (r *Router) CurrentRouteNamed(req *http.Request, patterns ...string) bool
- func (r *Router) CurrentRouteUses(req *http.Request, action string) bool
- func (r *Router) Delete(pattern string, h http.Handler, mws ...pipeline.Middleware[http.Handler]) *Route
- func (r *Router) Dispatch(w http.ResponseWriter, req *http.Request)
- func (r *Router) DispatchToRoute(w http.ResponseWriter, req *http.Request)
- func (r *Router) Domain(domain string) *RouteRegistrar
- func (r *Router) Fallback(h http.Handler, mws ...pipeline.Middleware[http.Handler]) *Route
- func (r *Router) FlushMiddlewareGroups() *Router
- func (r *Router) ForModule(name string) *Router
- func (r *Router) GatherRouteMiddleware(route *Route) []pipeline.Middleware[http.Handler]
- func (r *Router) Get(pattern string, h http.Handler, mws ...pipeline.Middleware[http.Handler]) *Route
- func (r *Router) GetBindingCallback(key string) BindingFunc
- func (r *Router) GetCurrentRoute(req *http.Request) *Route
- func (r *Router) GetGroupStack() []map[string]any
- func (r *Router) GetLastGroupPrefix() string
- func (r *Router) GetMiddleware() map[string]pipeline.Middleware[http.Handler]
- func (r *Router) GetMiddlewareGroups() map[string][]pipeline.Middleware[http.Handler]
- func (r *Router) GetPatterns() map[string]string
- func (r *Router) GetRoutes() *Routes
- func (r *Router) GetViewRenderer() ViewRenderer
- func (r *Router) Group(g Group) *Router
- func (r *Router) Has(name string) bool
- func (r *Router) HasGroupStack() bool
- func (r *Router) HasMiddlewareGroup(name string) bool
- func (r *Router) Input(req *http.Request, key string, def ...any) any
- func (r *Router) Is(req *http.Request, patterns ...string) bool
- func (r *Router) Match(methods []string, pattern string, h http.Handler, ...) *Route
- func (r *Router) Matched(cb func(*Route, *http.Request))
- func (r *Router) MergeWithLastGroup(attributes map[string]any, prependExistingPrefix ...bool) map[string]any
- func (r *Router) Middleware(mws ...pipeline.Middleware[http.Handler]) *RouteRegistrar
- func (r *Router) MiddlewareGroup(name string, mws ...pipeline.Middleware[http.Handler]) *Router
- func (r *Router) Model(key string, class UrlRoutable, callback ...MissingModelFunc)
- func (r *Router) Name(name string) *RouteRegistrar
- func (r *Router) Namespace(ns string) *RouteRegistrar
- func (r *Router) NewRoute(methods []string, uri string, action http.Handler) *Route
- func (r *Router) Options(pattern string, h http.Handler, mws ...pipeline.Middleware[http.Handler]) *Route
- func (r *Router) Patch(pattern string, h http.Handler, mws ...pipeline.Middleware[http.Handler]) *Route
- func (r *Router) Pattern(key, pattern string)
- func (r *Router) Patterns(patterns map[string]string)
- func (r *Router) PermanentRedirect(uri, destination string) *Route
- func (r *Router) Post(pattern string, h http.Handler, mws ...pipeline.Middleware[http.Handler]) *Route
- func (r *Router) Prefix(prefix string) *RouteRegistrar
- func (r *Router) PrependMiddlewareToGroup(name string, mw pipeline.Middleware[http.Handler]) *Router
- func (r *Router) PushMiddlewareToGroup(name string, mw pipeline.Middleware[http.Handler]) *Router
- func (r *Router) Put(pattern string, h http.Handler, mws ...pipeline.Middleware[http.Handler]) *Route
- func (r *Router) Redirect(uri, destination string, status ...int) *Route
- func (r *Router) RemoveMiddlewareFromGroup(name string, mw pipeline.Middleware[http.Handler]) *Router
- func (r *Router) ResolveMiddleware(middleware []pipeline.Middleware[http.Handler], ...) []pipeline.Middleware[http.Handler]
- func (r *Router) RespondWithRoute(req *http.Request, name string, w http.ResponseWriter) error
- func (r *Router) Routes() []*Route
- func (r *Router) ServeHTTP(w http.ResponseWriter, req *http.Request)
- func (r *Router) SetViewRenderer(vr ViewRenderer)
- func (r *Router) SubstituteBindings(ctx context.Context, g auth.Grant, rt *Route, req *http.Request) error
- func (r *Router) SubstituteImplicitBindings(ctx context.Context, g auth.Grant, rt *Route, req *http.Request) error
- func (r *Router) SubstituteImplicitBindingsUsing(callback func(rt *Route, req *http.Request) error) *Router
- func (r *Router) Table() *Routes
- func (r *Router) Uses(req *http.Request, patterns ...string) bool
- func (r *Router) View(pattern, view string, data ...map[string]any) *Route
- type Routes
- func (t *Routes) Add(route *Route) *Route
- func (t *Routes) All() []*Route
- func (t *Routes) Count() int
- func (t *Routes) GetByAction(action string) *Route
- func (t *Routes) GetByName(name string) *Route
- func (t *Routes) GetRoutes() []*Route
- func (t *Routes) GetRoutesByMethod(method string) []*Route
- func (t *Routes) GetRoutesByName() map[string]*Route
- func (t *Routes) HasNamedRoute(name string) bool
- func (t *Routes) Match(req *http.Request) *Route
- func (t *Routes) Must(name string, params ...string) string
- func (t *Routes) Refresh()
- func (t *Routes) RefreshActionLookups()
- func (t *Routes) RefreshNameLookups()
- func (t *Routes) Requirements() []Requirement
- func (t *Routes) Route(name string, params ...string) (string, error)
- type SessionStore
- type Shower
- type Storer
- type StreamCallback
- type StreamedResponse
- type Updater
- type UrlGenerator
- func (g *UrlGenerator) Action(action string, parameters map[string]any, absolute bool) (string, error)
- func (g *UrlGenerator) Asset(path string, secure *bool) string
- func (g *UrlGenerator) AssetFrom(root, path string, secure *bool) string
- func (g *UrlGenerator) Current() string
- func (g *UrlGenerator) Defaults(defaults map[string]any)
- func (g *UrlGenerator) ForceHttps(force ...bool)
- func (g *UrlGenerator) ForceRootUrl(root string)deprecated
- func (g *UrlGenerator) ForceScheme(scheme string)
- func (g *UrlGenerator) Format(root, path string, route ...*Route) string
- func (g *UrlGenerator) FormatHostUsing(callback func(root string, route *Route) string) *UrlGenerator
- func (g *UrlGenerator) FormatParameters(parameters map[string]any) map[string]any
- func (g *UrlGenerator) FormatPathUsing(callback func(path string, route *Route) string) *UrlGenerator
- func (g *UrlGenerator) FormatRoot(scheme string, root ...string) string
- func (g *UrlGenerator) FormatScheme(secure *bool) string
- func (g *UrlGenerator) Full() string
- func (g *UrlGenerator) GetDefaultParameters() map[string]any
- func (g *UrlGenerator) GetRequest() *http.Request
- func (g *UrlGenerator) GetRootControllerNamespace() string
- func (g *UrlGenerator) HasCorrectSignature(request *http.Request, absolute bool, ignoreQuery ...string) bool
- func (g *UrlGenerator) HasValidRelativeSignature(request *http.Request, ignoreQuery ...string) bool
- func (g *UrlGenerator) HasValidSignature(request *http.Request, absolute bool, ignoreQuery ...string) bool
- func (g *UrlGenerator) IsValidUrl(path string) bool
- func (g *UrlGenerator) PathFormatter() func(path string, route *Route) string
- func (g *UrlGenerator) Previous(fallback ...string) string
- func (g *UrlGenerator) PreviousPath(fallback ...string) string
- func (g *UrlGenerator) Query(path string, query map[string]any, extra []any, secure *bool) string
- func (g *UrlGenerator) ResolveMissingNamedRoutesUsing(...) *UrlGenerator
- func (g *UrlGenerator) Route(name string, parameters map[string]any, absolute bool) (string, error)
- func (g *UrlGenerator) Secure(path string, parameters ...any) string
- func (g *UrlGenerator) SecureAsset(path string) string
- func (g *UrlGenerator) SetKeyResolver(keyResolver func() []string) *UrlGenerator
- func (g *UrlGenerator) SetRequest(request *http.Request)
- func (g *UrlGenerator) SetRootControllerNamespace(rootNamespace string) *UrlGenerator
- func (g *UrlGenerator) SetRoutes(routes *Routes) *UrlGenerator
- func (g *UrlGenerator) SetSessionResolver(sessionResolver func() SessionStore) *UrlGenerator
- func (g *UrlGenerator) SignatureHasNotExpired(request *http.Request) bool
- func (g *UrlGenerator) SignedRoute(name string, parameters map[string]any, expiration time.Time, absolute bool) (string, error)
- func (g *UrlGenerator) TemporarySignedRoute(name string, expiration time.Time, parameters map[string]any, absolute bool) (string, error)
- func (g *UrlGenerator) To(path string, extra []any, secure *bool) string
- func (g *UrlGenerator) ToRoute(route *Route, parameters map[string]any, absolute bool) (string, error)
- func (g *UrlGenerator) UseAssetOrigin(root string)
- func (g *UrlGenerator) UseOrigin(root string)
- func (g *UrlGenerator) WithKeyResolver(keyResolver func() []string) *UrlGenerator
- type UrlRoutable
- type ViewRenderer
Constants ¶
This section is empty.
Variables ¶
var ErrTenantlessBinding = errors.New("routing: route binding without a tenant")
ErrTenantlessBinding is returned when a binding is resolved under a Grant that carries no tenant.
It is a refusal and not a warning: a query with no tenant to scope by reads every customer, so the binding fails rather than running unscoped.
Functions ¶
func ContextWithRoute ¶
ContextWithRoute returns ctx with route stored, for a handler or middleware that needs to install the matched route itself -- a custom dispatcher, or a test. ServeHTTP already does this for every registered route.
func FormatRoutes ¶
FormatRoutes renders the route table for the terminal, grouped by module and sorted by pattern.
It is here, and not in the CLI, so that every consumer prints the same table: the CLI's route list and the route list on the error page are the same rows in the same order, and a developer comparing the two is comparing like with like.
The name column is what a developer copies into Routes.Route instead of typing the path, so a route without one prints short rather than printing an empty column that looks like a value. A deprecated route prints its sunset date last, which is the date a reader plans against.
func Merge ¶
Merge folds a group's attributes into the ones it is nested inside, and returns the combined set.
Prefix concatenates with a slash, name with nothing (the dot is the caller's), namespace with a backslash, where unions with the new winning. Domain and controller replace rather than merge: a nested group that names a domain is declaring one, not adding to one.
prependExistingPrefix defaults to true. False puts the new prefix in front, which is how a group registered under a versioned API root keeps the version segment outermost.
func SignedRoute ¶
func SignedRoute(t *Routes, s *encryption.Signer, name string, ttl time.Duration, params ...string) (string, error)
SignedRoute builds the URL of a named route with a signature on it, valid for ttl.
SignedRoute(r.Table(), signer, "unsubscribe", 30*24*time.Hour, list.ID)
It is what an unsubscribe link and a verification link are made of. The alternative -- a table of tokens -- costs a write, a read, a cleanup job and a decision about what happens when the row is gone; this costs a signature, and a link that has run out says so.
The signature covers the whole address, not just the parameters: appending anything to the query string of a signed link invalidates it, so a route behind ValidateSignature cannot be handed a value its author did not sign.
It signs and it does not verify: middleware.ValidateSignature is the other half, and the Signer itself is in hesape/encryption, which is where a leaf primitive belongs. Building a signed URL is routing's, because the address being signed is a route.
func UniqueMiddleware ¶
func UniqueMiddleware(middleware []pipeline.Middleware[http.Handler]) []pipeline.Middleware[http.Handler]
UniqueMiddleware drops repeats while keeping the first of each, so a middleware a group and a route both attached runs once.
func VerifySignature ¶
func VerifySignature(s *encryption.Signer, r *http.Request) error
VerifySignature reports whether a request arrived on a URL this application signed, and that the signature has not run out.
It returns an error that unwraps to encryption.ErrSignature, and to encryption.ErrExpired when the only thing wrong is the time -- which is the one failure a person can act on, because "ask for another link" is a different sentence from "this link is not ours".
It is exported alongside the middleware because a handler that is reached some other way -- a webhook that also accepts an unsigned call from an authenticated operator -- needs the same check, and a second reading of the query parameter is a second place for the canonicalisation to be missing.
Types ¶
type Adapter ¶
Adapter turns one controller action into an http.Handler.
C is the request context a controller action receives, and it is a type parameter rather than a named type because that type lives above this package: hesape/http owns the request context, the renderer and the answer to a rejected form, and a router that imported it would be a router that had to know what an answer looks like in order to match a path.
The layer that owns C supplies the adapter, and the call reads:
routing.Resource(r, "invoices", InvoiceController{}, adapt)
where adapt is that layer's own function. It is the same one a single route already goes through -- r.Get("/x", adapt(c.Show)) -- passed by name instead of applied, so Resource can apply it to the seven actions it finds.
type BinaryFileResponse ¶
type BinaryFileResponse struct {
// Path is the file to send.
Path string
// Headers are sent alongside the ones http.ServeContent derives from the
// file itself.
Headers http.Header
}
BinaryFileResponse is a path on disk plus the headers to send it under. ResponseFactory.Download and File both build one.
The bytes are read when the response is sent, never before, so a large file costs a descriptor and not memory.
func (*BinaryFileResponse) SendContent ¶
func (r *BinaryFileResponse) SendContent(w http.ResponseWriter, req *http.Request) error
SendContent opens the file and hands it to http.ServeContent, which picks the status -- 200, 206 for a range request, 304 for a conditional one -- and streams the bytes.
func (*BinaryFileResponse) ServeHTTP ¶
func (r *BinaryFileResponse) ServeHTTP(w http.ResponseWriter, req *http.Request)
ServeHTTP makes a BinaryFileResponse an http.Handler. A file that cannot be opened answers 404.
func (*BinaryFileResponse) SetContentDisposition ¶
func (r *BinaryFileResponse) SetContentDisposition(disposition, name, fallback string) *BinaryFileResponse
SetContentDisposition says whether the browser saves the file or shows it, and under what name.
The fallback is the ASCII name a client that does not understand RFC 5987 falls back to, which is what [responseFactoryFallbackName] produces.
type BindingFunc ¶
BindingFunc resolves one route parameter into the value a handler receives.
It is the shape of the closure Router.Bind takes, and of the one RouteBinding.ForModel returns. It takes the context every query needs and the Grant every query is scoped by.
A binder that looks a record up by value alone returns another tenant's row for the same id. Filter by auth.Tenant(g).
func ForCallback ¶
func ForCallback(binder BindingFunc) BindingFunc
ForCallback returns binder unchanged: there is no container here to resolve a name through, so a binder is the closure itself.
func ForModel ¶
func ForModel(class UrlRoutable, callback ...MissingModelFunc) BindingFunc
ForModel turns a record type into a binder: the value from the path is handed to that type's ResolveRouteBinding, under the Grant, and what comes back is what the handler receives.
type Group ¶
type Group struct {
// Prefix is prepended to the path of every route registered under it.
Prefix string
// Name is prepended, dot-joined, to the name of every route registered
// under it. See Route.Name.
Name string
// Middleware wraps every route registered under it, outside any middleware
// the route gives itself, and inside the middleware of any enclosing group.
Middleware []pipeline.Middleware[http.Handler]
}
Group is what a sub-router adds to everything registered under it.
It is one struct rather than three arguments because the three are always decided together, and because a positional signature makes the common case -- a prefix and nothing else -- indistinguishable at the call site from the case that also names its routes.
admin := r.Group(routing.Group{
Prefix: "/admin",
Name: "admin",
Middleware: []pipeline.Middleware[http.Handler]{auth.RequireRole("admin")},
})
type ImplicitRouteBinding ¶
type ImplicitRouteBinding struct {
// contains filtered or unexported fields
}
ImplicitRouteBinding resolves a route's parameters into records.
There are no type hints to reflect on here, so the registry is explicit -- Router.Bind and Router.Model -- and this walks the route's parameters against it.
func NewImplicitRouteBinding ¶
func NewImplicitRouteBinding(b *RouteBindings) *ImplicitRouteBinding
NewImplicitRouteBinding creates a resolver backed by the bindings set.
func (*ImplicitRouteBinding) ResolveForRoute ¶
func (b *ImplicitRouteBinding) ResolveForRoute(ctx context.Context, g auth.Grant, route *Route, req *http.Request) error
ResolveForRoute resolves every parameter of the route that has a binder registered, and installs what came back as the value the handler reads.
Two things it refuses ¶
A Grant with no tenant, before it looks anything up: a lookup with nothing to scope by reads every customer.
A child whose parent is a record and whose route is scoped: the child is resolved through the parent -- /clients/{client}/invoices/{invoice} finds the invoice among that client's, so an invoice id belonging to somebody else is a 404 rather than a page. That is the second leak this file exists to close, and it is the one a tenant filter alone does not catch: two clients of the same tenant are still two clients.
type MissingModelFunc ¶
MissingModelFunc decides what a model binding that matched nothing means. It is the third argument of Router.Model, and returning a nil value with a nil error means "answer 404".
type Redirect ¶
type Redirect struct {
Path string
Status int
Headers http.Header
// Session carries flash data attached to the redirect: WithInput, WithErrors.
Session SessionStore
}
Redirect is the data a handler returns to tell the framework to send a redirect response.
The fields are public so the caller can add cookies, flash messages and headers before returning it.
func (*Redirect) ExceptInput ¶
ExceptInput flashes everything except the named keys.
func (*Redirect) GetTargetURL ¶
GetTargetURL returns the path being redirected to.
func (*Redirect) SetTargetURL ¶
SetTargetURL replaces the path.
func (*Redirect) WithErrors ¶
WithErrors flashes errors so the form shows them.
func (*Redirect) WithFragment ¶
WithFragment appends a fragment to the URL.
func (*Redirect) WithoutFragment ¶
WithoutFragment removes the fragment.
func (*Redirect) WithoutInput ¶
WithoutInput removes the flashed input.
type Redirector ¶
type Redirector struct {
// contains filtered or unexported fields
}
Redirector builds RedirectResponses to every place a handler can send the browser: back to the previous page, to a named route, to a path, to the address an unauthenticated visitor was trying to reach.
It holds a UrlGenerator and optionally a session store, and every method that answers a redirect does so by building a URL through the generator and wrapping it.
func NewRedirector ¶
func NewRedirector(g *UrlGenerator) *Redirector
NewRedirector returns a redirector backed by g.
func (*Redirector) Action ¶
func (r *Redirector) Action(action string, parameters map[string]any, status int, headers http.Header) (*Redirect, error)
Action sends the browser to a controller action.
func (*Redirector) Away ¶
Away sends the browser to an external URL, without validating the destination -- for a third-party integration, not for a path within the application.
func (*Redirector) Back ¶
Back sends the browser where it came from, and to the fallback when it came from nowhere -- a bookmarked form, a link opened in a new tab.
func (*Redirector) GetIntendedUrl ¶
func (r *Redirector) GetIntendedUrl() string
GetIntendedUrl reads the stored URL without removing it.
func (*Redirector) GetUrlGenerator ¶
func (r *Redirector) GetUrlGenerator() *UrlGenerator
GetUrlGenerator returns the generator backing this redirector.
func (*Redirector) Guest ¶
Guest sends the browser to a path, storing where it was going so Intended can bring it back after a sign-in.
func (*Redirector) Home ¶
func (r *Redirector) Home(status int, headers http.Header) *Redirect
Home sends the browser to the route named "home", falling back to "/" when there is none.
func (*Redirector) Intended ¶
Intended sends the browser to the URL stored by Guest, or to the default.
func (*Redirector) Refresh ¶
func (r *Redirector) Refresh(status int, headers http.Header) *Redirect
Refresh sends the browser to the current URL.
func (*Redirector) Route ¶
func (r *Redirector) Route(name string, parameters map[string]any, status int, headers http.Header) (*Redirect, error)
Route sends the browser to a named route.
func (*Redirector) SetIntendedUrl ¶
func (r *Redirector) SetIntendedUrl(u string) *Redirector
SetIntendedUrl stores a URL for Intended to read back.
func (*Redirector) SetSession ¶
func (r *Redirector) SetSession(s SessionStore)
SetSession attaches a session store so Intended and Guest can read and write the intended URL.
func (*Redirector) SignedRoute ¶
func (r *Redirector) SignedRoute(name string, parameters map[string]any, expiration time.Time, status int, headers http.Header) (*Redirect, error)
SignedRoute sends the browser to a signed URL for a named route.
type Requirement ¶ added in v0.25.0
type Requirement struct {
// Route is the route that declared it.
Route *Route
// Action is what Can declared.
Action auth.Action
}
Requirement is a route and the action it requires.
type ResponseFactory ¶
type ResponseFactory struct {
// contains filtered or unexported fields
}
ResponseFactory is the one place a handler asks for an answer of a given shape -- a body, a view, JSON, a stream, a file, a redirect -- instead of assembling one by hand.
It holds two collaborators: a ViewRenderer, which is this package's seam onto the view layer, and a Redirector. Every method named Redirect* is a delegation to that redirector and nothing else -- there is one redirect mechanism in this package, not two, which is why those methods answer with the Redirect the Redirector builds rather than with an hhttp.RedirectResponse.
func NewResponseFactory ¶
func NewResponseFactory(view ViewRenderer, redirector *Redirector) *ResponseFactory
NewResponseFactory builds a ResponseFactory from its two collaborators.
Either collaborator may be nil, and the method that needs the missing one says so rather than panicking: a project that never returns a view has no renderer to give.
func (*ResponseFactory) Download ¶
func (f *ResponseFactory) Download(path, name string, headers http.Header, disposition string) *BinaryFileResponse
Download sends a file from disk under a Content-Disposition that makes the browser save it.
It takes the path, and the file is opened when the response is sent rather than read into memory when it is built -- BinaryFileResponse serves it through http.ServeContent, so a range request and a conditional request are answered the way the standard library answers them.
An empty name means the file's own base name. An empty disposition defaults to "attachment".
func (*ResponseFactory) EventStream ¶
func (f *ResponseFactory) EventStream(callback iter.Seq[any], headers http.Header, endStreamWith ...any) *StreamedResponse
EventStream builds a server-sent event stream, with the three headers that stop a proxy or a browser from buffering it.
callback is an iter.Seq: a producer that is pulled, not a slice built in advance. A message that is an *hhttp.StreamedEvent names its own event; anything else is sent under "update", and a value that is neither a string nor a number is encoded as JSON.
endStreamWith is optional and defaults to "</stream>": omit it for that default, and pass nil to close the stream without a final event. Passing an *hhttp.StreamedEvent names the closing event.
The iteration stops when the request context is done.
func (*ResponseFactory) File ¶
func (f *ResponseFactory) File(path string, headers http.Header) *BinaryFileResponse
File sends the raw contents of a file, with no Content-Disposition, so a PDF or an image is shown rather than saved.
It takes the path, for the reason [Download] does.
func (*ResponseFactory) JSON ¶
func (f *ResponseFactory) JSON(data any, status int, headers http.Header, options int) (*hhttp.JsonResponse, error)
JSON builds a response whose body is data encoded as JSON.
options is a bitmask of hhttp's JSON* flags. encoding/json has no counterpart for a hex-escaping default, so 0 -- no flags -- is the default here.
It returns (*hhttp.JsonResponse, error), so data that cannot be encoded is reported rather than panicking.
func (*ResponseFactory) JSONP ¶
func (f *ResponseFactory) JSONP(callback string, data any, status int, headers http.Header, options int) (*hhttp.JsonResponse, error)
JSONP is [JSON] with the payload wrapped in a call to the named callback, which turns the Content-Type into text/javascript.
func (*ResponseFactory) Make ¶
func (f *ResponseFactory) Make(content any, status int, headers http.Header) (*hhttp.Response, error)
Make builds a response around a body.
A status of 0 means 200 with no headers, as it does everywhere else in this package. It returns (*hhttp.Response, error) so a body that cannot be encoded is reported rather than panicking.
func (*ResponseFactory) NoContent ¶
NoContent builds an empty body under a status that says there is nothing to read. A status of 0 means 204.
func (*ResponseFactory) RedirectGuest ¶
func (f *ResponseFactory) RedirectGuest(path string, status int, headers http.Header, secure *bool) (*Redirect, error)
RedirectGuest delegates to Redirector.Guest: the redirect that remembers where the visitor was going, so a sign-in can send them back there.
func (*ResponseFactory) RedirectTo ¶
func (f *ResponseFactory) RedirectTo(path string, status int, headers http.Header, secure *bool) (*Redirect, error)
RedirectTo delegates to Redirector.To.
func (*ResponseFactory) RedirectToAction ¶
func (f *ResponseFactory) RedirectToAction(action string, parameters map[string]any, status int, headers http.Header) (*Redirect, error)
RedirectToAction delegates to Redirector.Action.
func (*ResponseFactory) RedirectToIntended ¶
func (f *ResponseFactory) RedirectToIntended(def string, status int, headers http.Header, secure *bool) (*Redirect, error)
RedirectToIntended delegates to Redirector.Intended: the other half of [RedirectGuest].
An empty default falls back to "/".
func (*ResponseFactory) RedirectToRoute ¶
func (f *ResponseFactory) RedirectToRoute(route string, parameters map[string]any, status int, headers http.Header) (*Redirect, error)
RedirectToRoute delegates to Redirector.Route.
func (*ResponseFactory) Stream ¶
func (f *ResponseFactory) Stream(callback StreamCallback, status int, headers http.Header) *StreamedResponse
Stream builds a response whose body is written as it is produced, rather than assembled and then sent.
The callback is handed an io.Writer that flushes after every write, so the bytes reach the client as soon as they are written. It also receives a context, because a stream is I/O that outlives the call that started it and the client may hang up mid-body.
func (*ResponseFactory) StreamDownload ¶
func (f *ResponseFactory) StreamDownload(callback StreamCallback, name string, headers http.Header, disposition string) *StreamedResponse
StreamDownload is a [Stream] the browser saves instead of showing, because of the Content-Disposition header.
By the time the callback runs, the status and the headers are already sent and there is no error page left to send, so a failure is wrapped in exceptions.StreamedResponseError rather than returned bare.
An empty name leaves the header off. An empty disposition defaults to "attachment".
func (*ResponseFactory) StreamJSON ¶
func (f *ResponseFactory) StreamJSON(data any, status int, headers http.Header, encodingOptions int) *StreamedResponse
StreamJSON encodes the value straight onto the wire instead of building the whole document in memory first.
It answers with a StreamedResponse whose callback is the encoder, rather than a dedicated streaming-JSON response type.
encodingOptions is the same flag word [JSON] takes.
func (*ResponseFactory) View ¶
func (f *ResponseFactory) View(view string, data any, status int, headers http.Header) (*hhttp.Response, error)
View renders a named view and answers with what it drew.
It goes through ViewRenderer, which is the seam this package already uses to reach the view layer without importing it.
This takes a single view name; a caller that wants a fallback among several names chooses one before calling.
It returns (*hhttp.Response, error), so a missing or failing template is reported rather than panicking.
type Route ¶
type Route struct {
// Method is the HTTP method the route answers, or ANY for a route
// registered without one.
Method string
// Pattern is the full path, prefixes of every enclosing group included.
Pattern string
// Module is the module that registered the route, for grouping in route
// introspection. It is empty for a route registered outside a module.
Module string
// contains filtered or unexported fields
}
Route is one registered route.
It is metadata and the handler the mux dispatches to: what route introspection prints, what the error page shows for the pattern that matched, what a URL is generated from, and the http.Handler that answers. The handler is stored here rather than handed to the mux wrapped and frozen at registration, so a where constraint, a middleware added after registration and a route model binding can all take effect on a route that is already registered -- which is the shape every fluent call returns.
func Resource ¶
Resource registers the REST routes a controller implements.
routing.Resource(r, "invoices", InvoiceController{}, adapt)
The seven, in the conventional order and with the conventional names:
GET /invoices index invoices.index
GET /invoices/create create invoices.create
POST /invoices store invoices.store
GET /invoices/{id} show invoices.show
GET /invoices/{id}/edit edit invoices.edit
PUT /invoices/{id} update invoices.update
PATCH /invoices/{id} update invoices.update
DELETE /invoices/{id} destroy invoices.destroy
The order matters: /invoices/create is registered before /invoices/{id} so a GET of "create" reaches the form rather than being read as an id. Go's ServeMux prefers the more specific pattern, and registering in this order keeps the intent readable even where the mux would sort it out anyway.
A controller implementing none of the seven registers nothing and returns zero routes, which is a wiring mistake worth seeing in the route table.
It is a function and not a method on Router because a method cannot take a type parameter in Go, and C has to come from somewhere. The router is the first argument, so the line still reads left to right as "register, on this router, this resource".
func RouteFromContext ¶
RouteFromContext returns the route the request matched, or nil if it did not go through a registered route (a direct mux pattern, or a handler that never set it). It is the read side of the contract the route model binding depends on.
func (*Route) Action ¶
Action is an alias for Uses that takes the action string, for whichever call site reads better with it.
func (*Route) AllowsTrashedBindings ¶
AllowsTrashedBindings reports whether WithTrashed was set.
func (*Route) Bind ¶
Bind associates the route with a request: it installs the route in the request context and returns the bound parameters. The binding state lives in the request, and ServeHTTP already does this for every registered route, so Bind is for a caller that dispatches a route outside the mux (RespondWithRoute).
func (*Route) BindingFieldFor ¶
BindingFieldFor returns the binding field for a parameter, or empty.
func (*Route) BindingFields ¶
BindingFields returns the {param:field} qualifiers parsed off the URI, keyed by parameter. The route model binding uses them to resolve a model by a column other than id.
func (*Route) Block ¶
Block stops two requests of the same session from running this route at once -- a double-submitted form, mostly -- by holding a lock for lockSeconds and waiting up to waitSeconds to take it.
func (*Route) Can ¶
Can declares which authorization action this route requires.
r.Delete("/invoices/{id}", destroy).Name("invoices.destroy").Can("invoice.delete")
It declares and it does not decide, and that difference is the whole of what it is for. Nothing in the dispatch reads it: the handler still asks auth.Authorize, the policy still answers, and the repository still refuses without the Grant that answer produced. A route carrying this and no authorization is exactly as open as one carrying neither -- which is why it cannot become a second way to authorize, and must not be read as one.
What it is for is the catalogue. A permissions screen has to list what may be granted, and the honest list is the router: actions written down beside it disagree with the code the first time somebody adds a route. Requirements reads it back.
One route requires one action, because Grant.Check compares one. Declaring a second replaces the first rather than adding to it.
It used to take a string and a list of models, and to append them into the action map -- which nothing ever read. The type is the action the policy is asked about, and the reader is Requirements: what a route requires is now answerable rather than merely recorded.
The declaration is carried to the sibling rows of a single registration -- the PATCH of an update, the later verbs of a Match -- because they are one route to everyone but the mux.
func (*Route) Defaults ¶
Defaults sets a default value for a parameter, and returns the route. The redirect and view routes use it to carry a destination or a view name in the same place a bound parameter would live.
func (*Route) Deprecated ¶ added in v0.9.0
Deprecated marks the route as deprecated since the given instant and due to stop answering at sunset. It returns the route so the call chains, the same shape Name returns.
r.Get("/v1/invoices", h).Name("v1.invoices").
Deprecated(announced, announced.AddDate(1, 0, 0))
Every response the route produces then carries two header fields: the Deprecation field of RFC 9745, holding the instant the route was deprecated, and the Sunset field of RFC 8594, holding the instant it stops answering. A client that reads either one learns the route is going away from the route itself, rather than from an announcement somebody has to remember to send. Route introspection prints the sunset date alongside the route.
Both instants are required, and a sunset before the deprecation date panics. The two fields would otherwise contradict each other on the wire -- RFC 9745 forbids exactly that pair -- and a client that catches a server contradicting itself is a client that stops reading both fields. It panics at registration, where the rest of this package refuses a declaration it cannot honour, so the contradiction is found at boot rather than by whoever was relying on the dates.
The deprecation date is an argument rather than the moment of this call because the moment of this call is process start: it would move forward on every deploy, and a client tracking when the route was deprecated would watch the answer change without anything having been decided.
func (*Route) Domain ¶
Domain returns the host the route is declared for, or empty if none. http.ServeMux does not route by host, so this is for URL generation and for Matches.
func (*Route) EnforcesScopedBindings ¶
EnforcesScopedBindings reports whether ScopeBindings was set.
func (*Route) ExcludedMiddleware ¶
func (rt *Route) ExcludedMiddleware() []pipeline.Middleware[http.Handler]
ExcludedMiddleware returns the middleware WithoutMiddleware removed.
func (*Route) ForgetParameter ¶
ForgetParameter removes a per-request override, leaving the raw path value the next Parameter call reads.
func (*Route) GatherMiddleware ¶
func (rt *Route) GatherMiddleware() []pipeline.Middleware[http.Handler]
GatherMiddleware returns the full middleware list for the route, group middleware first. It is what the dispatcher reads.
func (*Route) GetAction ¶
GetAction returns the action descriptor, or one of its keys, as a map whose keys are uses, controller and anything else set through Uses.
func (*Route) GetActionMethod ¶
GetActionMethod returns the method half of the action string -- the part after @ in "PostController@show" -- or the whole string for a closure.
func (*Route) GetActionName ¶
GetActionName returns the action string the route was registered with, or "Closure". It is what CurrentRouteAction returns for the route.
func (*Route) GetController ¶
GetController returns the handler the route dispatches to: the http.Handler the registration was given, which is already the thing the mux calls. It returns nil for a route registered without one (which should not happen, but the nil is the honest answer).
func (*Route) GetControllerClass ¶
GetControllerClass returns the controller half of the action string -- the part before @ in "PostController@show" -- or "Closure" for a closure route.
func (*Route) GetDefaults ¶
GetDefaults returns the defaults map, or one key of it.
func (*Route) GetDeprecation ¶ added in v0.9.0
GetDeprecation returns the two instants Deprecated set: when the route was deprecated, and when it stops answering. Both are zero on a route that is not deprecated.
func (*Route) GetMissing ¶
GetMissing returns the handler Missing set, or nil.
func (*Route) GetName ¶
GetName returns the name given with Name, or empty. RouteName is the other name for it.
func (*Route) GetOptionalParameterNames ¶
GetOptionalParameterNames returns the names of the route's optional path parameters. The values are nil: the map is a set, and the question asked of it is only whether a name is in it.
func (*Route) GetValidators ¶
func (rt *Route) GetValidators() []matching.ValidatorInterface
GetValidators returns the four checks that decide whether the route answers a request: path, method, scheme and host.
func (*Route) GetWheres ¶
GetWheres returns the where constraints as the string patterns the caller set.
func (*Route) HasParameter ¶
HasParameter reports whether name is one of the route's path parameters.
func (*Route) HasParameters ¶
HasParameters reports whether the route declares any path parameter. Unlike Parameter and its siblings, it does not need a request.
func (*Route) HttpOnly ¶
HttpOnly reports whether the route answers only plain http, which a route generating a URL for a local development host declares.
func (*Route) IsDeprecated ¶ added in v0.9.0
IsDeprecated reports whether Deprecated was called on the route.
func (*Route) IsFallback ¶
IsFallback reports whether the route is a fallback route.
func (*Route) LocksFor ¶
LocksFor returns the lock duration Block set. nil means the route takes no lock.
func (*Route) Matches ¶
Matches reports whether the route answers the request. Method is checked against the route's verb and its siblings; the path is checked against the pattern, with a parameter segment matching any non-slash. A fallback route only matches if nothing more specific did, which the caller is expected to have tried first.
func (*Route) Methods ¶
Methods returns the HTTP verbs the route answers. A Match registration produces one route per verb, linked through siblings; Methods gathers them.
func (*Route) Middleware ¶
Middleware appends middleware to the route, to run after the group's middleware and after anything already attached. Because the chain is read at request time, a Middleware call after registration -- which is the shape every fluent call returns -- takes effect on a route already in the mux.
Per-route middleware set at registration, through the varargs of Get and its siblings, and through this method, is one form: both reach the same slice.
func (*Route) Missing ¶
Missing sets the handler that answers when an implicit model binding resolves nothing. The implicit binding (hesape/routing, in the binding files) calls it; this is the declaration.
func (*Route) Name ¶
Name gives the route a name, so a URL can be generated from it instead of written by hand.
r.Get("/", home).Name("home")
routing.Resource(r, "invoices", InvoiceController{}, adapt) // names them all
It returns the route so the call chains, and the declaration reads as one line.
The name of every enclosing group is prepended, joined with a dot: Group{Name: "admin"} around .Name("users") gives "admin.users". The dot is joined here rather than left for the caller to remember, because a forgotten dot produces "adminusers", which is a name that works everywhere until somebody reads it.
func (*Route) Named ¶
Named reports whether the route's name matches any of the patterns, using * as a glob wildcard. A route with no name matches nothing.
func (*Route) OriginalParameter ¶
OriginalParameter returns the raw path value for name, before any override a binding installed. It is what a binding compares against to know whether it changed anything.
func (*Route) OriginalParameters ¶
OriginalParameters returns every path parameter as it arrived, before any binding replaced an id with the record it names -- which is what a binder compares against to know it changed something.
The request is the argument, so a call against an unbound route is not expressible.
func (*Route) Parameter ¶
Parameter returns the value of a path parameter by name, with the per-request override a binding installed taking precedence over the raw path value. A missing parameter returns def.
func (*Route) ParameterNames ¶
ParameterNames returns the names of the path parameters in the pattern, in order. Optional markers (?) and wildcard markers (...) are stripped.
func (*Route) Parameters ¶
Parameters returns every path parameter for the request, overrides included. The map is a fresh copy, so a caller may mutate it.
func (*Route) ParametersWithoutNulls ¶
ParametersWithoutNulls returns Parameters with nil values dropped.
func (*Route) ParentOfParameter ¶
ParentOfParameter returns the value of the parameter that precedes name in the parameter list, or empty if name is first or absent. It is what scoped binding uses to resolve a child against its parent.
func (*Route) Prefix ¶
Prefix prepends a prefix to the route's pattern. The mux is not re-registered, so this is for the URL generator and the group merge, not for moving a route after it has been wired.
func (*Route) PreventsScopedBindings ¶
PreventsScopedBindings reports whether WithoutScopedBindings was set, which is distinct from leaving it unset.
func (*Route) RequiredAction ¶ added in v0.25.0
RequiredAction returns the action Can declared, or empty.
It is not called Action because that name is the controller action string, and the two are different questions: one is which method answers, this is what the caller has to be allowed to do.
func (*Route) RouteName ¶
RouteName returns the name given with Name, or empty.
The exported field used to be called Name and nothing ever wrote to it -- a promise the code did not keep. Now Name(...) writes it and this reads it.
func (*Route) Run ¶
func (rt *Route) Run(w http.ResponseWriter, req *http.Request)
Run runs the route's handler without re-running middleware: middleware is the router's job, run is the action's. The route-in-context is installed so Parameter and the binding still work.
func (*Route) Scheme ¶
Scheme declares the scheme the route answers on, "http" or "https". It is stored as one key of the route's action map. HttpOnly, HttpsOnly and Secure are the readers, and this is what writes what they read.
func (*Route) ScopeBindings ¶
ScopeBindings marks the route as enforcing scoped implicit bindings -- a child resolved against its parent parameter. The implicit binding reads it.
func (*Route) Secure ¶
Secure reports whether the route answers only https, which is what makes its generated URL https regardless of how the current request arrived.
func (*Route) ServeHTTP ¶
func (rt *Route) ServeHTTP(w http.ResponseWriter, req *http.Request)
ServeHTTP is the mux's entry into the route. It is the lazy half of the registration model: where the constraints, the matched listeners, the route-in-context and the middleware chain are applied here, at request time, so a fluent call after registration takes effect on a route already in the mux.
Where constraints are part of matching, so they run first and answer 404 on failure before anything else; then the route is installed in the context and the matched listeners fire; then the middleware chain runs the handler.
func (*Route) SetAction ¶
SetAction sets the action descriptor and returns the route, for the group merge that carries domain and middleware in the same map.
func (*Route) SetActionName ¶
SetActionName stores the action name for URL generation via action().
func (*Route) SetBindingFields ¶
SetBindingFields sets the binding fields, for the group merge and the deserializer.
func (*Route) SetBindingFieldsFromUri ¶
SetBindingFieldsFromUri parses the route's pattern, applies the binding fields to the route, and returns the cleaned URI. It is what the group merge and the URL generator call when a pattern carries {param:field}.
func (*Route) SetControllerMethod ¶
SetControllerMethod stores controller and method for this route.
func (*Route) SetDefaults ¶
SetDefaults replaces the defaults map.
func (*Route) SetFallback ¶
SetFallback marks the route as a fallback. Fallback does this; SetFallback is for the deserializer.
func (*Route) SetParameter ¶
SetParameter installs a per-request override for name, so a binding that resolves an id into a record hands the record to the handler through Parameter. It mutates the override map ServeHTTP placed in the context, so it does not need to return a new request.
func (*Route) SetRouter ¶
SetRouter attaches the router whose matched listeners the route fires and whose binders it resolves through.
func (*Route) SetURI ¶
SetURI sets the pattern the route answers. It is here for the binding and the URL generator, which can rewrite a route's pattern after a group merge; the mux is not re-registered, so a pattern change after registration does not move what the mux dispatches.
func (*Route) ToResponse ¶
func (rt *Route) ToResponse(w http.ResponseWriter, req *http.Request)
ToResponse runs the route's full chain -- middleware and handler -- and is what RespondWithRoute calls when it dispatches a named route directly.
func (*Route) URI ¶
URI returns the path pattern the route answers, prefixes of every enclosing group included.
func (*Route) Uses ¶
Uses sets the action string -- "PostController@show" or "Closure" -- and returns the route. It is the fluent form of declaring what a route answers, and it is what GetActionName and GetActionMethod read.
func (*Route) WaitsFor ¶
WaitsFor returns the wait duration Block set. nil means the route takes no lock.
func (*Route) Where ¶
Where sets a regular-expression constraint on a path parameter. A request whose value for that parameter fails the regex is answered 404 before the handler, which is what stops /users/{id} from reaching the database for /users/abc.
Go's ServeMux does not take a regex in a pattern, so the constraint is enforced here, at dispatch, rather than in the mux's matcher. A failing value does not fall through to a sibling route -- ServeMux already picked this one -- but the goal the constraint exists for, keeping a bad value out of the database, holds.
func (*Route) WhereAlpha ¶
WhereAlpha constrains the given parameters to ASCII letters.
func (*Route) WhereAlphaNumeric ¶
WhereAlphaNumeric constrains the given parameters to ASCII letters and digits.
func (*Route) WhereNumber ¶
WhereNumber constrains the given parameters to digits.
func (*Route) WithTrashed ¶
WithTrashed allows soft-deleted records through implicit model binding on this route. The implicit binding (hesape/routing, in the binding files) reads it; this is the declaration.
func (*Route) WithoutBlocking ¶
WithoutBlocking clears any lock Block set.
func (*Route) WithoutMiddleware ¶
WithoutMiddleware records middleware to remove from the route's chain at request time. A middleware removed here is one a group attached that this route specifically does not want, and the removal is by identity.
func (*Route) WithoutScopedBindings ¶
WithoutScopedBindings marks the route as not enforcing scoped bindings.
type RouteBindings ¶
type RouteBindings struct {
// contains filtered or unexported fields
}
RouteBindings holds the parameter binders registered for a router, filled by Router.Bind and Router.Model.
func NewRouteBindings ¶
func NewRouteBindings() *RouteBindings
NewRouteBindings returns an empty set of bindings.
func (*RouteBindings) Bind ¶
func (b *RouteBindings) Bind(key string, binder BindingFunc)
Bind registers a binder for a parameter key.
When a route carries a parameter named key, the binder resolves the raw path value and the handler receives what it returned rather than the string.
func (*RouteBindings) GetBindingCallback ¶
func (b *RouteBindings) GetBindingCallback(key string) BindingFunc
GetBindingCallback returns the binder for a key, or nil.
func (*RouteBindings) Model ¶
func (b *RouteBindings) Model(key string, class UrlRoutable, callback ...MissingModelFunc)
Model registers a model binding for a parameter key: the parameter resolves through the record's own ResolveRouteBinding, which is where the tenant filter lives.
The optional callback says what a value that matched nothing means. Without one, nothing found is nothing bound, which the caller answers 404.
type RouteNotFoundError ¶
type RouteNotFoundError struct {
Name string
}
RouteNotFoundError is returned when a named route or action is not in the table.
func (*RouteNotFoundError) Error ¶
func (e *RouteNotFoundError) Error() string
type RouteParameterBinder ¶
type RouteParameterBinder struct {
// contains filtered or unexported fields
}
RouteParameterBinder reads a route's parameters off a request.
The mux has already matched the path, so the path values are read back through PathValue; the host half and the defaults are this type's work.
It reads, it does not resolve ¶
What comes back here is what arrived in the URL: strings. Turning a string into a record is ImplicitRouteBinding's job, and that is where the Grant and the tenant filter are. Nothing in this file reaches a database, and nothing in it should: an id read off a path is untrusted input until a policy has been consulted about it.
func NewRouteParameterBinder ¶
func NewRouteParameterBinder(route *Route) *RouteParameterBinder
NewRouteParameterBinder builds a binder for route.
func (*RouteParameterBinder) Parameters ¶
func (b *RouteParameterBinder) Parameters(request *http.Request) map[string]any
Parameters returns every parameter of the route for this request, host parameters and defaults included.
type RouteRegistrar ¶
type RouteRegistrar struct {
// contains filtered or unexported fields
}
RouteRegistrar collects group attributes fluently and applies them to the routes registered through it: a builder that holds prefix, name, middleware, domain, namespace and where, and that hands them to the router when a route is registered.
The Router methods Prefix, Name, Middleware, Domain and Namespace return one, so the call chains:
r.Prefix("/api").Name("api.").Middleware(auth.RequireLogin).Get("/users", h)
The attributes it carries are the same fields Group carries, plus a where map for the Where* constraints the registrar forwards to each route. It does not hold a group stack: a registrar is one layer, and nesting is the router's Group.
func (*RouteRegistrar) Any ¶
func (reg *RouteRegistrar) Any(pattern string, h http.Handler, mws ...pipeline.Middleware[http.Handler]) *Route
Any registers a route answering every method with the registrar's attributes.
func (*RouteRegistrar) Attribute ¶
func (reg *RouteRegistrar) Attribute(key string, value any) *RouteRegistrar
Attribute sets one attribute on the registrar, for the caller that builds it piecemeal.
func (*RouteRegistrar) Delete ¶
func (reg *RouteRegistrar) Delete(pattern string, h http.Handler, mws ...pipeline.Middleware[http.Handler]) *Route
Delete registers a DELETE route with the registrar's attributes applied.
func (*RouteRegistrar) Domain ¶
func (reg *RouteRegistrar) Domain(domain string) *RouteRegistrar
Domain sets the domain on the registrar and returns it.
func (*RouteRegistrar) Get ¶
func (reg *RouteRegistrar) Get(pattern string, h http.Handler, mws ...pipeline.Middleware[http.Handler]) *Route
Get registers a GET route with the registrar's attributes applied.
func (*RouteRegistrar) Group ¶
func (reg *RouteRegistrar) Group(fn func(*Router))
Group creates a sub-router with the registrar's attributes and runs fn against it. It is the registrar's group, which forwards to Router.Group.
func (*RouteRegistrar) Match ¶
func (reg *RouteRegistrar) Match(methods []string, pattern string, h http.Handler, mws ...pipeline.Middleware[http.Handler]) *Route
Match registers one handler under several methods, with the registrar's attributes applied.
func (*RouteRegistrar) Middleware ¶
func (reg *RouteRegistrar) Middleware(mws ...pipeline.Middleware[http.Handler]) *RouteRegistrar
Middleware appends middleware to the registrar and returns it.
func (*RouteRegistrar) Name ¶
func (reg *RouteRegistrar) Name(name string) *RouteRegistrar
Name sets the name prefix on the registrar and returns it.
func (*RouteRegistrar) Namespace ¶
func (reg *RouteRegistrar) Namespace(ns string) *RouteRegistrar
Namespace sets the namespace on the registrar and returns it.
func (*RouteRegistrar) Options ¶
func (reg *RouteRegistrar) Options(pattern string, h http.Handler, mws ...pipeline.Middleware[http.Handler]) *Route
Options registers an OPTIONS route with the registrar's attributes applied.
func (*RouteRegistrar) Patch ¶
func (reg *RouteRegistrar) Patch(pattern string, h http.Handler, mws ...pipeline.Middleware[http.Handler]) *Route
Patch registers a PATCH route with the registrar's attributes applied.
func (*RouteRegistrar) Post ¶
func (reg *RouteRegistrar) Post(pattern string, h http.Handler, mws ...pipeline.Middleware[http.Handler]) *Route
Post registers a POST route with the registrar's attributes applied.
func (*RouteRegistrar) Prefix ¶
func (reg *RouteRegistrar) Prefix(prefix string) *RouteRegistrar
Prefix sets the prefix on the registrar and returns it.
func (*RouteRegistrar) Put ¶
func (reg *RouteRegistrar) Put(pattern string, h http.Handler, mws ...pipeline.Middleware[http.Handler]) *Route
Put registers a PUT route with the registrar's attributes applied.
func (*RouteRegistrar) Where ¶
func (reg *RouteRegistrar) Where(name, pattern string) *RouteRegistrar
Where sets a where constraint on the registrar and returns it. Every route registered through the registrar inherits it.
func (*RouteRegistrar) WhereAlpha ¶
func (reg *RouteRegistrar) WhereAlpha(params ...string) *RouteRegistrar
WhereAlpha constrains the given parameters to ASCII letters.
func (*RouteRegistrar) WhereAlphaNumeric ¶
func (reg *RouteRegistrar) WhereAlphaNumeric(params ...string) *RouteRegistrar
WhereAlphaNumeric constrains the given parameters to ASCII letters and digits.
func (*RouteRegistrar) WhereIn ¶
func (reg *RouteRegistrar) WhereIn(param string, values ...string) *RouteRegistrar
WhereIn constrains the given parameter to one of the listed values.
func (*RouteRegistrar) WhereNumber ¶
func (reg *RouteRegistrar) WhereNumber(params ...string) *RouteRegistrar
WhereNumber constrains the given parameters to digits.
func (*RouteRegistrar) WhereUlid ¶
func (reg *RouteRegistrar) WhereUlid(params ...string) *RouteRegistrar
WhereUlid constrains the given parameters to the ULID alphabet.
func (*RouteRegistrar) WhereUuid ¶
func (reg *RouteRegistrar) WhereUuid(params ...string) *RouteRegistrar
WhereUuid constrains the given parameters to the UUID format.
type RouteUri ¶
RouteUri is the parsed URI of a route: the cleaned path and the binding fields a {param:field} qualifier declared.
The parse is what pulls {post:slug} out of the pattern and leaves {post} in its place, so the mux sees a pattern it can match and the route model binding knows which column to resolve by.
type RouteUrlGenerator ¶
type RouteUrlGenerator struct {
// DefaultParameters are the named defaults every route inherits, keyed by
// parameter name or by "name:field" when the route binds by a field.
DefaultParameters map[string]any
// DontEncode are the characters restored after encoding, so a URL keeps
// its separators.
DontEncode map[string]string
// contains filtered or unexported fields
}
RouteUrlGenerator is the half of UrlGenerator that turns a route and a bag of parameters into an address.
The work it does that a string concatenation does not: it fills named placeholders, falls back to the defaults, drops optional segments nobody filled, puts what is left over on the query string, and refuses -- rather than emitting a URL with a literal "{invoice}" in it -- when a required placeholder has no value.
func NewRouteUrlGenerator ¶
func NewRouteUrlGenerator(u *UrlGenerator, request *http.Request) *RouteUrlGenerator
NewRouteUrlGenerator returns a generator for u and request.
func (*RouteUrlGenerator) Defaults ¶
func (g *RouteUrlGenerator) Defaults(defaults map[string]any)
Defaults merges defaults into DefaultParameters.
func (*RouteUrlGenerator) To ¶
func (g *RouteUrlGenerator) To(route *Route, parameters map[string]any, absolute bool) (string, error)
To builds the URL of a route.
It returns an error when a placeholder the caller left unfilled would 404 the reader; saying so at the call site is the whole reason a named route beats a string.
type Router ¶
type Router struct {
// contains filtered or unexported fields
}
Router is a thin shell over http.ServeMux.
The mux already handles methods and path parameters. This exists for groups, per-group middleware and route metadata -- the metadata is what lets the CLI print the routes and what a view uses to build a URL from a name.
It holds no request state. An earlier version carried the view renderer and the flash so that it could build a request context itself, which is what made registering a controller a second registration path; both belong to the layer that owns the request context, and neither is here.
Configuration that is router-level rather than per-sub-router -- the global where patterns, the matched listeners, the middleware aliases and groups, the explicit binders and the view renderer -- lives on the root and is reached through [Router.root], so a sub-router created by Group reads the same map the kernel populated.
func (*Router) AddRoute ¶
func (r *Router) AddRoute(methods []string, uri string, action http.Handler, mws ...pipeline.Middleware[http.Handler]) *Route
AddRoute creates a route and puts it in the table, which is what every verb method goes through.
func (*Router) AliasMiddleware ¶
AliasMiddleware registers a short name for a middleware, for route introspection and for the kernel's resolution.
func (*Router) Any ¶
func (r *Router) Any(pattern string, h http.Handler, mws ...pipeline.Middleware[http.Handler]) *Route
Any registers a route that answers every method.
It is one registration and one row in the table, because that is what the mux does with a pattern that names no method. Reach for it for a webhook endpoint whose sender is not specific about the verb, and for nothing else: a route that also answers POST is a route where the CSRF check and the policy were written for a request shape that is not the one arriving.
func (*Router) Bind ¶
func (r *Router) Bind(key string, binder BindingFunc)
Bind registers a binder for a parameter key: the binder resolves the raw path value into the record the handler expects.
The binder receives the auth.Grant and MUST filter by auth.Tenant(g). A binder that looks a record up by id alone delivers another customer's row through the URL, which is the most direct form of the cross-tenant leak.
func (*Router) Current ¶
Current returns the route the request matched, read from the request context. nil means the request did not go through a registered route.
func (*Router) CurrentRouteAction ¶
CurrentRouteAction returns the action string of the current route, or empty.
func (*Router) CurrentRouteName ¶
CurrentRouteName returns the name of the current route, or empty.
func (*Router) CurrentRouteNamed ¶
CurrentRouteNamed reports whether the current route's name matches any of the patterns.
func (*Router) CurrentRouteUses ¶
CurrentRouteUses reports whether the current route's action equals action.
func (*Router) Delete ¶
func (r *Router) Delete(pattern string, h http.Handler, mws ...pipeline.Middleware[http.Handler]) *Route
Delete registers a DELETE route.
func (*Router) Dispatch ¶
func (r *Router) Dispatch(w http.ResponseWriter, req *http.Request)
Dispatch runs the request through the mux, which is what ServeHTTP does. It is here for the surface that reads it as a method; the mux already holds the routes, and dispatch is its job.
func (*Router) DispatchToRoute ¶
func (r *Router) DispatchToRoute(w http.ResponseWriter, req *http.Request)
DispatchToRoute is an alias for Dispatch: there is no separate global pipeline here to skip, since the mux is both the pipeline and the match.
func (*Router) Domain ¶
func (r *Router) Domain(domain string) *RouteRegistrar
Domain returns a registrar that sets the domain on every route. The mux does not route by host, so this is for URL generation and matching.
func (*Router) Fallback ¶
Fallback registers what answers everything under this router that no other route matched.
On the root router that is the whole application, which is where a custom 404 page goes. On a group it is that group's subtree: a group under /api can answer its own misses with JSON while the rest of the site answers with a page.
It is registered as the subtree pattern of the group -- "/" at the root, "/api/" under a group prefixed /api -- which http.ServeMux already treats as the least specific match, so every other route in the subtree still wins.
func (*Router) FlushMiddlewareGroups ¶
FlushMiddlewareGroups empties the middleware groups.
func (*Router) ForModule ¶
ForModule returns a sub-router that tags its routes with the module name, for grouping in route introspection. The kernel calls it for each module.
func (*Router) GatherRouteMiddleware ¶
GatherRouteMiddleware resolves the route's full middleware list, excluded middleware removed.
func (*Router) Get ¶
func (r *Router) Get(pattern string, h http.Handler, mws ...pipeline.Middleware[http.Handler]) *Route
Get registers a GET route.
func (*Router) GetBindingCallback ¶
func (r *Router) GetBindingCallback(key string) BindingFunc
GetBindingCallback returns the binder registered for key, or nil.
func (*Router) GetCurrentRoute ¶
GetCurrentRoute returns the route the request matched. The request is the argument because a router shared by every goroutine cannot hold the one in flight.
func (*Router) GetGroupStack ¶
GetGroupStack returns the attributes of every enclosing group, outermost first.
func (*Router) GetLastGroupPrefix ¶
GetLastGroupPrefix returns the prefix of the innermost open group, or empty.
func (*Router) GetMiddleware ¶
GetMiddleware returns the middleware aliases.
func (*Router) GetMiddlewareGroups ¶
GetMiddlewareGroups returns the middleware groups.
func (*Router) GetPatterns ¶
GetPatterns returns the global where patterns.
func (*Router) GetRoutes ¶
GetRoutes returns the route collection, for URL generation and route introspection.
func (*Router) GetViewRenderer ¶
func (r *Router) GetViewRenderer() ViewRenderer
GetViewRenderer returns the view renderer, or nil.
func (*Router) Group ¶
Group returns a sub-router with the prefix and name appended and the middleware inherited. The route table is shared with the parent, and so is the root configuration -- patterns, matched listeners, aliases, groups, binders and the view renderer -- so a sub-router reads what the kernel set.
func (*Router) HasGroupStack ¶
HasGroupStack reports whether any route group is currently open.
func (*Router) HasMiddlewareGroup ¶
HasMiddlewareGroup reports whether a middleware group with the given name exists.
func (*Router) Is ¶
Is reports whether the current route's name matches any of the patterns, with * as a glob. It is the alias for CurrentRouteNamed.
func (*Router) Match ¶
func (r *Router) Match(methods []string, pattern string, h http.Handler, mws ...pipeline.Middleware[http.Handler]) *Route
Match registers one handler under several methods.
r.Match([]string{"GET", "POST"}, "/search", search)
It returns the route of the first method, and that is the row a name is registered against: the others are in the table, they carry the name for display, and generating a URL from it has one answer rather than one per verb. They all point at the same pattern, so there is nothing to choose between.
An empty list of methods panics. It would otherwise register a route that answers nothing, at boot, silently -- and the symptom is a 404 on a path that the route table says exists.
func (*Router) Matched ¶
Matched registers a callback that fires after a route matches, before its middleware.
func (*Router) MergeWithLastGroup ¶
func (r *Router) MergeWithLastGroup(attributes map[string]any, prependExistingPrefix ...bool) map[string]any
MergeWithLastGroup merges attributes with the innermost open group's own, through Merge.
func (*Router) Middleware ¶
func (r *Router) Middleware(mws ...pipeline.Middleware[http.Handler]) *RouteRegistrar
Middleware returns a registrar that wraps every route in the given middleware.
func (*Router) MiddlewareGroup ¶
MiddlewareGroup registers a named bundle of middleware the kernel composes.
func (*Router) Model ¶
func (r *Router) Model(key string, class UrlRoutable, callback ...MissingModelFunc)
Model registers a model binding for a parameter key: the parameter resolves through the record type's own ResolveRouteBinding, under the Grant.
There is no container to resolve a class out of, so this takes a zero value of the type instead -- which is the same thing a container would have handed the binder.
func (*Router) Name ¶
func (r *Router) Name(name string) *RouteRegistrar
Name returns a registrar that prepends name to every route's name. The name is dot-joined, as Group.Name is.
func (*Router) Namespace ¶
func (r *Router) Namespace(ns string) *RouteRegistrar
Namespace returns a registrar that sets the namespace on every route. Go has no string-based controller resolution, so the namespace is recorded for display and for the URL generator, and does not change which handler answers.
func (*Router) NewRoute ¶
NewRoute builds a route bound to this router without registering it, for a caller assembling a table of its own.
func (*Router) Options ¶
func (r *Router) Options(pattern string, h http.Handler, mws ...pipeline.Middleware[http.Handler]) *Route
Options registers an OPTIONS route.
func (*Router) Patch ¶
func (r *Router) Patch(pattern string, h http.Handler, mws ...pipeline.Middleware[http.Handler]) *Route
Patch registers a PATCH route.
func (*Router) Pattern ¶
Pattern sets a global where pattern, applied to every route at registration.
func (*Router) PermanentRedirect ¶
PermanentRedirect registers a 301 redirect route.
func (*Router) Post ¶
func (r *Router) Post(pattern string, h http.Handler, mws ...pipeline.Middleware[http.Handler]) *Route
Post registers a POST route.
func (*Router) Prefix ¶
func (r *Router) Prefix(prefix string) *RouteRegistrar
Prefix returns a registrar that prepends prefix to every route registered through it.
func (*Router) PrependMiddlewareToGroup ¶
func (r *Router) PrependMiddlewareToGroup(name string, mw pipeline.Middleware[http.Handler]) *Router
PrependMiddlewareToGroup adds a middleware to the start of a group, if it is not already there.
func (*Router) PushMiddlewareToGroup ¶
PushMiddlewareToGroup adds a middleware to the end of a group, creating the group if it does not exist, if it is not already there.
func (*Router) Put ¶
func (r *Router) Put(pattern string, h http.Handler, mws ...pipeline.Middleware[http.Handler]) *Route
Put registers a PUT route.
func (*Router) Redirect ¶
Redirect registers a route that redirects to destination with status. The handler it installs writes the Location header and the status.
It is registered under Any so that a link or form of any verb that lands here is redirected; the status is the caller's to set (302 for Redirect, 301 for PermanentRedirect).
func (*Router) RemoveMiddlewareFromGroup ¶
func (r *Router) RemoveMiddlewareFromGroup(name string, mw pipeline.Middleware[http.Handler]) *Router
RemoveMiddlewareFromGroup removes a middleware from a group by identity.
func (*Router) ResolveMiddleware ¶
func (r *Router) ResolveMiddleware(middleware []pipeline.Middleware[http.Handler], excluded ...[]pipeline.Middleware[http.Handler]) []pipeline.Middleware[http.Handler]
ResolveMiddleware removes the excluded middleware from the list and drops duplicates. A middleware here is already the function to run, so there is no name or priority to resolve first.
func (*Router) RespondWithRoute ¶
RespondWithRoute dispatches the named route directly, bypassing the mux's match: it binds the request to the route and runs it. Used by a caller that has a route name and a request that did not arrive through the mux.
func (*Router) ServeHTTP ¶
func (r *Router) ServeHTTP(w http.ResponseWriter, req *http.Request)
ServeHTTP dispatches to the underlying mux.
func (*Router) SetViewRenderer ¶
func (r *Router) SetViewRenderer(vr ViewRenderer)
SetViewRenderer wires the view renderer the kernel built, so View routes can render. Without it, a View route answers 500.
func (*Router) SubstituteBindings ¶
func (r *Router) SubstituteBindings(ctx context.Context, g auth.Grant, rt *Route, req *http.Request) error
SubstituteBindings resolves the explicit binders for the route's parameters and installs what they returned as the value the handler reads.
The Grant is a parameter and not something read out of the context, because a parameter cannot be forgotten. Every binding is scoped by auth.Tenant(g), and a Grant with no tenant is refused before anything is looked up.
func (*Router) SubstituteImplicitBindings ¶
func (r *Router) SubstituteImplicitBindings(ctx context.Context, g auth.Grant, rt *Route, req *http.Request) error
SubstituteImplicitBindings runs the implicit binding -- every parameter with a registered model, resolved through it, scoped where the route asked for scoping -- or the callback SubstituteImplicitBindingsUsing installed in its place.
func (*Router) SubstituteImplicitBindingsUsing ¶
func (r *Router) SubstituteImplicitBindingsUsing(callback func(rt *Route, req *http.Request) error) *Router
SubstituteImplicitBindingsUsing installs callback in place of the default implicit binding.
func (*Router) View ¶
View registers a GET route that renders a view.
The view renderer is the one SetViewRenderer wired; without it, the route answers 500, which is the honest answer to "the view layer was not given to the router". The handler it installs calls the renderer, and the data map is carried in the route's defaults -- the same place Redirect carries its destination, which keeps the two redirect-and-view routes shaped alike.
type Routes ¶
type Routes struct {
// contains filtered or unexported fields
}
Routes is the table of registered routes, and the index by name.
func NewRoutes ¶
func NewRoutes() *Routes
NewRoutes returns an empty table.
A Router builds its own; this is for the caller that has to hold one before there is a router -- a test, and the URL generator the view layer is handed at boot.
func (*Routes) Add ¶
Add registers a route in the table and returns it. It is the exported form of the add the router uses, for a caller that holds a table it built separately -- a test, or a compiled route collection.
func (*Routes) GetByAction ¶
GetByAction returns the route whose action string is action, or nil. The action string is what Uses set -- "PostController@show" or "Closure". The lookup is a scan, because the action is set after registration through a fluent call, and an index maintained at registration would miss it.
func (*Routes) GetByName ¶
GetByName returns the route registered with name, or nil. It is the exported form the URL generator reads.
func (*Routes) GetRoutes ¶
GetRoutes returns the routes in registration order. It is an alias for All.
func (*Routes) GetRoutesByMethod ¶
GetRoutesByMethod returns the routes that answer the given method, in registration order. A route registered with ANY answers every method.
func (*Routes) GetRoutesByName ¶
GetRoutesByName returns a copy of the name index.
func (*Routes) HasNamedRoute ¶
HasNamedRoute reports whether a route with the given name is registered.
func (*Routes) Match ¶
Match returns the route that answers the request, or nil. It is the table's side of what the mux does: the mux has already picked a route and dispatched it, but Current, a middleware that runs before the handler, and a custom dispatcher all need to find the route from the request, and they do it here.
Non-fallback routes are tried first, in registration order; a fallback route is returned only if nothing else matched.
func (*Routes) Must ¶
Must is Route for the places that cannot handle an error -- a template helper, mostly. It returns the message as the href, so a broken link says what is wrong instead of pointing at "/".
func (*Routes) Refresh ¶
func (t *Routes) Refresh()
Refresh rebuilds the name index from the route list. A name set fluently after the route was added to the table -- through Name, which writes the field and then calls register -- is already indexed; Refresh is for the caller that rewrites names in bulk and needs the index to follow.
func (*Routes) RefreshActionLookups ¶
func (t *Routes) RefreshActionLookups()
RefreshActionLookups is a no-op: the action index is a scan, not a maintained index, so there is nothing to rebuild.
func (*Routes) RefreshNameLookups ¶
func (t *Routes) RefreshNameLookups()
RefreshNameLookups is an alias for Refresh.
func (*Routes) Requirements ¶ added in v0.25.0
func (t *Routes) Requirements() []Requirement
Requirements lists the routes that declare a required action, in registration order.
It is the catalogue, and the router is the enumeration. A list of actions kept beside the routes would be wrong the first time somebody added one without remembering it, and a permissions screen reading that list would offer a permission no route requires -- or leave out one that some route does.
The sibling rows of a single registration are left out, so a resource route registered as PUT and PATCH is one requirement rather than two.
func (*Routes) Route ¶
Route builds the path of a named route, filling the parameters in order.
Route("home") -> "/"
Route("invoices.show", "42") -> "/invoices/42"
A hardcoded "/invoices/"+id compiles and keeps compiling after the route moves. This does not: an unknown name or a wrong number of parameters is an error the caller sees, not a 404 the person sees.
It returns an error rather than panicking, because a URL is often built from data -- and a panic in a template renderer takes the whole page down to report something a broken link would have said better.
type SessionStore ¶
type SessionStore interface {
Get(key string, def ...any) any
Put(key string, value any)
Pull(key string, def ...any) any
PreviousURL() string
}
SessionStore is what UrlGenerator reads the previous URL and the intended URL from.
Declared here rather than importing hesape/session, so that a package that only generates URLs does not depend on the session implementation.
type StreamCallback ¶
StreamCallback is the callback ResponseFactory.Stream, StreamDownload and EventStream take. It writes to w, which flushes after every write.
The context is the request's: it is done when the client hangs up.
type StreamedResponse ¶
type StreamedResponse struct {
// Callback writes the body.
Callback StreamCallback
// Status is the status code, 200 when zero.
Status int
// Headers are sent before the first byte of the body.
Headers http.Header
// contains filtered or unexported fields
}
StreamedResponse is a status, a set of headers and a body that is produced while it is being sent. ResponseFactory.Stream, StreamJSON, EventStream and StreamDownload all build one.
The fields are public because the whole point of returning a response instead of writing one is that a caller may still add a header to it.
func (*StreamedResponse) SendContent ¶
func (r *StreamedResponse) SendContent(w http.ResponseWriter, req *http.Request) error
SendContent writes the headers, the status and then the body, flushing as it goes.
It returns what the callback returned, which ServeHTTP cannot: by the time the callback fails the status line is already sent, so there is nowhere left to report it to the client, and a caller that wants to log it needs this.
func (*StreamedResponse) ServeHTTP ¶
func (r *StreamedResponse) ServeHTTP(w http.ResponseWriter, req *http.Request)
ServeHTTP makes a StreamedResponse an http.Handler, so a route may answer with one directly. It is SendContent with the arguments a handler is given, mirroring how hhttp.Response does it.
type UrlGenerator ¶
type UrlGenerator struct {
// contains filtered or unexported fields
}
UrlGenerator builds every address an application needs: the route named "invoices.show", the asset at "/css/app.css", the absolute address of the page being viewed.
It is the single answer to "where is this thing". A view that concatenates "/invoices/" with an id compiles and keeps compiling after the route moves; a call through UrlGenerator does not, and the developer finds out at build time rather than from a broken link.
Parameters are a map here. The positional shape -- route("invoices.show", 42), matched against the pattern in order -- is Routes.Route, because a Go map has no order to match against.
func NewUrlGenerator ¶
func NewUrlGenerator(routes *Routes, request *http.Request, assetRoot ...string) *UrlGenerator
NewUrlGenerator returns a generator backed by routes.
The request may be nil for a generator that only builds relative URLs: a CLI command that prints a list of links, or a test that has no incoming request to read the scheme and host from.
func (*UrlGenerator) Action ¶
func (g *UrlGenerator) Action(action string, parameters map[string]any, absolute bool) (string, error)
Action returns the URL of a controller action, written as "InvoiceController@show".
func (*UrlGenerator) Asset ¶
func (g *UrlGenerator) Asset(path string, secure *bool) string
Asset returns the URL of an application asset.
func (*UrlGenerator) AssetFrom ¶
func (g *UrlGenerator) AssetFrom(root, path string, secure *bool) string
AssetFrom returns the URL of an asset served from a custom root, such as a CDN.
func (*UrlGenerator) Current ¶
func (g *UrlGenerator) Current() string
Current returns the URL of the current request without its query string.
func (*UrlGenerator) Defaults ¶
func (g *UrlGenerator) Defaults(defaults map[string]any)
Defaults sets default values for named route parameters -- the locale segment every URL carries, typically.
func (*UrlGenerator) ForceHttps ¶
func (g *UrlGenerator) ForceHttps(force ...bool)
ForceHttps is ForceScheme("https").
func (*UrlGenerator) ForceRootUrl
deprecated
func (g *UrlGenerator) ForceRootUrl(root string)
ForceRootUrl sets the origin every generated URL is built from.
Deprecated: use UseOrigin.
func (*UrlGenerator) ForceScheme ¶
func (g *UrlGenerator) ForceScheme(scheme string)
ForceScheme forces every generated URL to a scheme. An empty scheme clears the force.
func (*UrlGenerator) Format ¶
func (g *UrlGenerator) Format(root, path string, route ...*Route) string
Format joins a root and a path into one URL, through the host and path formatters when they are set.
func (*UrlGenerator) FormatHostUsing ¶
func (g *UrlGenerator) FormatHostUsing(callback func(root string, route *Route) string) *UrlGenerator
FormatHostUsing sets the callback Format uses to rewrite the host of a generated URL.
func (*UrlGenerator) FormatParameters ¶
func (g *UrlGenerator) FormatParameters(parameters map[string]any) map[string]any
FormatParameters replaces every value that knows its own route key -- a record passed where an id was expected -- with that key.
This takes the map shape a route's parameters have. The list shape is applied inline by To, over the same conversion.
func (*UrlGenerator) FormatPathUsing ¶
func (g *UrlGenerator) FormatPathUsing(callback func(path string, route *Route) string) *UrlGenerator
FormatPathUsing sets the callback Format uses to rewrite the path of a generated URL.
func (*UrlGenerator) FormatRoot ¶
func (g *UrlGenerator) FormatRoot(scheme string, root ...string) string
FormatRoot returns the base URL -- scheme and host -- for the request, or for the root given.
func (*UrlGenerator) FormatScheme ¶
func (g *UrlGenerator) FormatScheme(secure *bool) string
FormatScheme returns the scheme with its "://" suffix.
func (*UrlGenerator) Full ¶
func (g *UrlGenerator) Full() string
Full returns the full URL of the current request, query string included.
func (*UrlGenerator) GetDefaultParameters ¶
func (g *UrlGenerator) GetDefaultParameters() map[string]any
GetDefaultParameters returns the default route parameters Defaults set.
func (*UrlGenerator) GetRequest ¶
func (g *UrlGenerator) GetRequest() *http.Request
GetRequest returns the current request, or nil.
func (*UrlGenerator) GetRootControllerNamespace ¶
func (g *UrlGenerator) GetRootControllerNamespace() string
GetRootControllerNamespace returns the root controller namespace.
func (*UrlGenerator) HasCorrectSignature ¶
func (g *UrlGenerator) HasCorrectSignature(request *http.Request, absolute bool, ignoreQuery ...string) bool
HasCorrectSignature reports whether the signature on the request matches the address the request arrived on.
Every key the resolver returns is tried, so a key being rotated out still opens the links it signed.
func (*UrlGenerator) HasValidRelativeSignature ¶
func (g *UrlGenerator) HasValidRelativeSignature(request *http.Request, ignoreQuery ...string) bool
HasValidRelativeSignature is HasValidSignature checked against the relative address.
func (*UrlGenerator) HasValidSignature ¶
func (g *UrlGenerator) HasValidSignature(request *http.Request, absolute bool, ignoreQuery ...string) bool
HasValidSignature reports whether the request carries a signature that is both correct and unexpired.
func (*UrlGenerator) IsValidUrl ¶
func (g *UrlGenerator) IsValidUrl(path string) bool
IsValidUrl reports whether path is already a complete URL rather than one this generator should build.
func (*UrlGenerator) PathFormatter ¶
func (g *UrlGenerator) PathFormatter() func(path string, route *Route) string
PathFormatter returns the path formatter in use, or one that changes nothing.
func (*UrlGenerator) Previous ¶
func (g *UrlGenerator) Previous(fallback ...string) string
Previous reads the Referer header first, then the session, then the fallback, then "/".
func (*UrlGenerator) PreviousPath ¶
func (g *UrlGenerator) PreviousPath(fallback ...string) string
PreviousPath returns the path part of Previous, with the application root and any query string removed.
func (*UrlGenerator) Query ¶
Query returns the absolute URL for a path with the given query parameters merged onto any the path already carried.
func (*UrlGenerator) ResolveMissingNamedRoutesUsing ¶
func (g *UrlGenerator) ResolveMissingNamedRoutesUsing(resolver func(name string, parameters map[string]any, absolute bool) (string, error)) *UrlGenerator
ResolveMissingNamedRoutesUsing sets the callback that gets a chance to answer a name this table does not know -- a route registered by another service, in front of the same domain.
func (*UrlGenerator) Secure ¶
func (g *UrlGenerator) Secure(path string, parameters ...any) string
Secure returns the absolute https URL for a path.
func (*UrlGenerator) SecureAsset ¶
func (g *UrlGenerator) SecureAsset(path string) string
SecureAsset returns the https URL of an application asset.
func (*UrlGenerator) SetKeyResolver ¶
func (g *UrlGenerator) SetKeyResolver(keyResolver func() []string) *UrlGenerator
SetKeyResolver sets the callback that resolves the signing keys. It returns every key a signature may have been made with, newest first: signing uses the first, verifying tries them all, which is what makes a key rotation something other than an outage.
func (*UrlGenerator) SetRequest ¶
func (g *UrlGenerator) SetRequest(request *http.Request)
SetRequest replaces the request, clears the cached scheme and root, and carries the route defaults over to the new route URL generator.
func (*UrlGenerator) SetRootControllerNamespace ¶
func (g *UrlGenerator) SetRootControllerNamespace(rootNamespace string) *UrlGenerator
SetRootControllerNamespace sets the root controller namespace formatAction qualifies an action name with.
func (*UrlGenerator) SetRoutes ¶
func (g *UrlGenerator) SetRoutes(routes *Routes) *UrlGenerator
SetRoutes replaces the route table URLs are generated against.
func (*UrlGenerator) SetSessionResolver ¶
func (g *UrlGenerator) SetSessionResolver(sessionResolver func() SessionStore) *UrlGenerator
SetSessionResolver sets the callback that resolves the session store.
func (*UrlGenerator) SignatureHasNotExpired ¶
func (g *UrlGenerator) SignatureHasNotExpired(request *http.Request) bool
SignatureHasNotExpired reports whether the request's signature has not expired. A link with no expiry never runs out; one whose expiry is in the past has.
func (*UrlGenerator) SignedRoute ¶
func (g *UrlGenerator) SignedRoute(name string, parameters map[string]any, expiration time.Time, absolute bool) (string, error)
SignedRoute returns the URL of a named route carrying an HMAC over the address it was issued for.
The signature covers the whole address, expiry included, so appending anything to a signed link invalidates it -- a route behind middleware.ValidateSignature cannot be handed a value its author did not sign. A zero expiration means the link does not run out.
It returns an error for reserved parameter names, an unknown route, or no signing key.
func (*UrlGenerator) TemporarySignedRoute ¶
func (g *UrlGenerator) TemporarySignedRoute(name string, expiration time.Time, parameters map[string]any, absolute bool) (string, error)
TemporarySignedRoute is SignedRoute with the expiration and parameters reordered.
func (*UrlGenerator) To ¶
func (g *UrlGenerator) To(path string, extra []any, secure *bool) string
To returns the absolute URL for a path.
To("invoices/42", nil, nil) // "https://example.com/invoices/42"
A path that is already a valid URL is returned unchanged. Anything in extra is appended as further path segments.
func (*UrlGenerator) ToRoute ¶
func (g *UrlGenerator) ToRoute(route *Route, parameters map[string]any, absolute bool) (string, error)
ToRoute builds the URL of a route already in hand.
func (*UrlGenerator) UseAssetOrigin ¶
func (g *UrlGenerator) UseAssetOrigin(root string)
UseAssetOrigin sets the origin every generated asset URL is built from -- a CDN host, typically.
func (*UrlGenerator) UseOrigin ¶
func (g *UrlGenerator) UseOrigin(root string)
UseOrigin sets the origin every generated URL is built from.
func (*UrlGenerator) WithKeyResolver ¶
func (g *UrlGenerator) WithKeyResolver(keyResolver func() []string) *UrlGenerator
WithKeyResolver returns a copy of the generator signing with a different key, which is how a link is issued for a tenant whose key is not the application's.
type UrlRoutable ¶
type UrlRoutable interface {
// GetRouteKey returns the value that stands for this record in a URL.
GetRouteKey() string
// GetRouteKeyName returns the column that value lives in, "id" unless
// the route binds by another.
GetRouteKeyName() string
// ResolveRouteBinding resolves value into a record. An empty field means
// GetRouteKeyName. It returns nil when nothing matched, which is a 404
// and not an error.
ResolveRouteBinding(ctx context.Context, g auth.Grant, value, field string) (UrlRoutable, error)
// ResolveChildRouteBinding is the same lookup, restricted to the children
// of this record, which is what a scoped nested resource resolves
// through.
ResolveChildRouteBinding(ctx context.Context, g auth.Grant, childType, value, field string) (UrlRoutable, error)
}
UrlRoutable is what a record implements to be reachable from a URL: it knows the value that stands for it in a path, the column that value lives in, and how to find itself again from that value.
The tenant comes from the Grant, never from the URL ¶
ResolveRouteBinding receives an auth.Grant and MUST filter by auth.Tenant(g). This is the exact place a multi-tenant application leaks: /invoices/9 arrives, the id is read off the path, a row is loaded by that id alone, and the reader is handed another customer's invoice. No policy was violated -- none was consulted. The implementation is one WHERE clause:
func (Invoice) ResolveRouteBinding(ctx context.Context, g auth.Grant, value, field string) (routing.UrlRoutable, error) {
if field == "" {
field = Invoice{}.GetRouteKeyName()
}
return invoices.FindBy(ctx, g, field, value) // scoped by auth.Tenant(g)
}
The Grant is a parameter and not something read out of the context, because a parameter cannot be forgotten: a resolver written without it does not compile.
type ViewRenderer ¶
type ViewRenderer interface {
// Render writes the rendered view to w. The status is the one a View
// route answers with, 200 by default, and headers are the caller's to
// add (the CSRF header, for instance).
Render(w io.Writer, name string, data any, status int, headers http.Header) error
}
ViewRenderer renders a named view with data. It is the minimal contract Router.View needs, and it is here rather than importing hesape/view because that import would make routing depend on the view layer -- and the view layer depends on routing for URL generation, which is a cycle. The concrete is hesape/view's Factory, wired by the kernel through SetViewRenderer.
The contract is what a redirect-to-view route touches: make a view by name, hand it data, and let it write the response. How the view renders is the view layer's concern.
Source Files
¶
Directories
¶
| Path | Synopsis |
|---|---|
|
Package console is empty, and stays that way: console commands live in the project's own binary, through the switch in bootstrap/console.go, rather than being registered here.
|
Package console is empty, and stays that way: console commands live in the project's own binary, through the switch in bootstrap/console.go, rather than being registered here. |
|
Package contracts is empty, and stays that way: an interface lives in the package that consumes it, not in one of its own.
|
Package contracts is empty, and stays that way: an interface lives in the package that consumes it, not in one of its own. |
|
Package controllers holds the contract a controller uses to declare its own middleware: HasMiddleware, and the MiddlewareDef pairs it returns.
|
Package controllers holds the contract a controller uses to declare its own middleware: HasMiddleware, and the MiddlewareDef pairs it returns. |
|
Package exceptions holds the error types a route, a signed URL or a streamed response can fail with: an invalid signature, a missing rate limiter, a route URL that is missing required parameters, a backed-enum parameter that matched no case, and a streamed response that failed after the headers were already sent.
|
Package exceptions holds the error types a route, a signed URL or a streamed response can fail with: an invalid signature, a missing rate limiter, a route URL that is missing required parameters, a backed-enum parameter that matched no case, and a streamed response that failed after the headers were already sent. |
|
Package matching holds the four checks that decide whether a route answers a request: its host, its method, its scheme and its path.
|
Package matching holds the four checks that decide whether a route answers a request: its host, its method, its scheme and its path. |
|
Package middleware holds the two middlewares that decide whether a request reaches a route at all.
|
Package middleware holds the two middlewares that decide whether a request reaches a route at all. |