mirror of
https://github.com/gin-gonic/gin.git
synced 2026-09-04 14:49:27 +08:00
Compare commits
6 Commits
63496bc13f
...
e3ee295864
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e3ee295864 | ||
|
|
dcaa4296d1 | ||
|
|
8dd20118ba | ||
|
|
00cfe5aac2 | ||
|
|
6d97ab9c02 | ||
|
|
8391a9f46c |
2
.github/workflows/codeql.yml
vendored
2
.github/workflows/codeql.yml
vendored
@ -33,7 +33,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v6
|
||||
uses: actions/checkout@v7
|
||||
|
||||
# Initializes the CodeQL tools for scanning.
|
||||
- name: Initialize CodeQL
|
||||
|
||||
12
.github/workflows/gin.yml
vendored
12
.github/workflows/gin.yml
vendored
@ -16,11 +16,11 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v6
|
||||
uses: actions/checkout@v7
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v6
|
||||
uses: actions/setup-go@v7
|
||||
with:
|
||||
go-version: "^1"
|
||||
- name: Setup golangci-lint
|
||||
@ -55,17 +55,17 @@ jobs:
|
||||
GOPROXY: https://proxy.golang.org
|
||||
steps:
|
||||
- name: Set up Go ${{ matrix.go }}
|
||||
uses: actions/setup-go@v6
|
||||
uses: actions/setup-go@v7
|
||||
with:
|
||||
go-version: ${{ matrix.go }}
|
||||
cache: false
|
||||
|
||||
- name: Checkout Code
|
||||
uses: actions/checkout@v6
|
||||
uses: actions/checkout@v7
|
||||
with:
|
||||
ref: ${{ github.ref }}
|
||||
|
||||
- uses: actions/cache@v5
|
||||
- uses: actions/cache@v6
|
||||
with:
|
||||
path: |
|
||||
${{ matrix.go-build }}
|
||||
@ -78,6 +78,6 @@ jobs:
|
||||
run: make test
|
||||
|
||||
- name: Upload coverage to Codecov
|
||||
uses: codecov/codecov-action@v6
|
||||
uses: codecov/codecov-action@v7
|
||||
with:
|
||||
flags: ${{ matrix.os }},go-${{ matrix.go }},${{ matrix.test-tags }}
|
||||
|
||||
4
.github/workflows/goreleaser.yml
vendored
4
.github/workflows/goreleaser.yml
vendored
@ -13,11 +13,11 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v6
|
||||
uses: actions/checkout@v7
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v6
|
||||
uses: actions/setup-go@v7
|
||||
with:
|
||||
go-version: "^1"
|
||||
- name: Run GoReleaser
|
||||
|
||||
2
.github/workflows/trivy-scan.yml
vendored
2
.github/workflows/trivy-scan.yml
vendored
@ -22,7 +22,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v6
|
||||
uses: actions/checkout@v7
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
|
||||
@ -121,6 +121,14 @@ func Benchmark404Many(B *testing.B) {
|
||||
runRequest(B, router, http.MethodGet, "/viewfake")
|
||||
}
|
||||
|
||||
func BenchmarkStaticRouteWithParamFallback(B *testing.B) {
|
||||
router := New()
|
||||
router.GET("/users/:id", func(c *Context) {})
|
||||
router.GET("/users/new", func(c *Context) {})
|
||||
router.GET("/users/:id/profile", func(c *Context) {})
|
||||
runRequest(B, router, http.MethodGet, "/users/new")
|
||||
}
|
||||
|
||||
type mockWriter struct {
|
||||
headers http.Header
|
||||
}
|
||||
|
||||
@ -1403,6 +1403,26 @@ func TestPlainBinding(t *testing.T) {
|
||||
require.NoError(t, p.Bind(req, ptr))
|
||||
}
|
||||
|
||||
func TestPlainBindingBindBody(t *testing.T) {
|
||||
p := Plain
|
||||
|
||||
var s string
|
||||
require.NoError(t, p.BindBody([]byte("test string"), &s))
|
||||
assert.Equal(t, "test string", s)
|
||||
|
||||
var bs []byte
|
||||
require.NoError(t, p.BindBody([]byte("test []byte"), &bs))
|
||||
assert.Equal(t, []byte("test []byte"), bs)
|
||||
|
||||
var i int
|
||||
require.Error(t, p.BindBody([]byte("test fail"), &i))
|
||||
|
||||
require.NoError(t, p.BindBody(nil, nil))
|
||||
|
||||
var ptr *string
|
||||
require.NoError(t, p.BindBody(nil, ptr))
|
||||
}
|
||||
|
||||
func testProtoBodyBindingFail(t *testing.T, b Binding, name, path, badPath, body, badBody string) {
|
||||
assert.Equal(t, name, b.Name())
|
||||
|
||||
|
||||
79
codec/json/json_test.go
Normal file
79
codec/json/json_test.go
Normal file
@ -0,0 +1,79 @@
|
||||
// Copyright 2026 Gin Core Team. All rights reserved.
|
||||
// Use of this source code is governed by a MIT style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package json
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestAPI(t *testing.T) {
|
||||
if API == nil {
|
||||
t.Fatal("API is nil")
|
||||
}
|
||||
if Package == "" {
|
||||
t.Fatal("Package is empty")
|
||||
}
|
||||
}
|
||||
|
||||
func TestAPIMarshalAndUnmarshal(t *testing.T) {
|
||||
type payload struct {
|
||||
Name string `json:"name"`
|
||||
}
|
||||
|
||||
data, err := API.Marshal(payload{Name: "gin"})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if string(data) != `{"name":"gin"}` {
|
||||
t.Fatalf("unexpected marshal output: %s", data)
|
||||
}
|
||||
|
||||
var decoded payload
|
||||
if err := API.Unmarshal(data, &decoded); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if decoded.Name != "gin" {
|
||||
t.Fatalf("unexpected decoded payload: %#v", decoded)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAPIMarshalIndent(t *testing.T) {
|
||||
data, err := API.MarshalIndent(map[string]string{"name": "gin"}, "", " ")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !bytes.Contains(data, []byte("\n ")) {
|
||||
t.Fatalf("expected indented JSON, got %q", data)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAPIEncoder(t *testing.T) {
|
||||
var buf bytes.Buffer
|
||||
encoder := API.NewEncoder(&buf)
|
||||
encoder.SetEscapeHTML(false)
|
||||
|
||||
if err := encoder.Encode("<gin>"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if got := buf.String(); got != "\"<gin>\"\n" {
|
||||
t.Fatalf("unexpected encoded JSON: %q", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAPIDecoder(t *testing.T) {
|
||||
decoder := API.NewDecoder(strings.NewReader(`{"known": 1, "extra": 2}`))
|
||||
decoder.UseNumber()
|
||||
decoder.DisallowUnknownFields()
|
||||
|
||||
var dst struct {
|
||||
Known json.Number `json:"known"`
|
||||
}
|
||||
if err := decoder.Decode(&dst); err == nil {
|
||||
t.Fatal("expected unknown field error")
|
||||
}
|
||||
}
|
||||
@ -8,10 +8,13 @@ import (
|
||||
"html/template"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func init() {
|
||||
@ -214,6 +217,24 @@ func TestSetHTMLTemplate(t *testing.T) {
|
||||
assert.NotNil(t, engine())
|
||||
}
|
||||
|
||||
func TestLoadHTMLGlob(t *testing.T) {
|
||||
LoadHTMLGlob("../testdata/template/*")
|
||||
|
||||
assert.NotNil(t, engine())
|
||||
}
|
||||
|
||||
func TestLoadHTMLFiles(t *testing.T) {
|
||||
LoadHTMLFiles("../testdata/template/hello.tmpl", "../testdata/template/raw.tmpl")
|
||||
|
||||
assert.NotNil(t, engine())
|
||||
}
|
||||
|
||||
func TestLoadHTMLFS(t *testing.T) {
|
||||
LoadHTMLFS(http.Dir("../testdata"), "template/hello.tmpl", "template/raw.tmpl")
|
||||
|
||||
assert.NotNil(t, engine())
|
||||
}
|
||||
|
||||
func TestStaticFile(t *testing.T) {
|
||||
StaticFile("/static-file", "../testdata/test_file.txt")
|
||||
|
||||
@ -224,6 +245,33 @@ func TestStaticFile(t *testing.T) {
|
||||
assert.Equal(t, http.StatusOK, w.Code)
|
||||
}
|
||||
|
||||
func TestRunReturnsListenError(t *testing.T) {
|
||||
err := Run("127.0.0.1:bad-port")
|
||||
|
||||
require.Error(t, err)
|
||||
}
|
||||
|
||||
func TestRunTLSReturnsListenError(t *testing.T) {
|
||||
err := RunTLS("127.0.0.1:bad-port", "../testdata/certificate/cert.pem", "../testdata/certificate/key.pem")
|
||||
|
||||
require.Error(t, err)
|
||||
}
|
||||
|
||||
func TestRunUnixReturnsListenError(t *testing.T) {
|
||||
err := RunUnix(filepath.Join(t.TempDir(), "missing", "gin.sock"))
|
||||
|
||||
require.Error(t, err)
|
||||
}
|
||||
|
||||
func TestRunFdReturnsListenerError(t *testing.T) {
|
||||
file, err := os.CreateTemp(t.TempDir(), "gin-fd")
|
||||
require.NoError(t, err)
|
||||
|
||||
err = RunFd(int(file.Fd()))
|
||||
|
||||
require.Error(t, err)
|
||||
}
|
||||
|
||||
func TestStatic(t *testing.T) {
|
||||
Static("/static-dir", "../testdata")
|
||||
|
||||
|
||||
8
go.mod
8
go.mod
@ -16,7 +16,7 @@ require (
|
||||
github.com/stretchr/testify v1.11.1
|
||||
github.com/ugorji/go/codec v1.3.1
|
||||
go.mongodb.org/mongo-driver/v2 v2.5.0
|
||||
golang.org/x/net v0.55.0
|
||||
golang.org/x/net v0.56.0
|
||||
google.golang.org/protobuf v1.36.11
|
||||
)
|
||||
|
||||
@ -39,7 +39,7 @@ require (
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
||||
go.uber.org/mock v0.6.0 // indirect
|
||||
golang.org/x/arch v0.25.0 // indirect
|
||||
golang.org/x/crypto v0.52.0 // indirect
|
||||
golang.org/x/sys v0.45.0 // indirect
|
||||
golang.org/x/text v0.37.0 // indirect
|
||||
golang.org/x/crypto v0.53.0 // indirect
|
||||
golang.org/x/sys v0.46.0 // indirect
|
||||
golang.org/x/text v0.39.0 // indirect
|
||||
)
|
||||
|
||||
16
go.sum
16
go.sum
@ -79,15 +79,15 @@ go.uber.org/mock v0.6.0 h1:hyF9dfmbgIX5EfOdasqLsWD6xqpNZlXblLB/Dbnwv3Y=
|
||||
go.uber.org/mock v0.6.0/go.mod h1:KiVJ4BqZJaMj4svdfmHM0AUx4NJYO8ZNpPnZn1Z+BBU=
|
||||
golang.org/x/arch v0.25.0 h1:qnk6Ksugpi5Bz32947rkUgDt9/s5qvqDPl/gBKdMJLE=
|
||||
golang.org/x/arch v0.25.0/go.mod h1:0X+GdSIP+kL5wPmpK7sdkEVTt2XoYP0cSjQSbZBwOi8=
|
||||
golang.org/x/crypto v0.52.0 h1:RMs7fP2rXdep0CftQlK8Uf+kibLm7qkCcradZWYz988=
|
||||
golang.org/x/crypto v0.52.0/go.mod h1:1QgfPxDqh0T2M/elOJtp9RvuR95kVjir0e6/BvEmGbc=
|
||||
golang.org/x/net v0.55.0 h1:bcvxaJn3e1U6InsFWt1JUq1aSjnRxLzT2rtD2KfkDF8=
|
||||
golang.org/x/net v0.55.0/go.mod h1:L5U2KuzuOe1lY7Z+aWVIKK6qEeJXnXV9yzGA+WCHJww=
|
||||
golang.org/x/crypto v0.53.0 h1:QZ4Muo8THX6CizN2vPPd5fBGHyogrdK9fG4wLPFUsto=
|
||||
golang.org/x/crypto v0.53.0/go.mod h1:DNLU434OwVakk9PzuwV8w62mAJpRJL3vsgcfp4Qnsio=
|
||||
golang.org/x/net v0.56.0 h1:Rw8j/hFzGvJUZwNBXnAtf5sVDVt+65SK2C7IxCxZt5o=
|
||||
golang.org/x/net v0.56.0/go.mod h1:D3Ku6r+V6JROoZK144D2XfMHFcMq/0zSfLelVTCFKec=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.45.0 h1:dO4czNzziLiiXplLQgBCEpCvXQ3dnkn0SdaZSYdQ+FY=
|
||||
golang.org/x/sys v0.45.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
|
||||
golang.org/x/text v0.37.0 h1:Cqjiwd9eSg8e0QAkyCaQTNHFIIzWtidPahFWR83rTrc=
|
||||
golang.org/x/text v0.37.0/go.mod h1:a5sjxXGs9hsn/AJVwuElvCAo9v8QYLzvavO5z2PiM38=
|
||||
golang.org/x/sys v0.46.0 h1:noSf2Fq6F8DBgS+LysIkx7rIExoNHJsxOAtPp4rthXw=
|
||||
golang.org/x/sys v0.46.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
|
||||
golang.org/x/text v0.39.0 h1:UbZz4pLOvn600D6Oh6GGEI6VAmndrEBLv8/6BEXzyus=
|
||||
golang.org/x/text v0.39.0/go.mod h1:3UwRclnC2g0TU9x8PZiyfOajCd1zaUNHF9cvqcQZ+ZM=
|
||||
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
|
||||
google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
|
||||
2
path.go
2
path.go
@ -55,7 +55,7 @@ func cleanPath(p string) string {
|
||||
|
||||
// A bit more clunky without a 'lazybuf' like the path package, but the loop
|
||||
// gets completely inlined (bufApp calls).
|
||||
// loop has no expensive function calls (except 1x make) // So in contrast to the path package this loop has no expensive function
|
||||
// So in contrast to the path package this loop has no expensive function
|
||||
// calls (except make, if needed).
|
||||
|
||||
for r < n {
|
||||
|
||||
42
tree.go
42
tree.go
@ -417,41 +417,38 @@ type skippedNode struct {
|
||||
// given path.
|
||||
func (n *node) getValue(path string, params *Params, skippedNodes *[]skippedNode, unescape bool) (value nodeValue) {
|
||||
var globalParamsCount int16
|
||||
var skipStatic bool
|
||||
|
||||
walk: // Outer loop for walking the tree
|
||||
for {
|
||||
prefix := n.path
|
||||
if len(path) > len(prefix) {
|
||||
if path[:len(prefix)] == prefix {
|
||||
fullPath := path
|
||||
path = path[len(prefix):]
|
||||
|
||||
// Try all the non-wildcard children first by matching the indices
|
||||
idxc := path[0]
|
||||
for i, c := range []byte(n.indices) {
|
||||
if c == idxc {
|
||||
// strings.HasPrefix(n.children[len(n.children)-1].path, ":") == n.wildChild
|
||||
if n.wildChild {
|
||||
index := len(*skippedNodes)
|
||||
*skippedNodes = (*skippedNodes)[:index+1]
|
||||
(*skippedNodes)[index] = skippedNode{
|
||||
path: prefix + path,
|
||||
node: &node{
|
||||
path: n.path,
|
||||
wildChild: n.wildChild,
|
||||
nType: n.nType,
|
||||
priority: n.priority,
|
||||
children: n.children,
|
||||
handlers: n.handlers,
|
||||
fullPath: n.fullPath,
|
||||
},
|
||||
paramsCount: globalParamsCount,
|
||||
if !skipStatic {
|
||||
for i, c := range []byte(n.indices) {
|
||||
if c == idxc {
|
||||
// strings.HasPrefix(n.children[len(n.children)-1].path, ":") == n.wildChild
|
||||
if n.wildChild {
|
||||
index := len(*skippedNodes)
|
||||
*skippedNodes = (*skippedNodes)[:index+1]
|
||||
(*skippedNodes)[index] = skippedNode{
|
||||
path: fullPath,
|
||||
node: n,
|
||||
paramsCount: globalParamsCount,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
n = n.children[i]
|
||||
continue walk
|
||||
n = n.children[i]
|
||||
continue walk
|
||||
}
|
||||
}
|
||||
}
|
||||
skipStatic = false
|
||||
|
||||
if !n.wildChild {
|
||||
// If the path at the end of the loop is not equal to '/' and the current node has no child nodes
|
||||
@ -467,6 +464,7 @@ walk: // Outer loop for walking the tree
|
||||
*value.params = (*value.params)[:skippedNode.paramsCount]
|
||||
}
|
||||
globalParamsCount = skippedNode.paramsCount
|
||||
skipStatic = true
|
||||
continue walk
|
||||
}
|
||||
}
|
||||
@ -598,6 +596,7 @@ walk: // Outer loop for walking the tree
|
||||
*value.params = (*value.params)[:skippedNode.paramsCount]
|
||||
}
|
||||
globalParamsCount = skippedNode.paramsCount
|
||||
skipStatic = true
|
||||
continue walk
|
||||
}
|
||||
}
|
||||
@ -655,6 +654,7 @@ walk: // Outer loop for walking the tree
|
||||
*value.params = (*value.params)[:skippedNode.paramsCount]
|
||||
}
|
||||
globalParamsCount = skippedNode.paramsCount
|
||||
skipStatic = true
|
||||
continue walk
|
||||
}
|
||||
}
|
||||
|
||||
34
tree_test.go
34
tree_test.go
@ -939,6 +939,40 @@ func TestTreeExpandParamsCapacity(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestTreeFindCaseInsensitivePathWithWildcardChildAndBufferedRune(t *testing.T) {
|
||||
tree := &node{
|
||||
path: "/",
|
||||
indices: "x",
|
||||
wildChild: true,
|
||||
children: []*node{
|
||||
{path: "x", handlers: fakeHandler("/x"), fullPath: "/x"},
|
||||
{path: ":id", nType: param, handlers: fakeHandler("/:id"), fullPath: "/:id"},
|
||||
},
|
||||
}
|
||||
|
||||
out := tree.findCaseInsensitivePathRec("/x", nil, [4]byte{0, 'x'}, false)
|
||||
if string(out) != "/x" {
|
||||
t.Fatalf("Wrong result: got %s, want /x", string(out))
|
||||
}
|
||||
}
|
||||
|
||||
func TestTreeFindCaseInsensitivePathWithWildcardChildAndUppercaseStatic(t *testing.T) {
|
||||
tree := &node{
|
||||
path: "/",
|
||||
indices: "A",
|
||||
wildChild: true,
|
||||
children: []*node{
|
||||
{path: "A", handlers: fakeHandler("/A"), fullPath: "/A"},
|
||||
{path: ":id", nType: param, handlers: fakeHandler("/:id"), fullPath: "/:id"},
|
||||
},
|
||||
}
|
||||
|
||||
out := tree.findCaseInsensitivePathRec("/a", nil, [4]byte{}, false)
|
||||
if string(out) != "/A" {
|
||||
t.Fatalf("Wrong result: got %s, want /A", string(out))
|
||||
}
|
||||
}
|
||||
|
||||
func TestTreeWildcardConflictEx(t *testing.T) {
|
||||
conflicts := [...]struct {
|
||||
route string
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user