378 lines
9.0 KiB
Go
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
|
|
}
|