Files
amcs/vendor/github.com/uptrace/bunrouter/request.go
T
Hein 1adf50e3db
CI / build-and-test (push) Failing after 1s
Release / release (push) Failing after 19m26s
fix(go.sum): update ResolveSpec dependency to v1.0.87
2026-06-23 13:17:16 +02:00

378 lines
9.0 KiB
Go

package bunrouter
import (
"context"
"encoding/json"
"fmt"
"net/http"
"strconv"
"strings"
)
type routeCtxKey struct{}
// ParamsFromContext retrieves route parameters from the given context.
// It returns an empty Params if no parameters are found.
func ParamsFromContext(ctx context.Context) Params {
if ctx == nil {
return Params{}
}
route, _ := ctx.Value(routeCtxKey{}).(Params)
return route
}
// contextWithParams stores route parameters in the context.
func contextWithParams(ctx context.Context, params Params) context.Context {
if ctx == nil {
ctx = context.Background()
}
return context.WithValue(ctx, routeCtxKey{}, params)
}
//------------------------------------------------------------------------------
// HTTPHandler converts http.Handler to bunrouter.HandlerFunc.
func HTTPHandler(handler http.Handler) HandlerFunc {
if handler == nil {
panic("bunrouter: nil handler")
}
return HTTPHandlerFunc(handler.ServeHTTP)
}
// HTTPHandlerFunc converts http.HandlerFunc to bunrouter.HandlerFunc.
func HTTPHandlerFunc(handler http.HandlerFunc) HandlerFunc {
if handler == nil {
panic("bunrouter: nil handler")
}
return func(w http.ResponseWriter, req Request) (err error) {
if w == nil {
return fmt.Errorf("bunrouter: nil response writer")
}
ctx := contextWithParams(req.Context(), req.params)
defer func() {
if v := recover(); v != nil {
var ok bool
err, ok = v.(error)
if !ok {
err = fmt.Errorf("bunrouter: panic recovered: %v", v)
}
}
}()
handler.ServeHTTP(w, req.Request.WithContext(ctx))
return err
}
}
// HandlerFunc is a function that handles HTTP requests in bunrouter.
// It returns an error that will be handled by the router.
type HandlerFunc func(w http.ResponseWriter, req Request) error
var _ http.Handler = (*HandlerFunc)(nil)
func (h HandlerFunc) ServeHTTP(w http.ResponseWriter, req *http.Request) {
if h == nil {
http.Error(w, "Handler not found", http.StatusInternalServerError)
return
}
if req == nil {
http.Error(w, "Invalid request", http.StatusBadRequest)
return
}
if err := h(w, NewRequest(req)); err != nil {
code := http.StatusInternalServerError
if httpErr, ok := err.(HTTPError); ok {
code = httpErr.StatusCode()
}
http.Error(w, err.Error(), code)
}
}
// HTTPError represents an HTTP error with a status code
type HTTPError interface {
error
StatusCode() int
}
// MiddlewareFunc is a function that wraps a HandlerFunc to provide middleware functionality.
type MiddlewareFunc func(next HandlerFunc) HandlerFunc
//------------------------------------------------------------------------------
// Request extends http.Request with route parameters.
type Request struct {
*http.Request
params Params
}
// NewRequest creates a new Request instance from an http.Request.
func NewRequest(req *http.Request) Request {
if req == nil {
req = &http.Request{}
}
return Request{
Request: req,
params: ParamsFromContext(req.Context()),
}
}
func newRequestParams(req *http.Request, params Params) Request {
if req == nil {
req = &http.Request{}
}
return Request{
Request: req,
params: params,
}
}
// WithContext returns a new Request with the provided context.
func (req Request) WithContext(ctx context.Context) Request {
if ctx == nil {
ctx = context.Background()
}
return Request{
Request: req.Request.WithContext(ctx),
params: req.params,
}
}
// Params returns the route parameters associated with the request.
func (req Request) Params() Params {
return req.params
}
// Param returns the value of the named parameter or empty string if not found.
func (req Request) Param(key string) string {
return req.Params().ByName(key)
}
// Route returns the matched route pattern.
func (req Request) Route() string {
return req.Params().Route()
}
//------------------------------------------------------------------------------
// Params holds route parameters and route information.
type Params struct {
path string
node *node
handler *routeHandler
wildcardLen uint16
}
// IsZero returns true if Params has no associated route node.
func (ps Params) IsZero() bool {
return ps.node == nil
}
// Route returns the route pattern that matched the request.
func (ps Params) Route() string {
if ps.node != nil {
return ps.node.route
}
return ""
}
// Get returns the value of the named parameter and whether it was found.
func (ps Params) Get(name string) (string, bool) {
if ps.node == nil || ps.handler == nil {
return "", false
}
if i, ok := ps.handler.params[name]; ok {
return ps.findParam(i)
}
return "", false
}
func (ps *Params) findParam(paramIndex int) (string, bool) {
if ps.node == nil || ps.handler == nil {
return "", false
}
path := ps.path
pathLen := len(path)
if pathLen == 0 {
return "", false
}
currNode := ps.node
currParamIndex := len(ps.handler.params) - 1
if paramIndex < 0 || paramIndex > currParamIndex {
return "", false
}
// Wildcard can be only in the final node.
if ps.node.isWC {
if currParamIndex == paramIndex {
if int(ps.wildcardLen) > pathLen {
return "", false
}
pathLen -= int(ps.wildcardLen)
return path[pathLen:], true
}
currParamIndex--
if int(ps.wildcardLen) > pathLen {
return "", false
}
pathLen -= int(ps.wildcardLen)
path = path[:pathLen]
}
for currNode != nil {
if currNode.part[0] != ':' { // static node
partLen := len(currNode.part)
if partLen > pathLen {
return "", false
}
pathLen -= partLen
path = path[:pathLen]
currNode = currNode.parent
continue
}
i := strings.LastIndexByte(path, '/')
if i == -1 {
return "", false
}
pathLen = i + 1
if currParamIndex == paramIndex {
return path[pathLen:], true
}
currParamIndex--
path = path[:pathLen]
currNode = currNode.parent
}
return "", false
}
// ByName returns the value of the named parameter or empty string if not found.
func (ps Params) ByName(name string) string {
s, _ := ps.Get(name)
return s
}
// Int parses the named parameter as an integer.
func (ps Params) Int(name string) (int, error) {
value := ps.ByName(name)
if value == "" {
return 0, fmt.Errorf("bunrouter: param '%s' not found", name)
}
return strconv.Atoi(value)
}
// Uint32 parses the named parameter as an unsigned 32-bit integer.
func (ps Params) Uint32(name string) (uint32, error) {
value := ps.ByName(name)
if value == "" {
return 0, fmt.Errorf("bunrouter: param '%s' not found", name)
}
n, err := strconv.ParseUint(value, 10, 32)
return uint32(n), err
}
// Uint64 parses the named parameter as an unsigned 64-bit integer.
func (ps Params) Uint64(name string) (uint64, error) {
value := ps.ByName(name)
if value == "" {
return 0, fmt.Errorf("bunrouter: param '%s' not found", name)
}
return strconv.ParseUint(value, 10, 64)
}
// Int32 parses the named parameter as a signed 32-bit integer.
func (ps Params) Int32(name string) (int32, error) {
value := ps.ByName(name)
if value == "" {
return 0, fmt.Errorf("bunrouter: param '%s' not found", name)
}
n, err := strconv.ParseInt(value, 10, 32)
return int32(n), err
}
// Int64 parses the named parameter as a signed 64-bit integer.
func (ps Params) Int64(name string) (int64, error) {
value := ps.ByName(name)
if value == "" {
return 0, fmt.Errorf("bunrouter: param '%s' not found", name)
}
return strconv.ParseInt(value, 10, 64)
}
// Map returns route parameters as a map[string]string.
func (ps Params) Map() map[string]string {
if ps.handler == nil || len(ps.handler.params) == 0 {
return make(map[string]string)
}
m := make(map[string]string, len(ps.handler.params))
for param, index := range ps.handler.params {
if value, ok := ps.findParam(index); ok {
m[param] = value
}
}
return m
}
// Param represents a key-value pair of route parameters.
type Param struct {
Key string
Value string
}
// Slice returns route parameters as a slice of Param.
func (ps Params) Slice() []Param {
if ps.handler == nil || len(ps.handler.params) == 0 {
return []Param{}
}
slice := make([]Param, len(ps.handler.params))
for param, index := range ps.handler.params {
if value, ok := ps.findParam(index); ok {
slice[index] = Param{Key: param, Value: value}
}
}
return slice
}
//------------------------------------------------------------------------------
// H is a shorthand for map[string]interface{}.
type H map[string]interface{}
// JSON marshals the value as JSON and writes it to the response writer.
// It sets the Content-Type header to application/json.
//
// Don't hesitate to copy-paste this function to your project and customize it as necessary.
func JSON(w http.ResponseWriter, value interface{}) error {
if w == nil {
return fmt.Errorf("bunrouter: nil response writer")
}
w.Header().Set("Content-Type", "application/json")
if value == nil {
if _, err := w.Write([]byte("null")); err != nil {
return fmt.Errorf("bunrouter: failed to write null response: %w", err)
}
return nil
}
enc := json.NewEncoder(w)
if err := enc.Encode(value); err != nil {
return fmt.Errorf("bunrouter: JSON encoding error: %w", err)
}
return nil
}