feat(06-01): grow router verbs, factories, and guard registry

- Add Post/Put/Patch/Delete on pact.Router and surf Router/Group
- Resolve name:param middleware via RegisterMiddlewareFactory
- Add bouncer.Registry with Guard, CredentialGuard, UnauthorizedWriter
- Re-express jwt as NewJWTGuard without changing Middleware bodies
This commit is contained in:
Jakub Zych
2026-09-19 18:59:12 +02:00
parent ecab09c37a
commit d376b1be2d
9 changed files with 678 additions and 14 deletions

View File

@@ -21,6 +21,11 @@ type namedMiddleware struct {
fn pact.Middleware
}
type namedMiddlewareFactory struct {
pluginID string
fn func(param string) pact.Middleware
}
type route struct {
pluginID string
method string
@@ -36,6 +41,7 @@ type Router struct {
prefix string
middleware []string
named map[string]namedMiddleware
factories map[string]namedMiddlewareFactory
routes []route
seen map[string]string
origins []string
@@ -58,9 +64,10 @@ type Group struct {
// New returns an empty router.
func New(origins []string) *Router {
return &Router{
named: make(map[string]namedMiddleware),
seen: make(map[string]string),
origins: origins,
named: make(map[string]namedMiddleware),
factories: make(map[string]namedMiddlewareFactory),
seen: make(map[string]string),
origins: origins,
}
}
@@ -79,6 +86,23 @@ func (r *Router) RegisterMiddleware(pluginID, name string, fn pact.Middleware) e
return nil
}
// RegisterMiddlewareFactory stores a parameterized middleware builder.
// At wrap time a name not found in r.named is split on the first ':' and
// the base is looked up here. Duplicate factory names fail.
func (r *Router) RegisterMiddlewareFactory(pluginID, name string, fn func(param string) pact.Middleware) error {
if r == nil {
return fmt.Errorf("surf: router is nil")
}
if name == "" || fn == nil {
return fmt.Errorf("surf: plugin %q registered empty middleware factory", pluginID)
}
if existing, ok := r.factories[name]; ok {
return fmt.Errorf("surf: middleware factory %q already registered by %s", name, existing.pluginID)
}
r.factories[name] = namedMiddlewareFactory{pluginID: pluginID, fn: fn}
return nil
}
// BindPlugin records the plugin declaring subsequent routes.
func (r *Router) BindPlugin(id string) {
if r != nil {
@@ -104,7 +128,35 @@ func (r *Router) Get(path string, handler http.HandlerFunc, middleware ...string
if r == nil {
return
}
r.add(r.pluginID, r.prefix, r.middleware, path, handler, middleware)
r.add(r.pluginID, r.prefix, r.middleware, http.MethodGet, path, handler, middleware)
}
func (r *Router) Post(path string, handler http.HandlerFunc, middleware ...string) {
if r == nil {
return
}
r.add(r.pluginID, r.prefix, r.middleware, http.MethodPost, path, handler, middleware)
}
func (r *Router) Put(path string, handler http.HandlerFunc, middleware ...string) {
if r == nil {
return
}
r.add(r.pluginID, r.prefix, r.middleware, http.MethodPut, path, handler, middleware)
}
func (r *Router) Patch(path string, handler http.HandlerFunc, middleware ...string) {
if r == nil {
return
}
r.add(r.pluginID, r.prefix, r.middleware, http.MethodPatch, path, handler, middleware)
}
func (r *Router) Delete(path string, handler http.HandlerFunc, middleware ...string) {
if r == nil {
return
}
r.add(r.pluginID, r.prefix, r.middleware, http.MethodDelete, path, handler, middleware)
}
func (g *Group) Group(prefix string, middleware []string, fn func(pact.Router)) {
@@ -125,7 +177,35 @@ func (g *Group) Get(path string, handler http.HandlerFunc, middleware ...string)
if g == nil || g.router == nil {
return
}
g.router.add(g.pluginID, g.prefix, g.middleware, path, handler, middleware)
g.router.add(g.pluginID, g.prefix, g.middleware, http.MethodGet, path, handler, middleware)
}
func (g *Group) Post(path string, handler http.HandlerFunc, middleware ...string) {
if g == nil || g.router == nil {
return
}
g.router.add(g.pluginID, g.prefix, g.middleware, http.MethodPost, path, handler, middleware)
}
func (g *Group) Put(path string, handler http.HandlerFunc, middleware ...string) {
if g == nil || g.router == nil {
return
}
g.router.add(g.pluginID, g.prefix, g.middleware, http.MethodPut, path, handler, middleware)
}
func (g *Group) Patch(path string, handler http.HandlerFunc, middleware ...string) {
if g == nil || g.router == nil {
return
}
g.router.add(g.pluginID, g.prefix, g.middleware, http.MethodPatch, path, handler, middleware)
}
func (g *Group) Delete(path string, handler http.HandlerFunc, middleware ...string) {
if g == nil || g.router == nil {
return
}
g.router.add(g.pluginID, g.prefix, g.middleware, http.MethodDelete, path, handler, middleware)
}
// Where attaches a compiled regex constraint to the last route, matching PHP ->where().
@@ -184,9 +264,9 @@ func (r *Router) addConstraint(c Constraint) {
last.constraints = append(last.constraints, c)
}
func (r *Router) add(pluginID, prefix string, groupMW []string, path string, handler http.HandlerFunc, extra []string) {
func (r *Router) add(pluginID, prefix string, groupMW []string, method, path string, handler http.HandlerFunc, extra []string) {
full := joinPath(prefix, path)
key := "GET " + full
key := method + " " + full
if prev, ok := r.seen[key]; ok {
r.compileErr = fmt.Errorf("surf: duplicate route %s registered by %s and %s", key, prev, pluginID)
return
@@ -196,7 +276,7 @@ func (r *Router) add(pluginID, prefix string, groupMW []string, path string, han
mw = append(mw, extra...)
r.routes = append(r.routes, route{
pluginID: pluginID,
method: "GET",
method: method,
path: full,
handler: handler,
middleware: mw,
@@ -213,7 +293,7 @@ func (r *Router) compile() (http.Handler, error) {
if err != nil {
return nil, err
}
mux.Handle("GET "+rt.path, h)
mux.Handle(rt.method+" "+rt.path, h)
}
return recoverJSON(cors(r.origins, mux)), nil
}
@@ -224,11 +304,18 @@ func (r *Router) wrap(rt route) (http.Handler, error) {
h = orgSlot(h)
for i := len(rt.middleware) - 1; i >= 0; i-- {
name := rt.middleware[i]
named, ok := r.named[name]
if !ok {
return nil, fmt.Errorf("surf: plugin %q references unknown middleware %q", rt.pluginID, name)
if named, ok := r.named[name]; ok {
h = named.fn(h)
continue
}
h = named.fn(h)
base, param, hasParam := strings.Cut(name, ":")
if hasParam {
if factory, ok := r.factories[base]; ok {
h = factory.fn(param)(h)
continue
}
}
return nil, fmt.Errorf("surf: plugin %q references unknown middleware %q", rt.pluginID, name)
}
h = locale(h)
return h, nil
@@ -246,6 +333,15 @@ func Assemble(app *backpack.App, plugins []party.Plugin) (http.Handler, error) {
}
}
}
for _, p := range plugins {
if hf, ok := p.(pact.HasMiddlewareFactories); ok {
for name, fn := range hf.MiddlewareFactories() {
if err := r.RegisterMiddlewareFactory(p.ID(), name, fn); err != nil {
return nil, err
}
}
}
}
for _, p := range plugins {
r.BindPlugin(p.ID())
if hr, ok := p.(pact.HasRoutes); ok {