package jmux import ( "context" "strings" "gfx.cafe/open/jrpc" ) // MethodParam returns the url parameter from a Request object. func MethodParam(r *jrpc.Request, key string) string { if rctx := RouteContext(r.Context()); rctx != nil { return rctx.MethodParam(key) } return "" } // MethodParamFromCtx returns the url parameter from a Request Context. 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 // Request Context. 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. // See Mux#routemethod. 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, // // 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) // }) // } func (x *Context) RoutePattern() string { routePattern := strings.Join(x.RoutePatterns, "") routePattern = replaceWildcards(routePattern) routePattern = strings.TrimSuffix(routePattern, "__") routePattern = strings.TrimSuffix(routePattern, "_") return routePattern } // replaceWildcards takes a route pattern and recursively replaces all // occurrences of "_*_" to "_". func replaceWildcards(p string) string { if strings.Contains(p, "_*_") { return replaceWildcards(strings.Replace(p, "_*_", "_", -1)) } 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 // for defining context keys was copied from Go 1.7's new use of context in net/ type contextKey struct { name string } func (k *contextKey) String() string { return "jsonrpc2 context value " + k.name }