658 lines
19 KiB
Go
658 lines
19 KiB
Go
package router
|
|
|
|
import (
|
|
"fmt"
|
|
"log/slog"
|
|
"net/http"
|
|
"path"
|
|
"slices"
|
|
"strings"
|
|
|
|
"ruben/inventory2/internal/consts"
|
|
"ruben/inventory2/internal/server/response"
|
|
)
|
|
|
|
type (
|
|
Mux struct {
|
|
Mux *http.ServeMux
|
|
middleware []response.Middleware
|
|
log *slog.Logger
|
|
}
|
|
)
|
|
|
|
var (
|
|
ErrHandlerNotFound = fmt.Errorf("%w: handler not found", consts.ErrNotFound)
|
|
)
|
|
|
|
func NewMux(log *slog.Logger, ms ...response.Middleware) *Mux {
|
|
return &Mux{
|
|
Mux: http.NewServeMux(),
|
|
middleware: ms,
|
|
log: log,
|
|
}
|
|
}
|
|
|
|
func (m *Mux) AddMiddleware(ms ...response.Middleware) *Mux {
|
|
m.middleware = append(m.middleware, ms...)
|
|
return m
|
|
}
|
|
|
|
func (m *Mux) traced(method, msg string, args ...any) func(func() (finalArgs []any)) {
|
|
return logTraceAndDefer(m.log, "Mux", method, msg, args...)
|
|
}
|
|
|
|
func (m *Mux) Handle(pattern string, fn response.HandlerFunc) {
|
|
defer m.traced("Handle", "", "pattern", pattern)(nil)
|
|
m.Mux.Handle(pattern, response.Handler(m.applyMiddleware(fn)))
|
|
}
|
|
|
|
func (m *Mux) applyMiddleware(fn response.HandlerFunc) response.HandlerFunc {
|
|
return applyMiddleware(fn, m.middleware...)
|
|
}
|
|
|
|
func (m *Mux) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|
m.Mux.ServeHTTP(w, r)
|
|
}
|
|
|
|
// TODO: support for methods?
|
|
// TODO: strip the prefix from the call to the subrouter handler call
|
|
// Route does not accept methods or ... wildcards
|
|
// func (m *Mux) Route(basePathPattern string, sr Subrouter) {
|
|
func (m *Mux) Route(pattern string, sr Subrouter) {
|
|
defer m.traced("Route", "pattern", pattern)(nil)
|
|
|
|
// ---
|
|
method, segments, _ := getHTTPMethodAndPathSegments(m.log, pattern)
|
|
|
|
if pattern == "" || (len(segments) == 0 && pattern[len(pattern)-1] != '/') {
|
|
panic("invalid subpath: " + pattern)
|
|
}
|
|
|
|
numPatternSegments := len(segments)
|
|
|
|
handler := response.Handler(m.applyMiddleware(buildSubrouterHandlerFunc(m.log, numPatternSegments, sr)))
|
|
|
|
cleanPattern := "/"
|
|
if joinedSegments := strings.Join(segments, "/"); joinedSegments != "" {
|
|
cleanPattern = "/" + joinedSegments + "/"
|
|
}
|
|
if method != "" {
|
|
cleanPattern = method + " " + cleanPattern
|
|
}
|
|
logTrace(m.log, "mux", "Route", "cleanPattern: "+cleanPattern)
|
|
// ---
|
|
|
|
/*
|
|
trimmedSubpathPattern := strings.Trim(path.Clean(basePathPattern), "/")
|
|
if trimmedSubpathPattern == "" && basePathPattern != "/" {
|
|
panic("invalid subpath: " + basePathPattern)
|
|
}
|
|
|
|
var trimmedPatternSegments []string
|
|
if trimmedSubpathPattern != "" {
|
|
trimmedPatternSegments = strings.Split(trimmedSubpathPattern, "/")
|
|
}
|
|
numPatternSegments := len(trimmedPatternSegments)
|
|
|
|
handler := response.Handler(m.applyMiddleware(buildSubrouterHandlerFunc(numPatternSegments, sr)))
|
|
|
|
cleanPattern := "/"
|
|
if joinedSegments := strings.Join(trimmedPatternSegments, "/"); joinedSegments != "" {
|
|
cleanPattern = "/" + joinedSegments + "/"
|
|
}
|
|
fmt.Println("Mux.Route: cleanPattern:", cleanPattern)
|
|
*/
|
|
|
|
if method != "" {
|
|
m.Mux.Handle(method+" "+cleanPattern, handler)
|
|
} else {
|
|
m.Mux.Handle("GET "+cleanPattern, handler)
|
|
m.Mux.Handle("POST "+cleanPattern, handler)
|
|
m.Mux.Handle("PUT "+cleanPattern, handler)
|
|
m.Mux.Handle("PATCH "+cleanPattern, handler)
|
|
m.Mux.Handle("DELETE "+cleanPattern, handler)
|
|
}
|
|
|
|
}
|
|
|
|
type (
|
|
Subrouter interface {
|
|
Handler(r *http.Request) (fn response.HandlerFunc, pathParams map[string]string, found bool)
|
|
}
|
|
|
|
SubMux struct {
|
|
tree *muxTree
|
|
middleware []response.Middleware
|
|
log *slog.Logger
|
|
}
|
|
)
|
|
|
|
func NewSubMux(log *slog.Logger, ms ...response.Middleware) *SubMux {
|
|
return &SubMux{
|
|
tree: newMuxTree(log.WithGroup("muxTree")),
|
|
middleware: ms,
|
|
log: log,
|
|
}
|
|
}
|
|
|
|
func (m *SubMux) traced(method, msg string, args ...any) func(func() (finalArgs []any)) {
|
|
return logTraceAndDefer(m.log, "SubMux", method, msg, args...)
|
|
}
|
|
|
|
// TODO: this is capturing all subroutes!
|
|
func (m *SubMux) Handle(pattern string, fn response.HandlerFunc) {
|
|
defer m.traced("Handle", "", "pattern", pattern)(nil)
|
|
m.tree.set(pattern, m.applyMiddleware(fn))
|
|
}
|
|
|
|
// TODO: need http method support
|
|
// Route does not accept methods or ... wildcards
|
|
// func (m *SubMux) Route(basePathPattern string, sr Subrouter) {
|
|
func (m *SubMux) Route(pattern string, sr Subrouter) {
|
|
//defer m.traced("Route", "", "basePathPattern", basePathPattern)(nil)
|
|
defer m.traced("Route", "", "pattern", pattern)(nil)
|
|
method, segments, _ := getHTTPMethodAndPathSegments(m.log, pattern)
|
|
|
|
if pattern == "" || (len(segments) == 0 && pattern[len(pattern)-1] != '/') {
|
|
panic("invalid subpath: " + pattern)
|
|
}
|
|
|
|
numPatternSegments := len(segments)
|
|
|
|
handler := m.applyMiddleware(buildSubrouterHandlerFunc(m.log, numPatternSegments, sr))
|
|
|
|
cleanPattern := "/"
|
|
if joinedSegments := strings.Join(segments, "/"); joinedSegments != "" {
|
|
cleanPattern = "/" + joinedSegments + "/"
|
|
}
|
|
cleanPattern = path.Clean(cleanPattern)
|
|
if method != "" {
|
|
cleanPattern = method + " " + cleanPattern
|
|
}
|
|
logTrace(m.log, "SubMux", "Route", "Handle about to be called", "cleanPattern", cleanPattern)
|
|
// ---
|
|
|
|
/*
|
|
// ---
|
|
trimmedSubpathPattern := strings.Trim(path.Clean(basePathPattern), "/")
|
|
if trimmedSubpathPattern == "" && basePathPattern != "/" {
|
|
panic("invalid subpath: " + basePathPattern)
|
|
}
|
|
|
|
var trimmedPatternSegments []string
|
|
if trimmedSubpathPattern != "" {
|
|
trimmedPatternSegments = strings.Split(trimmedSubpathPattern, "/")
|
|
}
|
|
numPatternSegments := len(trimmedPatternSegments)
|
|
|
|
handler := m.applyMiddleware(buildSubrouterHandlerFunc(m.log, numPatternSegments, sr))
|
|
|
|
// TODO: we're wrapping the pattern method!
|
|
cleanPattern := "/"
|
|
if joinedSegments := strings.Join(trimmedPatternSegments, "/"); joinedSegments != "" {
|
|
cleanPattern = "/" + joinedSegments + "/"
|
|
}
|
|
// ---
|
|
*/
|
|
|
|
m.Handle(cleanPattern, handler)
|
|
|
|
/*
|
|
m.Mux.Handle("GET "+cleanPattern, handler)
|
|
m.Mux.Handle("POST "+cleanPattern, handler)
|
|
m.Mux.Handle("PUT "+cleanPattern, handler)
|
|
m.Mux.Handle("PATCH "+cleanPattern, handler)
|
|
m.Mux.Handle("DELETE "+cleanPattern, handler)
|
|
*/
|
|
}
|
|
|
|
// // Route does not accept methods or ... wildcards
|
|
// func (m *SubMux) Route(basePathPattern string, sr Subrouter) {
|
|
// trimmedSubpathPattern := strings.Trim(path.Clean(basePathPattern), "/")
|
|
// if trimmedSubpathPattern == "" && basePathPattern != "/" {
|
|
// panic("invalid subpath: " + basePathPattern)
|
|
// }
|
|
//
|
|
// var trimmedPatternSegments []string
|
|
// if trimmedSubpathPattern != "" {
|
|
// trimmedPatternSegments = strings.Split(trimmedSubpathPattern, "/")
|
|
// }
|
|
// numPatternSegments := len(trimmedPatternSegments)
|
|
//
|
|
// handler := m.applyMiddleware(buildSubrouterHandlerFunc(numPatternSegments, sr))
|
|
//
|
|
// // TODO: we're wrapping the pattern method!
|
|
// cleanPattern := "/"
|
|
// if joinedSegments := strings.Join(trimmedPatternSegments, "/"); joinedSegments != "" {
|
|
// cleanPattern = "/" + joinedSegments + "/"
|
|
// }
|
|
//
|
|
// m.Handle(cleanPattern, handler)
|
|
//
|
|
// /*
|
|
// m.Mux.Handle("GET "+cleanPattern, handler)
|
|
// m.Mux.Handle("POST "+cleanPattern, handler)
|
|
// m.Mux.Handle("PUT "+cleanPattern, handler)
|
|
// m.Mux.Handle("PATCH "+cleanPattern, handler)
|
|
// m.Mux.Handle("DELETE "+cleanPattern, handler)
|
|
// */
|
|
// }
|
|
|
|
func buildSubrouterHandlerFunc(logger *slog.Logger, numPatternSegments int, sr Subrouter) response.HandlerFunc {
|
|
defer logTraceAndDefer(logger, "", "buildSubrouterHandlerFunc", "", "numPatternSegments", numPatternSegments)(nil)
|
|
return func(r *http.Request) (res response.Response, err error) {
|
|
defer logTraceAndDefer(logger, "", "buildSubrouterHandlerFunc", "", "numPatternSegments", numPatternSegments, "r.URL", r.URL)(func() []any {
|
|
return []any{
|
|
"res", res,
|
|
"err", err,
|
|
}
|
|
})
|
|
|
|
fullPath := r.URL.Path
|
|
|
|
// set trailing path on request
|
|
|
|
segments, trailingSlash := getPathSegments(logger, r.URL.Path)
|
|
|
|
/*
|
|
endsInSlash := strings.HasSuffix(r.URL.Path, "/")
|
|
segments := strings.Split(strings.Trim(r.URL.Path, "/"), "/")
|
|
*/
|
|
tail := segments[numPatternSegments:]
|
|
tailStr := "/"
|
|
if len(tail) > 0 {
|
|
tailStr += strings.Join(tail, "/")
|
|
if trailingSlash {
|
|
tailStr += "/"
|
|
}
|
|
}
|
|
|
|
r.URL.Path = tailStr
|
|
defer func() {
|
|
// reset the request path
|
|
r.URL.Path = fullPath
|
|
}()
|
|
|
|
// do the request
|
|
|
|
logTrace(logger, "", "buildSubrouterHandlerFunc", "handler called", "subpath", r.URL.Path)
|
|
fn, params, ok := sr.Handler(r)
|
|
logTrace(logger, "", "buildSubrouterHandlerFunc", "handler returned", "subpath", r.URL.Path, "fn", fn, "params", params, "ok", ok)
|
|
if !ok {
|
|
return nil, response.NotFound().Wrap(ErrHandlerNotFound)
|
|
}
|
|
|
|
for k, v := range params {
|
|
r.SetPathValue(k, v)
|
|
}
|
|
|
|
res, err = fn(r)
|
|
|
|
return res, err
|
|
}
|
|
}
|
|
|
|
func (m *SubMux) Handler(r *http.Request) (fn response.HandlerFunc, params map[string]string, found bool) {
|
|
defer m.traced("Handler", "", "r.URL", r.URL)(func() []any {
|
|
return []any{
|
|
"params", params,
|
|
"found", found,
|
|
}
|
|
})
|
|
return m.tree.get(r.Method, r.URL.Path)
|
|
}
|
|
|
|
func (m *SubMux) applyMiddleware(fn response.HandlerFunc) response.HandlerFunc {
|
|
return applyMiddleware(fn, m.middleware...)
|
|
}
|
|
|
|
// path pattern matching tree implementation
|
|
|
|
type (
|
|
muxTree struct {
|
|
log *slog.Logger
|
|
|
|
branches map[string]*muxTree
|
|
wildcardKey string
|
|
wildcardBranch *muxTree
|
|
|
|
method string
|
|
handler response.HandlerFunc
|
|
subrouteHandler response.HandlerFunc
|
|
}
|
|
)
|
|
|
|
func newMuxTree(log *slog.Logger) *muxTree {
|
|
return &muxTree{
|
|
log: log,
|
|
branches: make(map[string]*muxTree),
|
|
}
|
|
}
|
|
|
|
func (m *muxTree) traced(method, msg string, args ...any) func(func() (finalArgs []any)) {
|
|
return logTraceAndDefer(m.log, "muxTree", method, msg, args...)
|
|
}
|
|
|
|
func (m *muxTree) String() string {
|
|
if m == nil {
|
|
return "<nil>"
|
|
}
|
|
|
|
kvs := make([]string, 0, 6)
|
|
|
|
if m.subrouteHandler != nil {
|
|
kvs = append(kvs, fmt.Sprintf("subrouteHandler: %v", m.subrouteHandler))
|
|
}
|
|
if m.method != "" {
|
|
kvs = append(kvs, fmt.Sprintf("method: %v", m.method))
|
|
}
|
|
if m.handler != nil {
|
|
kvs = append(kvs, fmt.Sprintf("handler: %v", m.handler))
|
|
}
|
|
if m.wildcardKey != "" {
|
|
kvs = append(kvs, fmt.Sprintf("wildcardKey: %v", m.wildcardKey))
|
|
}
|
|
if m.wildcardBranch != nil {
|
|
kvs = append(kvs, fmt.Sprintf("wildcardBranch: %v", m.wildcardBranch))
|
|
}
|
|
if len(m.branches) > 0 {
|
|
branchKvs := make([]string, 0, len(m.branches))
|
|
for k, v := range m.branches {
|
|
branchKvs = append(branchKvs, fmt.Sprintf("%q: %s", k, v))
|
|
}
|
|
|
|
slices.Sort(branchKvs)
|
|
|
|
kvs = append(kvs, fmt.Sprintf("branches: {%s}", strings.Join(branchKvs, ", ")))
|
|
}
|
|
|
|
return fmt.Sprintf("{%s}", strings.Join(kvs, ", "))
|
|
}
|
|
|
|
func (m *muxTree) set(pattern string, fn response.HandlerFunc) {
|
|
defer m.traced("set", "", "pattern", pattern)(func() []any {
|
|
return []any{
|
|
"muxTree", m,
|
|
}
|
|
})
|
|
method, segments, trailingSlash, endOfURLWildcard := getHTTPMethodAndPathSegmentsDroppingEndOfURLWildcard(m.log, pattern)
|
|
m.setBySegments(
|
|
method,
|
|
segments,
|
|
trailingSlash,
|
|
endOfURLWildcard,
|
|
fn,
|
|
)
|
|
}
|
|
|
|
// TODO: handle /{$} properly
|
|
func (m *muxTree) setBySegments(method string, segments []string, trailingSlash, endOfURLWildcard bool, fn response.HandlerFunc) {
|
|
defer m.traced(
|
|
"setBySegments",
|
|
"",
|
|
"method", method,
|
|
"segments", segments,
|
|
"trailingSlash", trailingSlash,
|
|
"endOfURLWildcard", endOfURLWildcard,
|
|
)(func() []any {
|
|
return []any{
|
|
"muxTree", m,
|
|
}
|
|
})
|
|
if len(segments) == 0 {
|
|
m.method = method
|
|
if !trailingSlash || endOfURLWildcard {
|
|
m.handler = fn
|
|
} else if trailingSlash {
|
|
m.subrouteHandler = fn
|
|
}
|
|
return
|
|
}
|
|
|
|
head := segments[0]
|
|
tail := segments[1:]
|
|
|
|
var sub *muxTree
|
|
if key, ok := getWildcardPathSegmentKey(head); ok {
|
|
m.wildcardKey = key
|
|
if sub = m.wildcardBranch; sub == nil {
|
|
sub = newMuxTree(m.log)
|
|
m.wildcardBranch = sub
|
|
}
|
|
} else {
|
|
if sub = m.branches[head]; sub == nil {
|
|
sub = newMuxTree(m.log)
|
|
m.branches[head] = sub
|
|
}
|
|
}
|
|
|
|
/*
|
|
if isWildcard, wildcardKey := isWildcardPathSegment(head); wildcardKey == "$" {
|
|
// TODO: test
|
|
// TODO: handle trailing end path matching
|
|
if len(tail) > 0 || trailingSlash {
|
|
panic("{$} wildcard applied outside of end of path")
|
|
}
|
|
m.method = method
|
|
m.handler = fn
|
|
return
|
|
} else if isWildcard {
|
|
m.wildcardKey = wildcardKey
|
|
if sub = m.wildcardBranch; sub == nil {
|
|
sub = newMuxTree(m.log)
|
|
m.wildcardBranch = sub
|
|
}
|
|
} else {
|
|
if sub = m.branches[head]; sub == nil {
|
|
sub = newMuxTree(m.log)
|
|
m.branches[head] = sub
|
|
}
|
|
}
|
|
*/
|
|
|
|
sub.setBySegments(method, tail, trailingSlash, endOfURLWildcard, fn)
|
|
}
|
|
|
|
func (m *muxTree) get(method, pattern string) (fn response.HandlerFunc, params map[string]string, found bool) {
|
|
defer m.traced("get", "", "method", method, "pattern", pattern)(func() []any {
|
|
return []any{
|
|
"fn", fn,
|
|
"params", params,
|
|
"found", found,
|
|
}
|
|
})
|
|
segments, trailingSlash := getPathSegments(m.log, pattern)
|
|
return m.getByHTTPMethodAndSegments(method, segments, trailingSlash)
|
|
}
|
|
|
|
func (m *muxTree) getByHTTPMethodAndSegments(method string, segments []string, trailingSlash bool) (fn response.HandlerFunc, params map[string]string, found bool) {
|
|
defer m.traced("getByHTTPMethodAndSegments", "", "method", method, "segments", segments, "trailingSlash", trailingSlash)(func() []any {
|
|
return []any{
|
|
"fn", fn,
|
|
"params", params,
|
|
"found", found,
|
|
}
|
|
})
|
|
if len(segments) == 0 {
|
|
logTrace(m.log, "muxTree", "getByHTTPMethodAndSegments", "no segments", "tree", m)
|
|
if methodMatches := m.method == "" || m.method == method; methodMatches {
|
|
if fn = m.handler; fn == nil {
|
|
fn = m.subrouteHandler
|
|
}
|
|
return fn, map[string]string{}, fn != nil
|
|
}
|
|
return nil, map[string]string{}, false
|
|
}
|
|
|
|
head := segments[0]
|
|
tail := segments[1:]
|
|
|
|
if m.subrouteHandler != nil {
|
|
defer func() {
|
|
if !found && (m.method == "" || m.method == method) {
|
|
fn = m.subrouteHandler
|
|
found = true
|
|
}
|
|
}()
|
|
}
|
|
|
|
if sm, ok := m.branches[head]; ok {
|
|
logTrace(m.log, "muxTree", "getByHTTPMethodAndSegments", "matching branch found", "head", head, "sm", sm)
|
|
//return sm.getByHTTPMethodAndSegments(method, tail, trailingSlash)
|
|
if fn, params, found = sm.getByHTTPMethodAndSegments(method, tail, trailingSlash); found {
|
|
return fn, params, found
|
|
}
|
|
logTrace(m.log, "muxTree", "getByHTTPMethodAndSegments", "matching branch mismatched at subpath")
|
|
} else {
|
|
logTrace(m.log, "muxTree", "getByHTTPMethodAndSegments", "no matching branch found")
|
|
}
|
|
if sm := m.wildcardBranch; sm != nil {
|
|
logTrace(m.log, "muxTree", "getByHTTPMethodAndSegments", "wildcard branch found", "m.wildcardKey", m.wildcardKey, "sm", sm)
|
|
//fn, params, found = sm.getByHTTPMethodAndSegments(method, tail, trailingSlash)
|
|
if fn, params, found = sm.getByHTTPMethodAndSegments(method, tail, trailingSlash); found {
|
|
params[m.wildcardKey] = head
|
|
return fn, params, found
|
|
}
|
|
logTrace(m.log, "muxTree", "getByHTTPMethodAndSegments", "wildcard branch mismatched at subpath")
|
|
} else {
|
|
logTrace(m.log, "muxTree", "getByHTTPMethodAndSegments", "no wildcard branch found")
|
|
}
|
|
|
|
// --- TODO: test ---
|
|
if m.subrouteHandler != nil && (m.method == method || m.method == "") {
|
|
logTrace(m.log, "muxTree", "getByHTTPMethodAndSegments", "subroutes captured", "m", m)
|
|
return m.subrouteHandler, nil, true
|
|
}
|
|
|
|
logTrace(m.log, "muxTree", "getByHTTPMethodAndSegments", "subroutes not captured", "m", m)
|
|
// ---
|
|
|
|
return nil, map[string]string{}, false
|
|
}
|
|
|
|
func getHTTPMethodAndPathSegmentsDroppingEndOfURLWildcard(logger *slog.Logger, pattern string) (method string, segments []string, trailingSlash, endOfURLWildcard bool) {
|
|
defer logTraceAndDefer(logger, "", "getHTTPMethodAndPathSegmentsDroppingEndOfURLWildcard", "", "pattern", pattern)(func() []any {
|
|
return []any{
|
|
"method", method,
|
|
"segments", segments,
|
|
"trailingSlash", trailingSlash,
|
|
"endOfURLWildcard", endOfURLWildcard,
|
|
}
|
|
})
|
|
if vs := strings.SplitN(pattern, " ", 2); len(vs) == 2 {
|
|
method = vs[0]
|
|
pattern = vs[1]
|
|
}
|
|
segments, trailingSlash, endOfURLWildcard = getPathSegmentsWithoutEndOfURLWildcard(logger, pattern)
|
|
|
|
return method, segments, trailingSlash, endOfURLWildcard
|
|
}
|
|
|
|
func getHTTPMethodAndPathSegments(logger *slog.Logger, pattern string) (method string, segments []string, trailingSlash bool) {
|
|
defer logTraceAndDefer(logger, "", "getHTTPMethodAndPathSegments", "", "pattern", pattern)(func() []any {
|
|
return []any{
|
|
"method", method,
|
|
"segments", segments,
|
|
"trailingSlash", trailingSlash,
|
|
}
|
|
})
|
|
if vs := strings.SplitN(pattern, " ", 2); len(vs) == 2 {
|
|
method = vs[0]
|
|
pattern = vs[1]
|
|
}
|
|
segments, trailingSlash = getPathSegments(logger, pattern)
|
|
|
|
return method, segments, trailingSlash
|
|
}
|
|
|
|
func getPathSegments(logger *slog.Logger, pathPattern string) (segments []string, trailingSlash bool) {
|
|
defer logTraceAndDefer(logger, "", "getPathSegments", "", "pathPattern", pathPattern)(func() []any {
|
|
return []any{
|
|
"segments", segments,
|
|
"trailingSlash", trailingSlash,
|
|
}
|
|
})
|
|
trailingSlash = strings.HasSuffix(pathPattern, "/")
|
|
|
|
p := strings.Trim(path.Clean(pathPattern), "/")
|
|
if p == "" {
|
|
return nil, trailingSlash
|
|
}
|
|
|
|
return strings.Split(p, "/"), trailingSlash
|
|
}
|
|
|
|
// NOTE: if endOfURLWildcard, then trailingSlash
|
|
func getPathSegmentsWithoutEndOfURLWildcard(logger *slog.Logger, pathPattern string) (segments []string, trailingSlash, endOfURLWildcard bool) {
|
|
defer logTraceAndDefer(logger, "", "getPathSegmentsWithoutEndOfURLWildcard", "", "pathPattern", pathPattern)(func() []any {
|
|
return []any{
|
|
"segments", segments,
|
|
"trailingSlash", trailingSlash,
|
|
"endOfURLWildcard", endOfURLWildcard,
|
|
}
|
|
})
|
|
|
|
endOfURLWildcard = strings.HasSuffix(pathPattern, "/{$}")
|
|
if endOfURLWildcard {
|
|
pathPattern = pathPattern[:len(pathPattern)-3]
|
|
}
|
|
|
|
trailingSlash = strings.HasSuffix(pathPattern, "/")
|
|
|
|
p := strings.Trim(path.Clean(pathPattern), "/")
|
|
if p == "" {
|
|
return nil, trailingSlash, endOfURLWildcard
|
|
}
|
|
|
|
return strings.Split(p, "/"), trailingSlash, endOfURLWildcard
|
|
}
|
|
|
|
func getWildcardPathSegmentKey(s string) (string, bool) {
|
|
if isWildcard := len(s) > 2 && s[0] == '{' && s[len(s)-1] == '}'; isWildcard {
|
|
return s[1 : len(s)-1], true
|
|
}
|
|
return "", false
|
|
}
|
|
|
|
func applyMiddleware(fn response.HandlerFunc, ms ...response.Middleware) response.HandlerFunc {
|
|
for _, mw := range slices.Backward(ms) {
|
|
prev := fn
|
|
fn = mw(func(r *http.Request) (response.Response, error) {
|
|
return prev(r)
|
|
})
|
|
}
|
|
|
|
return fn
|
|
}
|
|
|
|
// logging utilities
|
|
|
|
func logTraceAndDefer(logger *slog.Logger, typeName, method, msg string, args ...any) func(func() (finalArgs []any)) {
|
|
fmsg := fmt.Sprintf("%s.%s called", typeName, method)
|
|
if msg != "" {
|
|
fmsg = fmt.Sprintf("%s: %s", fmsg, msg)
|
|
}
|
|
|
|
logger.Log(nil, consts.LevelTrace, fmsg, args...)
|
|
return func(cb func() (finalArgs []any)) {
|
|
if cb != nil {
|
|
args = append(args, cb()...)
|
|
}
|
|
fmsg := fmt.Sprintf("%s.%s returned", typeName, method)
|
|
if msg != "" {
|
|
fmsg = fmt.Sprintf("%s: %s", fmsg, msg)
|
|
}
|
|
|
|
logger.Log(nil, consts.LevelTrace, fmsg, args...)
|
|
}
|
|
}
|
|
|
|
func logTrace(logger *slog.Logger, typeName, method, msg string, args ...any) {
|
|
fmsg := fmt.Sprintf("%s.%s", typeName, method)
|
|
if msg != "" {
|
|
fmsg = fmt.Sprintf("%s: %s", fmsg, msg)
|
|
}
|
|
|
|
logger.Log(nil, consts.LevelTrace, fmsg, args...)
|
|
}
|