Skip to content
Permalink

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also or learn more about diff comparisons.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also . Learn more about diff comparisons here.
base repository: go-chi/chi
Failed to load repositories. Confirm that selected base ref is valid, then try again.
Loading
base: v5.0.11
Choose a base ref
...
head repository: go-chi/chi
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: v5.0.12
Choose a head ref
  • 8 commits
  • 9 files changed
  • 6 contributors

Commits on Jan 10, 2024

  1. Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature.
    Copy the full SHA
    f6b97ed View commit details

Commits on Jan 18, 2024

  1. Copy the full SHA
    7446950 View commit details

Commits on Jan 23, 2024

  1. Copy the full SHA
    bec368a View commit details

Commits on Feb 11, 2024

  1. go 1.22 ci (#898)

    pkieltyka authored Feb 11, 2024
    Copy the full SHA
    9436cc8 View commit details

Commits on Feb 17, 2024

  1. feat: update HTTP method parsing in patterns for Handle and `Handle…

    …Func` (#900)
    angelofallars authored Feb 17, 2024
    Copy the full SHA
    60b4f5f View commit details
  2. feat(mux): add 1.22-style path value support (#901)

    angelofallars authored Feb 17, 2024
    Copy the full SHA
    fd0ff0e View commit details
  3. go 1.22, PathValue wildcard test

    pkieltyka committed Feb 17, 2024
    Copy the full SHA
    ec67a86 View commit details
  4. v5.0.12

    pkieltyka committed Feb 17, 2024
    Copy the full SHA
    1191921 View commit details
Showing with 224 additions and 3 deletions.
  1. +1 −2 .github/workflows/ci.yml
  2. +5 −0 CHANGELOG.md
  3. +2 −0 README.md
  4. +1 −1 middleware/maybe.go
  5. +16 −0 mux.go
  6. +83 −0 mux_test.go
  7. +20 −0 path_value.go
  8. +19 −0 path_value_fallback.go
  9. +77 −0 path_value_test.go
3 changes: 1 addition & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -13,15 +13,14 @@ jobs:
test:
env:
GOPATH: ${{ github.workspace }}
GO111MODULE: off

defaults:
run:
working-directory: ${{ env.GOPATH }}/src/github.com/${{ github.repository }}

strategy:
matrix:
go-version: [1.15.x, 1.16.x, 1.17.x, 1.18.x, 1.19.x, 1.20.x, 1.21.x]
go-version: [1.15.x, 1.16.x, 1.17.x, 1.18.x, 1.19.x, 1.20.x, 1.21.x, 1.22.x]
os: [ubuntu-latest, windows-latest]

runs-on: ${{ matrix.os }}
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
# Changelog

## v5.0.12 (2024-02-16)

- History of changes: see https://github.com/go-chi/chi/compare/v5.0.11...v5.0.12


## v5.0.11 (2023-12-19)

- History of changes: see https://github.com/go-chi/chi/compare/v5.0.10...v5.0.11
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -354,6 +354,7 @@ with `net/http` can be used with chi's mux.
| [RouteHeaders] | Route handling for request headers |
| [SetHeader] | Short-hand middleware to set a response header key/value |
| [StripSlashes] | Strip slashes on routing paths |
| [Sunset] | Sunset set Deprecation/Sunset header to response |
| [Throttle] | Puts a ceiling on the number of concurrent requests |
| [Timeout] | Signals to the request context when the timeout deadline is reached |
| [URLFormat] | Parse extension from url and put it on request context |
@@ -380,6 +381,7 @@ with `net/http` can be used with chi's mux.
[RouteHeaders]: https://pkg.go.dev/github.com/go-chi/chi/middleware#RouteHeaders
[SetHeader]: https://pkg.go.dev/github.com/go-chi/chi/middleware#SetHeader
[StripSlashes]: https://pkg.go.dev/github.com/go-chi/chi/middleware#StripSlashes
[Sunset]: https://pkg.go.dev/github.com/go-chi/chi/v5/middleware#Sunset
[Throttle]: https://pkg.go.dev/github.com/go-chi/chi/middleware#Throttle
[ThrottleBacklog]: https://pkg.go.dev/github.com/go-chi/chi/middleware#ThrottleBacklog
[ThrottleWithOpts]: https://pkg.go.dev/github.com/go-chi/chi/middleware#ThrottleWithOpts
2 changes: 1 addition & 1 deletion middleware/maybe.go
Original file line number Diff line number Diff line change
@@ -4,7 +4,7 @@ import "net/http"

// Maybe middleware will allow you to change the flow of the middleware stack execution depending on return
// value of maybeFn(request). This is useful for example if you'd like to skip a middleware handler if
// a request does not satisfied the maybeFn logic.
// a request does not satisfy the maybeFn logic.
func Maybe(mw func(http.Handler) http.Handler, maybeFn func(r *http.Request) bool) func(http.Handler) http.Handler {
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
16 changes: 16 additions & 0 deletions mux.go
Original file line number Diff line number Diff line change
@@ -107,12 +107,24 @@ func (mx *Mux) Use(middlewares ...func(http.Handler) http.Handler) {
// Handle adds the route `pattern` that matches any http method to
// execute the `handler` http.Handler.
func (mx *Mux) Handle(pattern string, handler http.Handler) {
parts := strings.SplitN(pattern, " ", 2)
if len(parts) == 2 {
mx.Method(parts[0], parts[1], handler)
return
}

mx.handle(mALL, pattern, handler)
}

// HandleFunc adds the route `pattern` that matches any http method to
// execute the `handlerFn` http.HandlerFunc.
func (mx *Mux) HandleFunc(pattern string, handlerFn http.HandlerFunc) {
parts := strings.SplitN(pattern, " ", 2)
if len(parts) == 2 {
mx.Method(parts[0], parts[1], handlerFn)
return
}

mx.handle(mALL, pattern, handlerFn)
}

@@ -440,6 +452,10 @@ func (mx *Mux) routeHTTP(w http.ResponseWriter, r *http.Request) {

// Find the route
if _, _, h := mx.tree.FindRoute(rctx, method, routePath); h != nil {
if supportsPathValue {
setPathValue(rctx, r)
}

h.ServeHTTP(w, r)
return
}
83 changes: 83 additions & 0 deletions mux_test.go
Original file line number Diff line number Diff line change
@@ -640,6 +640,89 @@ func TestMuxWith(t *testing.T) {
}
}

func TestMuxHandlePatternValidation(t *testing.T) {
testCases := []struct {
name string
pattern string
shouldPanic bool
method string // Method to be used for the test request
path string // Path to be used for the test request
expectedBody string // Expected response body
expectedStatus int // Expected HTTP status code
}{
// Valid patterns
{
name: "Valid pattern without HTTP GET",
pattern: "/user/{id}",
shouldPanic: false,
method: "GET",
path: "/user/123",
expectedBody: "without-prefix GET",
expectedStatus: http.StatusOK,
},
{
name: "Valid pattern with HTTP POST",
pattern: "POST /products/{id}",
shouldPanic: false,
method: "POST",
path: "/products/456",
expectedBody: "with-prefix POST",
expectedStatus: http.StatusOK,
},
// Invalid patterns
{
name: "Invalid pattern with no method",
pattern: "INVALID/user/{id}",
shouldPanic: true,
},
{
name: "Invalid pattern with supported method",
pattern: "GET/user/{id}",
shouldPanic: true,
},
{
name: "Invalid pattern with unsupported method",
pattern: "UNSUPPORTED /unsupported-method",
shouldPanic: true,
},
}

for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
defer func() {
if r := recover(); r != nil && !tc.shouldPanic {
t.Errorf("Unexpected panic for pattern %s:\n%v", tc.pattern, r)
}
}()

r1 := NewRouter()
r1.Handle(tc.pattern, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte(tc.expectedBody))
}))

// Test that HandleFunc also handles method patterns
r2 := NewRouter()
r2.HandleFunc(tc.pattern, func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte(tc.expectedBody))
})

if !tc.shouldPanic {
for _, r := range []Router{r1, r2} {
// Use testRequest for valid patterns
ts := httptest.NewServer(r)
defer ts.Close()

resp, body := testRequest(t, ts, tc.method, tc.path, nil)
if body != tc.expectedBody || resp.StatusCode != tc.expectedStatus {
t.Errorf("Expected status %d and body %s; got status %d and body %s for pattern %s",
tc.expectedStatus, tc.expectedBody, resp.StatusCode, body, tc.pattern)
}
}
}
})
}
}

