good morning!!!!

Skip to content
Snippets Groups Projects
router_context.go 4.68 KiB
Newer Older
a's avatar
a committed
package jmux
a's avatar
rpc
a committed

import (
	"context"
	"strings"
a's avatar
a committed

a's avatar
a committed
	"gfx.cafe/open/jrpc/pkg/jsonrpc"
a's avatar
rpc
a committed
)

a's avatar
a  
a committed
// MethodParam returns the url parameter from a Request object.
a's avatar
a committed
func MethodParam(r *jsonrpc.Request, key string) string {
a's avatar
rpc
a committed
	if rctx := RouteContext(r.Context()); rctx != nil {
		return rctx.MethodParam(key)
	}
	return ""
}

a's avatar
a  
a committed
// MethodParamFromCtx returns the url parameter from a Request Context.
a's avatar
rpc
a committed
func MethodParamFromCtx(ctx context.Context, key string) string {
	if rctx := RouteContext(ctx); rctx != nil {
		return rctx.MethodParam(key)
	}
	return ""
}

// RouteContext returns chi's routing Context object from a
a's avatar
a  
a committed
// Request Context.
a's avatar
rpc
a committed
func RouteContext(ctx context.Context) *Context {
	val, _ := ctx.Value(RouteCtxKey).(*Context)
	return val
}

// NewRouteContext returns a new routing Context object.
func NewRouteContext() *Context {
	return &Context{}
}

var (
	// RouteCtxKey is the context.Context key to store the request context.
	RouteCtxKey = &contextKey{"RouteContext"}
)

// Context is the default routing context set on the root node of a
// request context to track route patterns, Method parameters and
// an optional routing path.
type Context struct {
	Routes Routes

	// parentCtx is the parent of this one, for using Context as a
	// context.Context directly. This is an optimization that saves
	// 1 allocation.
	parentCtx context.Context

	// Routing path/method override used during the route search.
a's avatar
a  
a committed
	// See Mux#routemethod.
a's avatar
rpc
a committed
	RoutePath   string
	RouteMethod string

	// MethodParams are the stack of routeParams captured during the
	// routing lifecycle across a stack of sub-routers.
	MethodParams RouteParams

	// Route parameters matched for the current sub-router. It is
	// intentionally unexported so it cant be tampered.
	routeParams RouteParams

	// The endpoint routing pattern that matched the request URI path
	// or `RoutePath` of the current sub-router. This value will update
	// during the lifecycle of a request passing through a stack of
	// sub-routers.
	routePattern string

	// Routing pattern stack throughout the lifecycle of the request,
	// across all connected routers. It is a record of all matching
	// patterns across a stack of sub-routers.
	RoutePatterns []string

	// methodNotAllowed hint
	methodNotAllowed bool
}

// Reset a routing context to its initial state.
func (x *Context) Reset() {
	x.Routes = nil
	x.RoutePath = ""
	x.RouteMethod = ""
	x.RoutePatterns = x.RoutePatterns[:0]
	x.MethodParams.Keys = x.MethodParams.Keys[:0]
	x.MethodParams.Values = x.MethodParams.Values[:0]

	x.routePattern = ""
	x.routeParams.Keys = x.routeParams.Keys[:0]
	x.routeParams.Values = x.routeParams.Values[:0]
	x.methodNotAllowed = false
	x.parentCtx = nil
}

// MethodParam returns the corresponding Method parameter value from the request
// routing context.
func (x *Context) MethodParam(key string) string {
	for k := len(x.MethodParams.Keys) - 1; k >= 0; k-- {
		if x.MethodParams.Keys[k] == key {
			return x.MethodParams.Values[k]
		}
	}
	return ""
}

// RoutePattern builds the routing pattern string for the particular
// request, at the particular point during routing. This means, the value
// will change throughout the execution of a request in a router. That is
// why its advised to only use this value after calling the next handler.
//
// For example,
//
a's avatar
a  
a committed
//	func Instrument(next Handler) Handler {
//	  return HandlerFunc(func(w ResponseWriter, r *Request) {
//	    next.Servew, r)
//	    routePattern := chi.RouteContext(r.Context()).RoutePattern()
//	    measure(w, r, routePattern)
//		 })
//	}
a's avatar
rpc
a committed
func (x *Context) RoutePattern() string {
	routePattern := strings.Join(x.RoutePatterns, "")
	routePattern = replaceWildcards(routePattern)
a's avatar
a committed
	routePattern = strings.TrimSuffix(routePattern, sepString+sepString)
a's avatar
a committed
	routePattern = strings.TrimSuffix(routePattern, sepString)
a's avatar
rpc
a committed
	return routePattern
}

// replaceWildcards takes a route pattern and recursively replaces all
a's avatar
a committed
// occurrences of "/*/" to "/".
a's avatar
rpc
a committed
func replaceWildcards(p string) string {
a's avatar
a committed
	if strings.Contains(p, sepString+"*"+sepString) {
		return replaceWildcards(strings.Replace(p, sepString+"*"+sepString, sepString, -1))
a's avatar
rpc
a committed
	}
	return p
}

// RouteParams is a structure to track Method routing parameters efficiently.
type RouteParams struct {
	Keys, Values []string
}

// Add will append a Method parameter to the end of the route param
func (s *RouteParams) Add(key, value string) {
	s.Keys = append(s.Keys, key)
	s.Values = append(s.Values, value)
}

// contextKey is a value for use with context.WithValue. It's used as
// a pointer so it fits in an interface{} without allocation. This technique
a's avatar
a  
a committed
// for defining context keys was copied from Go 1.7's new use of context in net/
a's avatar
rpc
a committed
type contextKey struct {
	name string
}

func (k *contextKey) String() string {
a's avatar
a committed
	return "jrpc context value " + k.name
a's avatar
rpc
a committed
}