Files
member-console/internal/web/route_url.go
T
cgalo5758 88db730fcc Add dual licensing and SPDX headers
Introduce a commercial license option alongside AGPL-3.0-only, require a
CLA for contributors, and document the terms in COMMERCIAL.md and
NOTICE. Add a script to stamp SPDX headers on Go files and apply it
across the tree.
2026-09-06 02:29:42 -05:00

62 lines
1.9 KiB
Go

// SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Commercial
// SPDX-FileCopyrightText: 2025-2026 Christian Galo
package web
import (
"fmt"
"net/url"
"strings"
)
// RouteURL substitutes {placeholder} markers in a route pattern with the
// given args in order. The first arg replaces the first placeholder, and
// so on. Path components are escaped via url.PathEscape so values
// containing slashes or reserved characters do not break route matching.
//
// Pattern strings must match the path portion of a registered route (the
// pattern as written in mux.HandleFunc, minus the method), e.g.
// "/partials/operator/products/{productID}/prices". Templates use this
// helper instead of literal `hx-get="/partials/operator/products/{{.ID}}/prices"`
// so cmd/lint can statically verify every URL reference resolves to a
// registered handler. See docs/operator-ux-conventions.md §9.
//
// Template registration name is "routeURL". Handlers wire it in with:
//
// template.FuncMap{
// "routeURL": web.RouteURL,
// ...
// }
//
// Returns an error if placeholder count and arg count disagree — surfaced
// as a template execution error in dev and to the slog handler in prod.
func RouteURL(pattern string, args ...any) (string, error) {
var out strings.Builder
out.Grow(len(pattern))
argIdx := 0
i := 0
for i < len(pattern) {
if pattern[i] != '{' {
out.WriteByte(pattern[i])
i++
continue
}
end := strings.IndexByte(pattern[i:], '}')
if end == -1 {
return "", fmt.Errorf("routeURL: unclosed placeholder in %q", pattern)
}
if argIdx >= len(args) {
return "", fmt.Errorf("routeURL: not enough args for %q (placeholders > %d args)", pattern, len(args))
}
out.WriteString(url.PathEscape(fmt.Sprint(args[argIdx])))
argIdx++
i += end + 1
}
if argIdx != len(args) {
return "", fmt.Errorf("routeURL: too many args for %q (got %d, want %d)", pattern, len(args), argIdx)
}
return out.String(), nil
}