func TestRouterFromMuxWith(t *testing.T) {
t.Parallel()

20 changes: 20 additions & 0 deletions path_value.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
//go:build go1.22
// +build go1.22

package chi

import "net/http"

// supportsPathValue is true if the Go version is 1.22 and above.
//
// If this is true, `net/http.Request` has methods `SetPathValue` and `PathValue`.
const supportsPathValue = true

// setPathValue sets the path values in the Request value
// based on the provided request context.
func setPathValue(rctx *Context, r *http.Request) {
for i, key := range rctx.URLParams.Keys {
value := rctx.URLParams.Values[i]
r.SetPathValue(key, value)
}
}
19 changes: 19 additions & 0 deletions path_value_fallback.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
//go:build !go1.22
// +build !go1.22

package chi

import "net/http"

// supportsPathValue is true if the Go version is 1.22 and above.
//
// If this is true, `net/http.Request` has methods `SetPathValue` and `PathValue`.
const supportsPathValue = false

// setPathValue sets the path values in the Request value
// based on the provided request context.
//
// setPathValue is only supported in Go 1.22 and above so
// this is just a blank function so that it compiles.
func setPathValue(rctx *Context, r *http.Request) {
}
77 changes: 77 additions & 0 deletions path_value_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
//go:build go1.22
// +build go1.22

package chi

import (
"net/http"
"net/http/httptest"
"strings"
"testing"
)

func TestPathValue(t *testing.T) {
testCases := []struct {
name string
pattern string
method string
pathKeys []string
requestPath string
expectedBody string
}{
{
name: "Basic path value",
pattern: "/hubs/{hubID}",
method: "GET",
pathKeys: []string{"hubID"},
requestPath: "/hubs/392",
expectedBody: "392",
},
{
name: "Two path values",
pattern: "/users/{userID}/conversations/{conversationID}",
method: "POST",
pathKeys: []string{"userID", "conversationID"},
requestPath: "/users/Gojo/conversations/2948",
expectedBody: "Gojo 2948",
},
{
name: "Wildcard path",
pattern: "/users/{userID}/friends/*",
method: "POST",
pathKeys: []string{"userID", "*"},
requestPath: "/users/Gojo/friends/all-of-them/and/more",
expectedBody: "Gojo all-of-them/and/more",
},
}

for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
r := NewRouter()

r.Handle(tc.method+" "+tc.pattern, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
pathValues := []string{}
for _, pathKey := range tc.pathKeys {
pathValue := r.PathValue(pathKey)
if pathValue == "" {
pathValue = "NOT_FOUND:" + pathKey
}

pathValues = append(pathValues, pathValue)
}

body := strings.Join(pathValues, " ")

w.Write([]byte(body))
}))

ts := httptest.NewServer(r)
defer ts.Close()

_, body := testRequest(t, ts, tc.method, tc.requestPath, nil)
if body != tc.expectedBody {
t.Fatalf("expecting %q, got %q", tc.expectedBody, body)
}
})
}
}