mirror of
				https://github.com/gin-gonic/gin.git
				synced 2025-11-04 01:02:14 +08:00 
			
		
		
		
	Compare commits
	
		
			29 Commits
		
	
	
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						 | 
					2e22e50859 | ||
| 
						 | 
					52f70cf18a | ||
| 
						 | 
					87c207a140 | ||
| 
						 | 
					c0048f645e | ||
| 
						 | 
					38e7651192 | ||
| 
						 | 
					c221133ee8 | ||
| 
						 | 
					c3d1092b3b | ||
| 
						 | 
					9968c4bf9d | ||
| 
						 | 
					053e5765fd | ||
| 
						 | 
					0d085ed9fe | ||
| 
						 | 
					5dd833f1f2 | ||
| 
						 | 
					48a5dca087 | ||
| 
						 | 
					0bd10a84f9 | ||
| 
						 | 
					4dec17afdf | ||
| 
						 | 
					731374fb36 | ||
| 
						 | 
					8ca975441f | ||
| 
						 | 
					39858a0859 | ||
| 
						 | 
					ed150e7254 | ||
| 
						 | 
					234a6d4c00 | ||
| 
						 | 
					df2753778e | ||
| 
						 | 
					048f6fb884 | ||
| 
						 | 
					61b67de522 | ||
| 
						 | 
					f3a5e78719 | ||
| 
						 | 
					414de60574 | ||
| 
						 | 
					59e9d4a794 | ||
| 
						 | 
					6a1d1218c3 | ||
| 
						 | 
					7925414704 | ||
| 
						 | 
					1bbbec0baf | ||
| 
						 | 
					4dd00f81b1 | 
							
								
								
									
										49
									
								
								.github/ISSUE_TEMPLATE.md
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										49
									
								
								.github/ISSUE_TEMPLATE.md
									
									
									
									
										vendored
									
									
								
							@ -1,49 +0,0 @@
 | 
				
			|||||||
- With issues:
 | 
					 | 
				
			||||||
  - Use the search tool before opening a new issue.
 | 
					 | 
				
			||||||
  - Please provide source code and commit sha if you found a bug.
 | 
					 | 
				
			||||||
  - Review existing issues and provide feedback or react to them.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
## Description
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
<!-- Description of a problem -->
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
## How to reproduce
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
<!-- The smallest possible code example to show the problem that can be compiled, like -->
 | 
					 | 
				
			||||||
```
 | 
					 | 
				
			||||||
package main
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import (
 | 
					 | 
				
			||||||
	"github.com/gin-gonic/gin"
 | 
					 | 
				
			||||||
)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
func main() {
 | 
					 | 
				
			||||||
	g := gin.Default()
 | 
					 | 
				
			||||||
	g.GET("/hello/:name", func(c *gin.Context) {
 | 
					 | 
				
			||||||
		c.String(200, "Hello %s", c.Param("name"))
 | 
					 | 
				
			||||||
	})
 | 
					 | 
				
			||||||
	g.Run(":9000")
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
```
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
## Expectations
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
<!-- Your expectation result of 'curl' command, like -->
 | 
					 | 
				
			||||||
```
 | 
					 | 
				
			||||||
$ curl http://localhost:9000/hello/world
 | 
					 | 
				
			||||||
Hello world
 | 
					 | 
				
			||||||
```
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
## Actual result
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
<!-- Actual result showing the problem -->
 | 
					 | 
				
			||||||
```
 | 
					 | 
				
			||||||
$ curl -i http://localhost:9000/hello/world
 | 
					 | 
				
			||||||
<YOUR RESULT>
 | 
					 | 
				
			||||||
```
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
## Environment
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
- go version:
 | 
					 | 
				
			||||||
- gin version (or commit ref):
 | 
					 | 
				
			||||||
- operating system:
 | 
					 | 
				
			||||||
							
								
								
									
										60
									
								
								.github/ISSUE_TEMPLATE/bug-report.yaml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										60
									
								
								.github/ISSUE_TEMPLATE/bug-report.yaml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@ -0,0 +1,60 @@
 | 
				
			|||||||
 | 
					name: Bug Report
 | 
				
			||||||
 | 
					description: Found something you weren't expecting? Report it here!
 | 
				
			||||||
 | 
					labels: ["type/bug"]
 | 
				
			||||||
 | 
					body:
 | 
				
			||||||
 | 
					  - type: markdown
 | 
				
			||||||
 | 
					    attributes:
 | 
				
			||||||
 | 
					      value: |
 | 
				
			||||||
 | 
					        NOTE: If your issue is a security concern, please send an email to appleboy.tw@gmail.com instead of opening a public issue.
 | 
				
			||||||
 | 
					  - type: markdown
 | 
				
			||||||
 | 
					    attributes:
 | 
				
			||||||
 | 
					      value: |
 | 
				
			||||||
 | 
					        1. Please speak English, this is the language all maintainers can speak and write.
 | 
				
			||||||
 | 
					        2. Please ask questions problems on our Discussions Forum (https://github.com/gin-gonic/gin/discussions).
 | 
				
			||||||
 | 
					        3. Make sure you are using the latest release and
 | 
				
			||||||
 | 
					           take a moment to check that your issue hasn't been reported before.
 | 
				
			||||||
 | 
					  - type: textarea
 | 
				
			||||||
 | 
					    id: description
 | 
				
			||||||
 | 
					    attributes:
 | 
				
			||||||
 | 
					      label: Description
 | 
				
			||||||
 | 
					      description: |
 | 
				
			||||||
 | 
					        Please provide a description of your issue here, with a URL if you were able to reproduce the issue (see below)
 | 
				
			||||||
 | 
					  - type: input
 | 
				
			||||||
 | 
					    id: gin-ver
 | 
				
			||||||
 | 
					    attributes:
 | 
				
			||||||
 | 
					      label: Gin Version
 | 
				
			||||||
 | 
					      description: Gin version (or commit reference) of your instance
 | 
				
			||||||
 | 
					    validations:
 | 
				
			||||||
 | 
					      required: true
 | 
				
			||||||
 | 
					  - type: dropdown
 | 
				
			||||||
 | 
					    id: can-reproduce
 | 
				
			||||||
 | 
					    attributes:
 | 
				
			||||||
 | 
					      label: Can you reproduce the bug?
 | 
				
			||||||
 | 
					      description: |
 | 
				
			||||||
 | 
					        If so, please write the steps to reproduce the bug.
 | 
				
			||||||
 | 
					      options:
 | 
				
			||||||
 | 
					        - "Yes"
 | 
				
			||||||
 | 
					        - "No"
 | 
				
			||||||
 | 
					    validations:
 | 
				
			||||||
 | 
					      required: true
 | 
				
			||||||
 | 
					  - type: markdown
 | 
				
			||||||
 | 
					    attributes:
 | 
				
			||||||
 | 
					      value: |
 | 
				
			||||||
 | 
					        It's really important to provide pertinent logs
 | 
				
			||||||
 | 
					        Please read https://docs.gitea.com/administration/logging-config#collecting-logs-for-help
 | 
				
			||||||
 | 
					        In addition, if your problem relates to git commands set `RUN_MODE=dev` at the top of app.ini
 | 
				
			||||||
 | 
					  - type: textarea
 | 
				
			||||||
 | 
					    id: source-code
 | 
				
			||||||
 | 
					    attributes:
 | 
				
			||||||
 | 
					      label: Source Code
 | 
				
			||||||
 | 
					      description: If this issue involves source code, please provide a minimal reproducible example
 | 
				
			||||||
 | 
					  - type: input
 | 
				
			||||||
 | 
					    id: go-ver
 | 
				
			||||||
 | 
					    attributes:
 | 
				
			||||||
 | 
					      label: Go Version
 | 
				
			||||||
 | 
					      description: The version of Go running on the server
 | 
				
			||||||
 | 
					  - type: input
 | 
				
			||||||
 | 
					    id: os-ver
 | 
				
			||||||
 | 
					    attributes:
 | 
				
			||||||
 | 
					      label: Operating System
 | 
				
			||||||
 | 
					      description: The operating system you are using to run Gin
 | 
				
			||||||
							
								
								
									
										11
									
								
								.github/ISSUE_TEMPLATE/config.yml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								.github/ISSUE_TEMPLATE/config.yml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@ -0,0 +1,11 @@
 | 
				
			|||||||
 | 
					blank_issues_enabled: false
 | 
				
			||||||
 | 
					contact_links:
 | 
				
			||||||
 | 
					  - name: Go.dev API Documentation
 | 
				
			||||||
 | 
					    url: https://pkg.go.dev/github.com/gin-gonic/gin
 | 
				
			||||||
 | 
					    about: Comprehensive API documentation for Gin.
 | 
				
			||||||
 | 
					  - name: Gin User Guides
 | 
				
			||||||
 | 
					    url: https://gin-gonic.com/
 | 
				
			||||||
 | 
					    about: In-depth user guides and tutorials for using Gin.
 | 
				
			||||||
 | 
					  - name: Discussions Forum
 | 
				
			||||||
 | 
					    url: https://github.com/gin-gonic/gin/discussions
 | 
				
			||||||
 | 
					    about: Questions and configuration or deployment problems can also be discussed.
 | 
				
			||||||
							
								
								
									
										18
									
								
								.github/ISSUE_TEMPLATE/feature-request.yaml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								.github/ISSUE_TEMPLATE/feature-request.yaml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@ -0,0 +1,18 @@
 | 
				
			|||||||
 | 
					name: Feature Request
 | 
				
			||||||
 | 
					description: Got an idea for a feature that Gin doesn't have currently?  Submit your idea here!
 | 
				
			||||||
 | 
					labels: ["type/proposal"]
 | 
				
			||||||
 | 
					body:
 | 
				
			||||||
 | 
					  - type: markdown
 | 
				
			||||||
 | 
					    attributes:
 | 
				
			||||||
 | 
					      value: |
 | 
				
			||||||
 | 
					        1. Please speak English, this is the language all maintainers can speak and write.
 | 
				
			||||||
 | 
					        2. Please ask questions problems on our Discussions Forum (https://github.com/gin-gonic/gin/discussions).
 | 
				
			||||||
 | 
					        3. Please take a moment to check that your feature hasn't already been suggested.
 | 
				
			||||||
 | 
					  - type: textarea
 | 
				
			||||||
 | 
					    id: description
 | 
				
			||||||
 | 
					    attributes:
 | 
				
			||||||
 | 
					      label: Feature Description
 | 
				
			||||||
 | 
					      placeholder: |
 | 
				
			||||||
 | 
					        I think it would be great if Gin had...
 | 
				
			||||||
 | 
					    validations:
 | 
				
			||||||
 | 
					      required: true
 | 
				
			||||||
							
								
								
									
										15
									
								
								.github/PULL_REQUEST_TEMPLATE.md
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										15
									
								
								.github/PULL_REQUEST_TEMPLATE.md
									
									
									
									
										vendored
									
									
								
							@ -1,7 +1,10 @@
 | 
				
			|||||||
- With pull requests:
 | 
					# Pull Request Checklist
 | 
				
			||||||
  - Open your pull request against `master`
 | 
					 | 
				
			||||||
  - Your pull request should have no more than two commits, if not you should squash them.
 | 
					 | 
				
			||||||
  - It should pass all tests in the available continuous integration systems such as GitHub Actions.
 | 
					 | 
				
			||||||
  - You should add/modify tests to cover your proposed code changes.
 | 
					 | 
				
			||||||
  - If your pull request contains a new feature, please document it on the README.
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Please ensure your pull request meets the following requirements:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- [ ] Open your pull request against the `master` branch.
 | 
				
			||||||
 | 
					- [ ] All tests pass in available continuous integration systems (e.g., GitHub Actions).
 | 
				
			||||||
 | 
					- [ ] Tests are added or modified as needed to cover code changes.
 | 
				
			||||||
 | 
					- [ ] If the pull request introduces a new feature, the feature is documented in the `docs/doc.md`.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Thank you for contributing!
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										14
									
								
								.github/dependabot.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										14
									
								
								.github/dependabot.yml
									
									
									
									
										vendored
									
									
								
							@ -1,10 +1,14 @@
 | 
				
			|||||||
version: 2
 | 
					version: 2
 | 
				
			||||||
updates:
 | 
					updates:
 | 
				
			||||||
  - package-ecosystem: github-actions
 | 
					 | 
				
			||||||
    directory: /
 | 
					 | 
				
			||||||
    schedule:
 | 
					 | 
				
			||||||
      interval: weekly
 | 
					 | 
				
			||||||
  - package-ecosystem: gomod
 | 
					  - package-ecosystem: gomod
 | 
				
			||||||
    directory: /
 | 
					    directory: /
 | 
				
			||||||
    schedule:
 | 
					    schedule:
 | 
				
			||||||
      interval: weekly
 | 
					      interval: daily
 | 
				
			||||||
 | 
					  - package-ecosystem: github-actions
 | 
				
			||||||
 | 
					    directory: /
 | 
				
			||||||
 | 
					    groups:
 | 
				
			||||||
 | 
					      actions:
 | 
				
			||||||
 | 
					        patterns:
 | 
				
			||||||
 | 
					          - "*"
 | 
				
			||||||
 | 
					    schedule:
 | 
				
			||||||
 | 
					      interval: daily
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										6
									
								
								.github/workflows/codeql.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										6
									
								
								.github/workflows/codeql.yml
									
									
									
									
										vendored
									
									
								
							@ -33,11 +33,11 @@ jobs:
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    steps:
 | 
					    steps:
 | 
				
			||||||
      - name: Checkout repository
 | 
					      - name: Checkout repository
 | 
				
			||||||
        uses: actions/checkout@v4
 | 
					        uses: actions/checkout@v5
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      # Initializes the CodeQL tools for scanning.
 | 
					      # Initializes the CodeQL tools for scanning.
 | 
				
			||||||
      - name: Initialize CodeQL
 | 
					      - name: Initialize CodeQL
 | 
				
			||||||
        uses: github/codeql-action/init@v3
 | 
					        uses: github/codeql-action/init@v4
 | 
				
			||||||
        with:
 | 
					        with:
 | 
				
			||||||
          languages: ${{ matrix.language }}
 | 
					          languages: ${{ matrix.language }}
 | 
				
			||||||
          # If you wish to specify custom queries, you can do so here or in a config file.
 | 
					          # If you wish to specify custom queries, you can do so here or in a config file.
 | 
				
			||||||
@ -46,4 +46,4 @@ jobs:
 | 
				
			|||||||
          # queries: ./path/to/local/query, your-org/your-repo/queries@main
 | 
					          # queries: ./path/to/local/query, your-org/your-repo/queries@main
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      - name: Perform CodeQL Analysis
 | 
					      - name: Perform CodeQL Analysis
 | 
				
			||||||
        uses: github/codeql-action/analyze@v3
 | 
					        uses: github/codeql-action/analyze@v4
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										16
									
								
								.github/workflows/gin.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										16
									
								
								.github/workflows/gin.yml
									
									
									
									
										vendored
									
									
								
							@ -16,7 +16,7 @@ jobs:
 | 
				
			|||||||
    runs-on: ubuntu-latest
 | 
					    runs-on: ubuntu-latest
 | 
				
			||||||
    steps:
 | 
					    steps:
 | 
				
			||||||
      - name: Checkout
 | 
					      - name: Checkout
 | 
				
			||||||
        uses: actions/checkout@v4
 | 
					        uses: actions/checkout@v5
 | 
				
			||||||
        with:
 | 
					        with:
 | 
				
			||||||
          fetch-depth: 0
 | 
					          fetch-depth: 0
 | 
				
			||||||
      - name: Set up Go
 | 
					      - name: Set up Go
 | 
				
			||||||
@ -33,7 +33,7 @@ jobs:
 | 
				
			|||||||
    strategy:
 | 
					    strategy:
 | 
				
			||||||
      matrix:
 | 
					      matrix:
 | 
				
			||||||
        os: [ubuntu-latest, macos-latest]
 | 
					        os: [ubuntu-latest, macos-latest]
 | 
				
			||||||
        go: ["1.23", "1.24", "1.25"]
 | 
					        go: ["1.24", "1.25"]
 | 
				
			||||||
        test-tags:
 | 
					        test-tags:
 | 
				
			||||||
          [
 | 
					          [
 | 
				
			||||||
            "",
 | 
					            "",
 | 
				
			||||||
@ -61,7 +61,7 @@ jobs:
 | 
				
			|||||||
          cache: false
 | 
					          cache: false
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      - name: Checkout Code
 | 
					      - name: Checkout Code
 | 
				
			||||||
        uses: actions/checkout@v4
 | 
					        uses: actions/checkout@v5
 | 
				
			||||||
        with:
 | 
					        with:
 | 
				
			||||||
          ref: ${{ github.ref }}
 | 
					          ref: ${{ github.ref }}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -78,7 +78,7 @@ jobs:
 | 
				
			|||||||
        run: make test
 | 
					        run: make test
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      - name: Upload coverage to Codecov
 | 
					      - name: Upload coverage to Codecov
 | 
				
			||||||
        uses: codecov/codecov-action@v4
 | 
					        uses: codecov/codecov-action@v5
 | 
				
			||||||
        with:
 | 
					        with:
 | 
				
			||||||
          flags: ${{ matrix.os }},go-${{ matrix.go }},${{ matrix.test-tags }}
 | 
					          flags: ${{ matrix.os }},go-${{ matrix.go }},${{ matrix.test-tags }}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -92,8 +92,8 @@ jobs:
 | 
				
			|||||||
      - name: Run Trivy vulnerability scanner in repo mode
 | 
					      - name: Run Trivy vulnerability scanner in repo mode
 | 
				
			||||||
        uses: aquasecurity/trivy-action@0.33.1
 | 
					        uses: aquasecurity/trivy-action@0.33.1
 | 
				
			||||||
        with:
 | 
					        with:
 | 
				
			||||||
          scan-type: 'fs'
 | 
					          scan-type: "fs"
 | 
				
			||||||
          ignore-unfixed: true
 | 
					          ignore-unfixed: true
 | 
				
			||||||
          format: 'table'
 | 
					          format: "table"
 | 
				
			||||||
          exit-code: '1'
 | 
					          exit-code: "1"
 | 
				
			||||||
          severity: 'CRITICAL,HIGH,MEDIUM'
 | 
					          severity: "CRITICAL,HIGH,MEDIUM"
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										2
									
								
								.github/workflows/goreleaser.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.github/workflows/goreleaser.yml
									
									
									
									
										vendored
									
									
								
							@ -13,7 +13,7 @@ jobs:
 | 
				
			|||||||
    runs-on: ubuntu-latest
 | 
					    runs-on: ubuntu-latest
 | 
				
			||||||
    steps:
 | 
					    steps:
 | 
				
			||||||
      - name: Checkout
 | 
					      - name: Checkout
 | 
				
			||||||
        uses: actions/checkout@v4
 | 
					        uses: actions/checkout@v5
 | 
				
			||||||
        with:
 | 
					        with:
 | 
				
			||||||
          fetch-depth: 0
 | 
					          fetch-depth: 0
 | 
				
			||||||
      - name: Set up Go
 | 
					      - name: Set up Go
 | 
				
			||||||
 | 
				
			|||||||
@ -1,13 +1,41 @@
 | 
				
			|||||||
## Contributing
 | 
					# Contributing
 | 
				
			||||||
 | 
					
 | 
				
			||||||
- With issues:
 | 
					We welcome both issue reports and pull requests! Please follow these guidelines to help maintainers respond effectively.
 | 
				
			||||||
  - Use the search tool before opening a new issue.
 | 
					
 | 
				
			||||||
  - Please provide source code and commit sha if you found a bug.
 | 
					## Issues
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- **Before opening a new issue:**
 | 
				
			||||||
 | 
					  - Use the search tool to check for existing issues or feature requests.
 | 
				
			||||||
  - Review existing issues and provide feedback or react to them.
 | 
					  - Review existing issues and provide feedback or react to them.
 | 
				
			||||||
 | 
					  - Use English for all communications — it is the language all maintainers read and write.
 | 
				
			||||||
 | 
					  - For questions, configuration or deployment problems, please use the [Discussions Forum](https://github.com/gin-gonic/gin/discussions).
 | 
				
			||||||
 | 
					  - For bug reports involving sensitive security issues, email <appleboy.tw@gmail.com> instead of posting publicly.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
- With pull requests:
 | 
					- **Reporting a bug:**
 | 
				
			||||||
  - Open your pull request against `master`
 | 
					  - Please provide a clear description of your issue, and a minimal reproducible code example if possible.
 | 
				
			||||||
  - Your pull request should have no more than two commits, if not you should squash them.
 | 
					  - Include the Gin version (or commit reference), Go version, and operating system.
 | 
				
			||||||
  - It should pass all tests in the available continuous integration systems such as GitHub Actions.
 | 
					  - Indicate whether you can reproduce the bug and describe steps to do so.
 | 
				
			||||||
  - You should add/modify tests to cover your proposed code changes.
 | 
					  - Attach relevant logs per [Logging Documentation](https://docs.gitea.com/administration/logging-config#collecting-logs-for-help).
 | 
				
			||||||
  - If your pull request contains a new feature, please document it on the README.
 | 
					
 | 
				
			||||||
 | 
					- **Feature requests:**
 | 
				
			||||||
 | 
					  - Before opening a request, check that a similar idea hasn’t already been suggested.
 | 
				
			||||||
 | 
					  - Clearly describe your proposed feature and its benefits.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					_For API Documentation, User Guides, and more, see:_
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- [Go.dev API Documentation](https://pkg.go.dev/github.com/gin-gonic/gin)
 | 
				
			||||||
 | 
					- [Gin User Guides](https://gin-gonic.com/)
 | 
				
			||||||
 | 
					- [Discussions Forum](https://github.com/gin-gonic/gin/discussions)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## Pull Requests
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Please ensure your pull request meets the following requirements:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- Open your pull request against the `master` branch.
 | 
				
			||||||
 | 
					- Your pull request should have no more than two commits — squash them if necessary.
 | 
				
			||||||
 | 
					- All tests pass in available continuous integration systems (e.g., GitHub Actions).
 | 
				
			||||||
 | 
					- Add or modify tests to cover your code changes.
 | 
				
			||||||
 | 
					- If your pull request introduces a new feature, document it in [`docs/doc.md`](docs/doc.md), not in the README.
 | 
				
			||||||
 | 
					- Follow the checklist in the [Pull Request Template](.github/PULL_REQUEST_TEMPLATE.md:1).
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Thank you for contributing!
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										186
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										186
									
								
								README.md
									
									
									
									
									
								
							@ -9,46 +9,54 @@
 | 
				
			|||||||
[](https://sourcegraph.com/github.com/gin-gonic/gin?badge)
 | 
					[](https://sourcegraph.com/github.com/gin-gonic/gin?badge)
 | 
				
			||||||
[](https://www.codetriage.com/gin-gonic/gin)
 | 
					[](https://www.codetriage.com/gin-gonic/gin)
 | 
				
			||||||
[](https://github.com/gin-gonic/gin/releases)
 | 
					[](https://github.com/gin-gonic/gin/releases)
 | 
				
			||||||
[](https://www.tickgit.com/browse?repo=github.com/gin-gonic/gin)
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
Gin is a web framework written in [Go](https://go.dev/). It features a martini-like API with performance that is up to 40 times faster thanks to [httprouter](https://github.com/julienschmidt/httprouter).
 | 
					## 📰 [Announcing Gin 1.11.0!](https://gin-gonic.com/en/blog/news/gin-1-11-0-release-announcement/)
 | 
				
			||||||
If you need performance and good productivity, you will love Gin.
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
**Gin's key features are:**
 | 
					Read about the latest features and improvements in Gin 1.11.0 on our official blog.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
- Zero allocation router
 | 
					---
 | 
				
			||||||
- Speed
 | 
					 | 
				
			||||||
- Middleware support
 | 
					 | 
				
			||||||
- Crash-free
 | 
					 | 
				
			||||||
- JSON validation
 | 
					 | 
				
			||||||
- Route grouping
 | 
					 | 
				
			||||||
- Error management
 | 
					 | 
				
			||||||
- Built-in rendering
 | 
					 | 
				
			||||||
- Extensible
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
## Getting started
 | 
					Gin is a high-performance HTTP web framework written in [Go](https://go.dev/). It provides a Martini-like API but with significantly better performance—up to 40 times faster—thanks to [httprouter](https://github.com/julienschmidt/httprouter). Gin is designed for building REST APIs, web applications, and microservices where speed and developer productivity are essential.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					**Why choose Gin?**
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Gin combines the simplicity of Express.js-style routing with Go's performance characteristics, making it ideal for:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- Building high-throughput REST APIs
 | 
				
			||||||
 | 
					- Developing microservices that need to handle many concurrent requests
 | 
				
			||||||
 | 
					- Creating web applications that require fast response times
 | 
				
			||||||
 | 
					- Prototyping web services quickly with minimal boilerplate
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					**Gin's key features:**
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- **Zero allocation router** - Extremely memory-efficient routing with no heap allocations
 | 
				
			||||||
 | 
					- **High performance** - Benchmarks show superior speed compared to other Go web frameworks
 | 
				
			||||||
 | 
					- **Middleware support** - Extensible middleware system for authentication, logging, CORS, etc.
 | 
				
			||||||
 | 
					- **Crash-free** - Built-in recovery middleware prevents panics from crashing your server
 | 
				
			||||||
 | 
					- **JSON validation** - Automatic request/response JSON binding and validation
 | 
				
			||||||
 | 
					- **Route grouping** - Organize related routes and apply common middleware
 | 
				
			||||||
 | 
					- **Error management** - Centralized error handling and logging
 | 
				
			||||||
 | 
					- **Built-in rendering** - Support for JSON, XML, HTML templates, and more
 | 
				
			||||||
 | 
					- **Extensible** - Large ecosystem of community middleware and plugins
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## Getting Started
 | 
				
			||||||
 | 
					
 | 
				
			||||||
### Prerequisites
 | 
					### Prerequisites
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Gin requires [Go](https://go.dev/) version [1.23](https://go.dev/doc/devel/release#go1.23.0) or above.
 | 
					- **Go version**: Gin requires [Go](https://go.dev/) version [1.24](https://go.dev/doc/devel/release#go1.24.0) or above
 | 
				
			||||||
 | 
					- **Basic Go knowledge**: Familiarity with Go syntax and package management is helpful
 | 
				
			||||||
 | 
					
 | 
				
			||||||
### Getting Gin
 | 
					### Installation
 | 
				
			||||||
 | 
					
 | 
				
			||||||
With [Go's module support](https://go.dev/wiki/Modules#how-to-use-modules), `go [build|run|test]` automatically fetches the necessary dependencies when you add the import in your code:
 | 
					With [Go's module support](https://go.dev/wiki/Modules#how-to-use-modules), simply import Gin in your code and Go will automatically fetch it during build:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
```sh
 | 
					```go
 | 
				
			||||||
import "github.com/gin-gonic/gin"
 | 
					import "github.com/gin-gonic/gin"
 | 
				
			||||||
```
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Alternatively, use `go get`:
 | 
					### Your First Gin Application
 | 
				
			||||||
 | 
					
 | 
				
			||||||
```sh
 | 
					Here's a complete example that demonstrates Gin's simplicity:
 | 
				
			||||||
go get -u github.com/gin-gonic/gin
 | 
					 | 
				
			||||||
```
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
### Running Gin
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
A basic example:
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
```go
 | 
					```go
 | 
				
			||||||
package main
 | 
					package main
 | 
				
			||||||
@ -60,59 +68,80 @@ import (
 | 
				
			|||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func main() {
 | 
					func main() {
 | 
				
			||||||
 | 
					  // Create a Gin router with default middleware (logger and recovery)
 | 
				
			||||||
  r := gin.Default()
 | 
					  r := gin.Default()
 | 
				
			||||||
 | 
					  
 | 
				
			||||||
 | 
					  // Define a simple GET endpoint
 | 
				
			||||||
  r.GET("/ping", func(c *gin.Context) {
 | 
					  r.GET("/ping", func(c *gin.Context) {
 | 
				
			||||||
 | 
					    // Return JSON response
 | 
				
			||||||
    c.JSON(http.StatusOK, gin.H{
 | 
					    c.JSON(http.StatusOK, gin.H{
 | 
				
			||||||
      "message": "pong",
 | 
					      "message": "pong",
 | 
				
			||||||
    })
 | 
					    })
 | 
				
			||||||
  })
 | 
					  })
 | 
				
			||||||
  r.Run() // listen and serve on 0.0.0.0:8080 (for windows "localhost:8080")
 | 
					  
 | 
				
			||||||
 | 
					  // Start server on port 8080 (default)
 | 
				
			||||||
 | 
					  // Server will listen on 0.0.0.0:8080 (localhost:8080 on Windows)
 | 
				
			||||||
 | 
					  r.Run()
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
```
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
To run the code, use the `go run` command, like:
 | 
					**Running the application:**
 | 
				
			||||||
 | 
					
 | 
				
			||||||
```sh
 | 
					1. Save the code above as `main.go`
 | 
				
			||||||
go run example.go
 | 
					2. Run the application:
 | 
				
			||||||
```
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
Then visit [`0.0.0.0:8080/ping`](http://0.0.0.0:8080/ping) in your browser to see the response!
 | 
					   ```sh
 | 
				
			||||||
 | 
					   go run main.go
 | 
				
			||||||
 | 
					   ```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
### See more examples
 | 
					3. Open your browser and visit [`http://localhost:8080/ping`](http://localhost:8080/ping)
 | 
				
			||||||
 | 
					4. You should see: `{"message":"pong"}`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#### Quick Start
 | 
					**What this example demonstrates:**
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Learn and practice with the [Gin Quick Start](docs/doc.md), which includes API examples and builds tag.
 | 
					- Creating a Gin router with default middleware
 | 
				
			||||||
 | 
					- Defining HTTP endpoints with simple handler functions
 | 
				
			||||||
 | 
					- Returning JSON responses
 | 
				
			||||||
 | 
					- Starting an HTTP server
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#### Examples
 | 
					### Next Steps
 | 
				
			||||||
 | 
					
 | 
				
			||||||
A number of ready-to-run examples demonstrating various use cases of Gin are available in the [Gin examples](https://github.com/gin-gonic/examples) repository.
 | 
					After running your first Gin application, explore these resources to learn more:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
## Documentation
 | 
					#### 📚 Learning Resources
 | 
				
			||||||
 | 
					
 | 
				
			||||||
See the [API documentation on go.dev](https://pkg.go.dev/github.com/gin-gonic/gin).
 | 
					- **[Gin Quick Start Guide](docs/doc.md)** - Comprehensive tutorial with API examples and build configurations
 | 
				
			||||||
 | 
					- **[Example Repository](https://github.com/gin-gonic/examples)** - Ready-to-run examples demonstrating various Gin use cases:
 | 
				
			||||||
 | 
					  - REST API development
 | 
				
			||||||
 | 
					  - Authentication & middleware
 | 
				
			||||||
 | 
					  - File uploads and downloads
 | 
				
			||||||
 | 
					  - WebSocket connections
 | 
				
			||||||
 | 
					  - Template rendering
 | 
				
			||||||
 | 
					
 | 
				
			||||||
The documentation is also available on [gin-gonic.com](https://gin-gonic.com) in several languages:
 | 
					## 📖 Documentation
 | 
				
			||||||
 | 
					
 | 
				
			||||||
- [English](https://gin-gonic.com/en/docs/)
 | 
					### API Reference
 | 
				
			||||||
- [简体中文](https://gin-gonic.com/zh-cn/docs/)
 | 
					 | 
				
			||||||
- [繁體中文](https://gin-gonic.com/zh-tw/docs/)
 | 
					 | 
				
			||||||
- [日本語](https://gin-gonic.com/ja/docs/)
 | 
					 | 
				
			||||||
- [Español](https://gin-gonic.com/es/docs/)
 | 
					 | 
				
			||||||
- [한국어](https://gin-gonic.com/ko-kr/docs/)
 | 
					 | 
				
			||||||
- [Turkish](https://gin-gonic.com/tr/docs/)
 | 
					 | 
				
			||||||
- [Persian](https://gin-gonic.com/fa/docs/)
 | 
					 | 
				
			||||||
- [Português](https://gin-gonic.com/pt/docs/)
 | 
					 | 
				
			||||||
- [Russian](https://gin-gonic.com/ru/docs/)
 | 
					 | 
				
			||||||
- [Indonesian](https://gin-gonic.com/id/docs/)
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
### Articles
 | 
					- **[Go.dev API Documentation](https://pkg.go.dev/github.com/gin-gonic/gin)** - Complete API reference with examples
 | 
				
			||||||
 | 
					
 | 
				
			||||||
- [Tutorial: Developing a RESTful API with Go and Gin](https://go.dev/doc/tutorial/web-service-gin)
 | 
					### User Guides
 | 
				
			||||||
 | 
					
 | 
				
			||||||
## Benchmarks
 | 
					The comprehensive documentation is available on [gin-gonic.com](https://gin-gonic.com) in multiple languages:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Gin uses a custom version of [HttpRouter](https://github.com/julienschmidt/httprouter), [see all benchmarks](/BENCHMARKS.md).
 | 
					- [English](https://gin-gonic.com/en/docs/) | [简体中文](https://gin-gonic.com/zh-cn/docs/) | [繁體中文](https://gin-gonic.com/zh-tw/docs/)
 | 
				
			||||||
 | 
					- [日本語](https://gin-gonic.com/ja/docs/) | [한국어](https://gin-gonic.com/ko-kr/docs/) | [Español](https://gin-gonic.com/es/docs/)
 | 
				
			||||||
 | 
					- [Turkish](https://gin-gonic.com/tr/docs/) | [Persian](https://gin-gonic.com/fa/docs/) | [Português](https://gin-gonic.com/pt/docs/)
 | 
				
			||||||
 | 
					- [Russian](https://gin-gonic.com/ru/docs/) | [Indonesian](https://gin-gonic.com/id/docs/)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### Official Tutorials
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- [Go.dev Tutorial: Developing a RESTful API with Go and Gin](https://go.dev/doc/tutorial/web-service-gin)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## ⚡ Performance Benchmarks
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Gin demonstrates exceptional performance compared to other Go web frameworks. It uses a custom version of [HttpRouter](https://github.com/julienschmidt/httprouter) for maximum efficiency. [View detailed benchmarks →](/BENCHMARKS.md)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					**Gin vs. Other Go Frameworks** (GitHub API routing benchmark):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| Benchmark name                 |       (1) |             (2) |          (3) |             (4) |
 | 
					| Benchmark name                 |       (1) |             (2) |          (3) |             (4) |
 | 
				
			||||||
| ------------------------------ | --------: | --------------: | -----------: | --------------: |
 | 
					| ------------------------------ | --------: | --------------: | -----------: | --------------: |
 | 
				
			||||||
@ -152,23 +181,44 @@ Gin uses a custom version of [HttpRouter](https://github.com/julienschmidt/httpr
 | 
				
			|||||||
- (3): Heap Memory (B/op), lower is better
 | 
					- (3): Heap Memory (B/op), lower is better
 | 
				
			||||||
- (4): Average Allocations per Repetition (allocs/op), lower is better
 | 
					- (4): Average Allocations per Repetition (allocs/op), lower is better
 | 
				
			||||||
 | 
					
 | 
				
			||||||
## Middleware
 | 
					## 🔌 Middleware Ecosystem
 | 
				
			||||||
 | 
					
 | 
				
			||||||
You can find many useful Gin middlewares at [gin-contrib](https://github.com/gin-contrib) and [gin-gonic/contrib](https://github.com/gin-gonic/contrib).
 | 
					Gin has a rich ecosystem of middleware for common web development needs. Explore community-contributed middleware:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
## Uses
 | 
					- **[gin-contrib](https://github.com/gin-contrib)** - Official middleware collection including:
 | 
				
			||||||
 | 
					  - Authentication (JWT, Basic Auth, Sessions)
 | 
				
			||||||
 | 
					  - CORS, Rate limiting, Compression
 | 
				
			||||||
 | 
					  - Logging, Metrics, Tracing
 | 
				
			||||||
 | 
					  - Static file serving, Template engines
 | 
				
			||||||
 | 
					  
 | 
				
			||||||
 | 
					- **[gin-gonic/contrib](https://github.com/gin-gonic/contrib)** - Additional community middleware
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Here are some awesome projects that are using the [Gin](https://github.com/gin-gonic/gin) web framework.
 | 
					## 🏢 Production Usage
 | 
				
			||||||
 | 
					
 | 
				
			||||||
- [gorush](https://github.com/appleboy/gorush): A push notification server.
 | 
					Gin powers many high-traffic applications and services in production:
 | 
				
			||||||
- [fnproject](https://github.com/fnproject/fn): A container native, cloud agnostic serverless platform.
 | 
					 | 
				
			||||||
- [photoprism](https://github.com/photoprism/photoprism): Personal photo management powered by Google TensorFlow.
 | 
					 | 
				
			||||||
- [lura](https://github.com/luraproject/lura): Ultra performant API Gateway with middleware.
 | 
					 | 
				
			||||||
- [picfit](https://github.com/thoas/picfit): An image resizing server.
 | 
					 | 
				
			||||||
- [dkron](https://github.com/distribworks/dkron): Distributed, fault tolerant job scheduling system.
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
## Contributing
 | 
					- **[gorush](https://github.com/appleboy/gorush)** - High-performance push notification server
 | 
				
			||||||
 | 
					- **[fnproject](https://github.com/fnproject/fn)** - Container-native, serverless platform
 | 
				
			||||||
 | 
					- **[photoprism](https://github.com/photoprism/photoprism)** - AI-powered personal photo management
 | 
				
			||||||
 | 
					- **[lura](https://github.com/luraproject/lura)** - Ultra-performant API Gateway framework
 | 
				
			||||||
 | 
					- **[picfit](https://github.com/thoas/picfit)** - Real-time image processing server
 | 
				
			||||||
 | 
					- **[dkron](https://github.com/distribworks/dkron)** - Distributed job scheduling system
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Gin is the work of hundreds of contributors. We appreciate your help!
 | 
					## 🤝 Contributing
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Please see [CONTRIBUTING.md](CONTRIBUTING.md) for details on submitting patches and the contribution workflow.
 | 
					Gin is the work of hundreds of contributors from around the world. We welcome and appreciate your contributions!
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### How to Contribute
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- 🐛 **Report bugs** - Help us identify and fix issues
 | 
				
			||||||
 | 
					- 💡 **Suggest features** - Share your ideas for improvements
 | 
				
			||||||
 | 
					- 📝 **Improve documentation** - Help make our docs clearer
 | 
				
			||||||
 | 
					- 🔧 **Submit code** - Fix bugs or implement new features
 | 
				
			||||||
 | 
					- 🧪 **Write tests** - Improve our test coverage
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### Getting Started with Contributing
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					1. Check out our [CONTRIBUTING.md](CONTRIBUTING.md) for detailed guidelines
 | 
				
			||||||
 | 
					2. Join our community discussions and ask questions
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					**All contributions are valued and help make Gin better for everyone!**
 | 
				
			||||||
 | 
				
			|||||||
@ -87,7 +87,7 @@ func BenchmarkOneRouteString(B *testing.B) {
 | 
				
			|||||||
	runRequest(B, router, http.MethodGet, "/text")
 | 
						runRequest(B, router, http.MethodGet, "/text")
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func BenchmarkManyRoutesFist(B *testing.B) {
 | 
					func BenchmarkManyRoutesFirst(B *testing.B) {
 | 
				
			||||||
	router := New()
 | 
						router := New()
 | 
				
			||||||
	router.Any("/ping", func(c *Context) {})
 | 
						router.Any("/ping", func(c *Context) {})
 | 
				
			||||||
	runRequest(B, router, http.MethodGet, "/ping")
 | 
						runRequest(B, router, http.MethodGet, "/ping")
 | 
				
			||||||
 | 
				
			|||||||
@ -7,6 +7,7 @@ package binding
 | 
				
			|||||||
import (
 | 
					import (
 | 
				
			||||||
	"errors"
 | 
						"errors"
 | 
				
			||||||
	"fmt"
 | 
						"fmt"
 | 
				
			||||||
 | 
						"maps"
 | 
				
			||||||
	"mime/multipart"
 | 
						"mime/multipart"
 | 
				
			||||||
	"reflect"
 | 
						"reflect"
 | 
				
			||||||
	"strconv"
 | 
						"strconv"
 | 
				
			||||||
@ -230,9 +231,12 @@ func setByForm(value reflect.Value, field reflect.StructField, form map[string][
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	switch value.Kind() {
 | 
						switch value.Kind() {
 | 
				
			||||||
	case reflect.Slice:
 | 
						case reflect.Slice:
 | 
				
			||||||
		if !ok {
 | 
							if len(vs) == 0 {
 | 
				
			||||||
			vs = []string{opt.defaultValue}
 | 
								if !opt.isDefaultExists {
 | 
				
			||||||
 | 
									return false, nil
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								vs = []string{opt.defaultValue}
 | 
				
			||||||
			// pre-process the default value for multi if present
 | 
								// pre-process the default value for multi if present
 | 
				
			||||||
			cfTag := field.Tag.Get("collection_format")
 | 
								cfTag := field.Tag.Get("collection_format")
 | 
				
			||||||
			if cfTag == "" || cfTag == "multi" {
 | 
								if cfTag == "" || cfTag == "multi" {
 | 
				
			||||||
@ -250,9 +254,12 @@ func setByForm(value reflect.Value, field reflect.StructField, form map[string][
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
		return true, setSlice(vs, value, field)
 | 
							return true, setSlice(vs, value, field)
 | 
				
			||||||
	case reflect.Array:
 | 
						case reflect.Array:
 | 
				
			||||||
		if !ok {
 | 
							if len(vs) == 0 {
 | 
				
			||||||
			vs = []string{opt.defaultValue}
 | 
								if !opt.isDefaultExists {
 | 
				
			||||||
 | 
									return false, nil
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								vs = []string{opt.defaultValue}
 | 
				
			||||||
			// pre-process the default value for multi if present
 | 
								// pre-process the default value for multi if present
 | 
				
			||||||
			cfTag := field.Tag.Get("collection_format")
 | 
								cfTag := field.Tag.Get("collection_format")
 | 
				
			||||||
			if cfTag == "" || cfTag == "multi" {
 | 
								if cfTag == "" || cfTag == "multi" {
 | 
				
			||||||
@ -489,9 +496,7 @@ func setFormMap(ptr any, form map[string][]string) error {
 | 
				
			|||||||
		if !ok {
 | 
							if !ok {
 | 
				
			||||||
			return ErrConvertMapStringSlice
 | 
								return ErrConvertMapStringSlice
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
		for k, v := range form {
 | 
							maps.Copy(ptrMap, form)
 | 
				
			||||||
			ptrMap[k] = v
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
		return nil
 | 
							return nil
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
				
			|||||||
@ -635,3 +635,83 @@ func TestMappingCustomArrayForm(t *testing.T) {
 | 
				
			|||||||
	expected, _ := convertTo(val)
 | 
						expected, _ := convertTo(val)
 | 
				
			||||||
	assert.Equal(t, expected, s.FileData)
 | 
						assert.Equal(t, expected, s.FileData)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func TestMappingEmptyValues(t *testing.T) {
 | 
				
			||||||
 | 
						t.Run("slice with default", func(t *testing.T) {
 | 
				
			||||||
 | 
							var s struct {
 | 
				
			||||||
 | 
								Slice []int `form:"slice,default=5"`
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// field not present
 | 
				
			||||||
 | 
							err := mappingByPtr(&s, formSource{}, "form")
 | 
				
			||||||
 | 
							require.NoError(t, err)
 | 
				
			||||||
 | 
							assert.Equal(t, []int{5}, s.Slice)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// field present but empty
 | 
				
			||||||
 | 
							err = mappingByPtr(&s, formSource{"slice": {}}, "form")
 | 
				
			||||||
 | 
							require.NoError(t, err)
 | 
				
			||||||
 | 
							assert.Equal(t, []int{5}, s.Slice)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// field present with values
 | 
				
			||||||
 | 
							err = mappingByPtr(&s, formSource{"slice": {"1", "2", "3"}}, "form")
 | 
				
			||||||
 | 
							require.NoError(t, err)
 | 
				
			||||||
 | 
							assert.Equal(t, []int{1, 2, 3}, s.Slice)
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						t.Run("array with default", func(t *testing.T) {
 | 
				
			||||||
 | 
							var s struct {
 | 
				
			||||||
 | 
								Array [1]int `form:"array,default=5"`
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// field not present
 | 
				
			||||||
 | 
							err := mappingByPtr(&s, formSource{}, "form")
 | 
				
			||||||
 | 
							require.NoError(t, err)
 | 
				
			||||||
 | 
							assert.Equal(t, [1]int{5}, s.Array)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// field present but empty
 | 
				
			||||||
 | 
							err = mappingByPtr(&s, formSource{"array": {}}, "form")
 | 
				
			||||||
 | 
							require.NoError(t, err)
 | 
				
			||||||
 | 
							assert.Equal(t, [1]int{5}, s.Array)
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						t.Run("slice without default", func(t *testing.T) {
 | 
				
			||||||
 | 
							var s struct {
 | 
				
			||||||
 | 
								Slice []int `form:"slice"`
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// field present but empty
 | 
				
			||||||
 | 
							err := mappingByPtr(&s, formSource{"slice": {}}, "form")
 | 
				
			||||||
 | 
							require.NoError(t, err)
 | 
				
			||||||
 | 
							assert.Equal(t, []int(nil), s.Slice)
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						t.Run("array without default", func(t *testing.T) {
 | 
				
			||||||
 | 
							var s struct {
 | 
				
			||||||
 | 
								Array [1]int `form:"array"`
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// field present but empty
 | 
				
			||||||
 | 
							err := mappingByPtr(&s, formSource{"array": {}}, "form")
 | 
				
			||||||
 | 
							require.NoError(t, err)
 | 
				
			||||||
 | 
							assert.Equal(t, [1]int{0}, s.Array)
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						t.Run("slice with collection format", func(t *testing.T) {
 | 
				
			||||||
 | 
							var s struct {
 | 
				
			||||||
 | 
								SliceMulti []int `form:"slice_multi,default=1;2;3" collection_format:"multi"`
 | 
				
			||||||
 | 
								SliceCsv   []int `form:"slice_csv,default=1;2;3" collection_format:"csv"`
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// field not present
 | 
				
			||||||
 | 
							err := mappingByPtr(&s, formSource{}, "form")
 | 
				
			||||||
 | 
							require.NoError(t, err)
 | 
				
			||||||
 | 
							assert.Equal(t, []int{1, 2, 3}, s.SliceMulti)
 | 
				
			||||||
 | 
							assert.Equal(t, []int{1, 2, 3}, s.SliceCsv)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// field present but empty
 | 
				
			||||||
 | 
							err = mappingByPtr(&s, formSource{"slice_multi": {}, "slice_csv": {}}, "form")
 | 
				
			||||||
 | 
							require.NoError(t, err)
 | 
				
			||||||
 | 
							assert.Equal(t, []int{1, 2, 3}, s.SliceMulti)
 | 
				
			||||||
 | 
							assert.Equal(t, []int{1, 2, 3}, s.SliceCsv)
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										82
									
								
								context.go
									
									
									
									
									
								
							
							
						
						
									
										82
									
								
								context.go
									
									
									
									
									
								
							@ -10,6 +10,7 @@ import (
 | 
				
			|||||||
	"io"
 | 
						"io"
 | 
				
			||||||
	"io/fs"
 | 
						"io/fs"
 | 
				
			||||||
	"log"
 | 
						"log"
 | 
				
			||||||
 | 
						"maps"
 | 
				
			||||||
	"math"
 | 
						"math"
 | 
				
			||||||
	"mime/multipart"
 | 
						"mime/multipart"
 | 
				
			||||||
	"net"
 | 
						"net"
 | 
				
			||||||
@ -130,11 +131,8 @@ func (c *Context) Copy() *Context {
 | 
				
			|||||||
	cp.fullPath = c.fullPath
 | 
						cp.fullPath = c.fullPath
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	cKeys := c.Keys
 | 
						cKeys := c.Keys
 | 
				
			||||||
	cp.Keys = make(map[any]any, len(cKeys))
 | 
					 | 
				
			||||||
	c.mu.RLock()
 | 
						c.mu.RLock()
 | 
				
			||||||
	for k, v := range cKeys {
 | 
						cp.Keys = maps.Clone(cKeys)
 | 
				
			||||||
		cp.Keys[k] = v
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	c.mu.RUnlock()
 | 
						c.mu.RUnlock()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	cParams := c.Params
 | 
						cParams := c.Params
 | 
				
			||||||
@ -272,7 +270,7 @@ func (c *Context) Error(err error) *Error {
 | 
				
			|||||||
/************************************/
 | 
					/************************************/
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Set is used to store a new key/value pair exclusively for this context.
 | 
					// Set is used to store a new key/value pair exclusively for this context.
 | 
				
			||||||
// It also lazy initializes  c.Keys if it was not used previously.
 | 
					// It also lazy initializes c.Keys if it was not used previously.
 | 
				
			||||||
func (c *Context) Set(key any, value any) {
 | 
					func (c *Context) Set(key any, value any) {
 | 
				
			||||||
	c.mu.Lock()
 | 
						c.mu.Lock()
 | 
				
			||||||
	defer c.mu.Unlock()
 | 
						defer c.mu.Unlock()
 | 
				
			||||||
@ -308,165 +306,175 @@ func getTyped[T any](c *Context, key any) (res T) {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// GetString returns the value associated with the key as a string.
 | 
					// GetString returns the value associated with the key as a string.
 | 
				
			||||||
func (c *Context) GetString(key any) (s string) {
 | 
					func (c *Context) GetString(key any) string {
 | 
				
			||||||
	return getTyped[string](c, key)
 | 
						return getTyped[string](c, key)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// GetBool returns the value associated with the key as a boolean.
 | 
					// GetBool returns the value associated with the key as a boolean.
 | 
				
			||||||
func (c *Context) GetBool(key any) (b bool) {
 | 
					func (c *Context) GetBool(key any) bool {
 | 
				
			||||||
	return getTyped[bool](c, key)
 | 
						return getTyped[bool](c, key)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// GetInt returns the value associated with the key as an integer.
 | 
					// GetInt returns the value associated with the key as an integer.
 | 
				
			||||||
func (c *Context) GetInt(key any) (i int) {
 | 
					func (c *Context) GetInt(key any) int {
 | 
				
			||||||
	return getTyped[int](c, key)
 | 
						return getTyped[int](c, key)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// GetInt8 returns the value associated with the key as an integer 8.
 | 
					// GetInt8 returns the value associated with the key as an integer 8.
 | 
				
			||||||
func (c *Context) GetInt8(key any) (i8 int8) {
 | 
					func (c *Context) GetInt8(key any) int8 {
 | 
				
			||||||
	return getTyped[int8](c, key)
 | 
						return getTyped[int8](c, key)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// GetInt16 returns the value associated with the key as an integer 16.
 | 
					// GetInt16 returns the value associated with the key as an integer 16.
 | 
				
			||||||
func (c *Context) GetInt16(key any) (i16 int16) {
 | 
					func (c *Context) GetInt16(key any) int16 {
 | 
				
			||||||
	return getTyped[int16](c, key)
 | 
						return getTyped[int16](c, key)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// GetInt32 returns the value associated with the key as an integer 32.
 | 
					// GetInt32 returns the value associated with the key as an integer 32.
 | 
				
			||||||
func (c *Context) GetInt32(key any) (i32 int32) {
 | 
					func (c *Context) GetInt32(key any) int32 {
 | 
				
			||||||
	return getTyped[int32](c, key)
 | 
						return getTyped[int32](c, key)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// GetInt64 returns the value associated with the key as an integer 64.
 | 
					// GetInt64 returns the value associated with the key as an integer 64.
 | 
				
			||||||
func (c *Context) GetInt64(key any) (i64 int64) {
 | 
					func (c *Context) GetInt64(key any) int64 {
 | 
				
			||||||
	return getTyped[int64](c, key)
 | 
						return getTyped[int64](c, key)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// GetUint returns the value associated with the key as an unsigned integer.
 | 
					// GetUint returns the value associated with the key as an unsigned integer.
 | 
				
			||||||
func (c *Context) GetUint(key any) (ui uint) {
 | 
					func (c *Context) GetUint(key any) uint {
 | 
				
			||||||
	return getTyped[uint](c, key)
 | 
						return getTyped[uint](c, key)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// GetUint8 returns the value associated with the key as an unsigned integer 8.
 | 
					// GetUint8 returns the value associated with the key as an unsigned integer 8.
 | 
				
			||||||
func (c *Context) GetUint8(key any) (ui8 uint8) {
 | 
					func (c *Context) GetUint8(key any) uint8 {
 | 
				
			||||||
	return getTyped[uint8](c, key)
 | 
						return getTyped[uint8](c, key)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// GetUint16 returns the value associated with the key as an unsigned integer 16.
 | 
					// GetUint16 returns the value associated with the key as an unsigned integer 16.
 | 
				
			||||||
func (c *Context) GetUint16(key any) (ui16 uint16) {
 | 
					func (c *Context) GetUint16(key any) uint16 {
 | 
				
			||||||
	return getTyped[uint16](c, key)
 | 
						return getTyped[uint16](c, key)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// GetUint32 returns the value associated with the key as an unsigned integer 32.
 | 
					// GetUint32 returns the value associated with the key as an unsigned integer 32.
 | 
				
			||||||
func (c *Context) GetUint32(key any) (ui32 uint32) {
 | 
					func (c *Context) GetUint32(key any) uint32 {
 | 
				
			||||||
	return getTyped[uint32](c, key)
 | 
						return getTyped[uint32](c, key)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// GetUint64 returns the value associated with the key as an unsigned integer 64.
 | 
					// GetUint64 returns the value associated with the key as an unsigned integer 64.
 | 
				
			||||||
func (c *Context) GetUint64(key any) (ui64 uint64) {
 | 
					func (c *Context) GetUint64(key any) uint64 {
 | 
				
			||||||
	return getTyped[uint64](c, key)
 | 
						return getTyped[uint64](c, key)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// GetFloat32 returns the value associated with the key as a float32.
 | 
					// GetFloat32 returns the value associated with the key as a float32.
 | 
				
			||||||
func (c *Context) GetFloat32(key any) (f32 float32) {
 | 
					func (c *Context) GetFloat32(key any) float32 {
 | 
				
			||||||
	return getTyped[float32](c, key)
 | 
						return getTyped[float32](c, key)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// GetFloat64 returns the value associated with the key as a float64.
 | 
					// GetFloat64 returns the value associated with the key as a float64.
 | 
				
			||||||
func (c *Context) GetFloat64(key any) (f64 float64) {
 | 
					func (c *Context) GetFloat64(key any) float64 {
 | 
				
			||||||
	return getTyped[float64](c, key)
 | 
						return getTyped[float64](c, key)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// GetTime returns the value associated with the key as time.
 | 
					// GetTime returns the value associated with the key as time.
 | 
				
			||||||
func (c *Context) GetTime(key any) (t time.Time) {
 | 
					func (c *Context) GetTime(key any) time.Time {
 | 
				
			||||||
	return getTyped[time.Time](c, key)
 | 
						return getTyped[time.Time](c, key)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// GetDuration returns the value associated with the key as a duration.
 | 
					// GetDuration returns the value associated with the key as a duration.
 | 
				
			||||||
func (c *Context) GetDuration(key any) (d time.Duration) {
 | 
					func (c *Context) GetDuration(key any) time.Duration {
 | 
				
			||||||
	return getTyped[time.Duration](c, key)
 | 
						return getTyped[time.Duration](c, key)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// GetIntSlice returns the value associated with the key as a slice of integers.
 | 
					// GetIntSlice returns the value associated with the key as a slice of integers.
 | 
				
			||||||
func (c *Context) GetIntSlice(key any) (is []int) {
 | 
					func (c *Context) GetIntSlice(key any) []int {
 | 
				
			||||||
	return getTyped[[]int](c, key)
 | 
						return getTyped[[]int](c, key)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// GetInt8Slice returns the value associated with the key as a slice of int8 integers.
 | 
					// GetInt8Slice returns the value associated with the key as a slice of int8 integers.
 | 
				
			||||||
func (c *Context) GetInt8Slice(key any) (i8s []int8) {
 | 
					func (c *Context) GetInt8Slice(key any) []int8 {
 | 
				
			||||||
	return getTyped[[]int8](c, key)
 | 
						return getTyped[[]int8](c, key)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// GetInt16Slice returns the value associated with the key as a slice of int16 integers.
 | 
					// GetInt16Slice returns the value associated with the key as a slice of int16 integers.
 | 
				
			||||||
func (c *Context) GetInt16Slice(key any) (i16s []int16) {
 | 
					func (c *Context) GetInt16Slice(key any) []int16 {
 | 
				
			||||||
	return getTyped[[]int16](c, key)
 | 
						return getTyped[[]int16](c, key)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// GetInt32Slice returns the value associated with the key as a slice of int32 integers.
 | 
					// GetInt32Slice returns the value associated with the key as a slice of int32 integers.
 | 
				
			||||||
func (c *Context) GetInt32Slice(key any) (i32s []int32) {
 | 
					func (c *Context) GetInt32Slice(key any) []int32 {
 | 
				
			||||||
	return getTyped[[]int32](c, key)
 | 
						return getTyped[[]int32](c, key)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// GetInt64Slice returns the value associated with the key as a slice of int64 integers.
 | 
					// GetInt64Slice returns the value associated with the key as a slice of int64 integers.
 | 
				
			||||||
func (c *Context) GetInt64Slice(key any) (i64s []int64) {
 | 
					func (c *Context) GetInt64Slice(key any) []int64 {
 | 
				
			||||||
	return getTyped[[]int64](c, key)
 | 
						return getTyped[[]int64](c, key)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// GetUintSlice returns the value associated with the key as a slice of unsigned integers.
 | 
					// GetUintSlice returns the value associated with the key as a slice of unsigned integers.
 | 
				
			||||||
func (c *Context) GetUintSlice(key any) (uis []uint) {
 | 
					func (c *Context) GetUintSlice(key any) []uint {
 | 
				
			||||||
	return getTyped[[]uint](c, key)
 | 
						return getTyped[[]uint](c, key)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// GetUint8Slice returns the value associated with the key as a slice of uint8 integers.
 | 
					// GetUint8Slice returns the value associated with the key as a slice of uint8 integers.
 | 
				
			||||||
func (c *Context) GetUint8Slice(key any) (ui8s []uint8) {
 | 
					func (c *Context) GetUint8Slice(key any) []uint8 {
 | 
				
			||||||
	return getTyped[[]uint8](c, key)
 | 
						return getTyped[[]uint8](c, key)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// GetUint16Slice returns the value associated with the key as a slice of uint16 integers.
 | 
					// GetUint16Slice returns the value associated with the key as a slice of uint16 integers.
 | 
				
			||||||
func (c *Context) GetUint16Slice(key any) (ui16s []uint16) {
 | 
					func (c *Context) GetUint16Slice(key any) []uint16 {
 | 
				
			||||||
	return getTyped[[]uint16](c, key)
 | 
						return getTyped[[]uint16](c, key)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// GetUint32Slice returns the value associated with the key as a slice of uint32 integers.
 | 
					// GetUint32Slice returns the value associated with the key as a slice of uint32 integers.
 | 
				
			||||||
func (c *Context) GetUint32Slice(key any) (ui32s []uint32) {
 | 
					func (c *Context) GetUint32Slice(key any) []uint32 {
 | 
				
			||||||
	return getTyped[[]uint32](c, key)
 | 
						return getTyped[[]uint32](c, key)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// GetUint64Slice returns the value associated with the key as a slice of uint64 integers.
 | 
					// GetUint64Slice returns the value associated with the key as a slice of uint64 integers.
 | 
				
			||||||
func (c *Context) GetUint64Slice(key any) (ui64s []uint64) {
 | 
					func (c *Context) GetUint64Slice(key any) []uint64 {
 | 
				
			||||||
	return getTyped[[]uint64](c, key)
 | 
						return getTyped[[]uint64](c, key)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// GetFloat32Slice returns the value associated with the key as a slice of float32 numbers.
 | 
					// GetFloat32Slice returns the value associated with the key as a slice of float32 numbers.
 | 
				
			||||||
func (c *Context) GetFloat32Slice(key any) (f32s []float32) {
 | 
					func (c *Context) GetFloat32Slice(key any) []float32 {
 | 
				
			||||||
	return getTyped[[]float32](c, key)
 | 
						return getTyped[[]float32](c, key)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// GetFloat64Slice returns the value associated with the key as a slice of float64 numbers.
 | 
					// GetFloat64Slice returns the value associated with the key as a slice of float64 numbers.
 | 
				
			||||||
func (c *Context) GetFloat64Slice(key any) (f64s []float64) {
 | 
					func (c *Context) GetFloat64Slice(key any) []float64 {
 | 
				
			||||||
	return getTyped[[]float64](c, key)
 | 
						return getTyped[[]float64](c, key)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// GetStringSlice returns the value associated with the key as a slice of strings.
 | 
					// GetStringSlice returns the value associated with the key as a slice of strings.
 | 
				
			||||||
func (c *Context) GetStringSlice(key any) (ss []string) {
 | 
					func (c *Context) GetStringSlice(key any) []string {
 | 
				
			||||||
	return getTyped[[]string](c, key)
 | 
						return getTyped[[]string](c, key)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// GetStringMap returns the value associated with the key as a map of interfaces.
 | 
					// GetStringMap returns the value associated with the key as a map of interfaces.
 | 
				
			||||||
func (c *Context) GetStringMap(key any) (sm map[string]any) {
 | 
					func (c *Context) GetStringMap(key any) map[string]any {
 | 
				
			||||||
	return getTyped[map[string]any](c, key)
 | 
						return getTyped[map[string]any](c, key)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// GetStringMapString returns the value associated with the key as a map of strings.
 | 
					// GetStringMapString returns the value associated with the key as a map of strings.
 | 
				
			||||||
func (c *Context) GetStringMapString(key any) (sms map[string]string) {
 | 
					func (c *Context) GetStringMapString(key any) map[string]string {
 | 
				
			||||||
	return getTyped[map[string]string](c, key)
 | 
						return getTyped[map[string]string](c, key)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// GetStringMapStringSlice returns the value associated with the key as a map to a slice of strings.
 | 
					// GetStringMapStringSlice returns the value associated with the key as a map to a slice of strings.
 | 
				
			||||||
func (c *Context) GetStringMapStringSlice(key any) (smss map[string][]string) {
 | 
					func (c *Context) GetStringMapStringSlice(key any) map[string][]string {
 | 
				
			||||||
	return getTyped[map[string][]string](c, key)
 | 
						return getTyped[map[string][]string](c, key)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Delete deletes the key from the Context's Key map, if it exists.
 | 
				
			||||||
 | 
					// This operation is safe to be used by concurrent go-routines
 | 
				
			||||||
 | 
					func (c *Context) Delete(key any) {
 | 
				
			||||||
 | 
						c.mu.Lock()
 | 
				
			||||||
 | 
						defer c.mu.Unlock()
 | 
				
			||||||
 | 
						if c.Keys != nil {
 | 
				
			||||||
 | 
							delete(c.Keys, key)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/************************************/
 | 
					/************************************/
 | 
				
			||||||
/************ INPUT DATA ************/
 | 
					/************ INPUT DATA ************/
 | 
				
			||||||
/************************************/
 | 
					/************************************/
 | 
				
			||||||
 | 
				
			|||||||
@ -404,6 +404,19 @@ func TestContextSetGetBool(t *testing.T) {
 | 
				
			|||||||
	assert.True(t, c.GetBool("bool"))
 | 
						assert.True(t, c.GetBool("bool"))
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func TestSetGetDelete(t *testing.T) {
 | 
				
			||||||
 | 
						c, _ := CreateTestContext(httptest.NewRecorder())
 | 
				
			||||||
 | 
						key := "example-key"
 | 
				
			||||||
 | 
						value := "example-value"
 | 
				
			||||||
 | 
						c.Set(key, value)
 | 
				
			||||||
 | 
						val, exists := c.Get(key)
 | 
				
			||||||
 | 
						assert.True(t, exists)
 | 
				
			||||||
 | 
						assert.Equal(t, val, value)
 | 
				
			||||||
 | 
						c.Delete(key)
 | 
				
			||||||
 | 
						_, exists = c.Get(key)
 | 
				
			||||||
 | 
						assert.False(t, exists)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func TestContextGetInt(t *testing.T) {
 | 
					func TestContextGetInt(t *testing.T) {
 | 
				
			||||||
	c, _ := CreateTestContext(httptest.NewRecorder())
 | 
						c, _ := CreateTestContext(httptest.NewRecorder())
 | 
				
			||||||
	c.Set("int", 1)
 | 
						c.Set("int", 1)
 | 
				
			||||||
@ -1233,7 +1246,7 @@ func TestContextRenderNoContentHTML(t *testing.T) {
 | 
				
			|||||||
	assert.Equal(t, "text/html; charset=utf-8", w.Header().Get("Content-Type"))
 | 
						assert.Equal(t, "text/html; charset=utf-8", w.Header().Get("Content-Type"))
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// TestContextXML tests that the response is serialized as XML
 | 
					// TestContextRenderXML tests that the response is serialized as XML
 | 
				
			||||||
// and Content-Type is set to application/xml
 | 
					// and Content-Type is set to application/xml
 | 
				
			||||||
func TestContextRenderXML(t *testing.T) {
 | 
					func TestContextRenderXML(t *testing.T) {
 | 
				
			||||||
	w := httptest.NewRecorder()
 | 
						w := httptest.NewRecorder()
 | 
				
			||||||
@ -3320,7 +3333,7 @@ func TestContextSetCookieData(t *testing.T) {
 | 
				
			|||||||
	assert.Contains(t, setCookie, "Max-Age=1")
 | 
						assert.Contains(t, setCookie, "Max-Age=1")
 | 
				
			||||||
	assert.Contains(t, setCookie, "HttpOnly")
 | 
						assert.Contains(t, setCookie, "HttpOnly")
 | 
				
			||||||
	assert.Contains(t, setCookie, "Secure")
 | 
						assert.Contains(t, setCookie, "Secure")
 | 
				
			||||||
	// SameSite=Lax might be omitted in Go 1.23+ as it's the default
 | 
						// SameSite=Lax might be omitted in Go 1.24+ as it's the default
 | 
				
			||||||
	// assert.Contains(t, setCookie, "SameSite=Lax")
 | 
						// assert.Contains(t, setCookie, "SameSite=Lax")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// Test that when Path is empty, "/" is automatically set
 | 
						// Test that when Path is empty, "/" is automatically set
 | 
				
			||||||
@ -3341,7 +3354,7 @@ func TestContextSetCookieData(t *testing.T) {
 | 
				
			|||||||
	assert.Contains(t, setCookie, "Max-Age=1")
 | 
						assert.Contains(t, setCookie, "Max-Age=1")
 | 
				
			||||||
	assert.Contains(t, setCookie, "HttpOnly")
 | 
						assert.Contains(t, setCookie, "HttpOnly")
 | 
				
			||||||
	assert.Contains(t, setCookie, "Secure")
 | 
						assert.Contains(t, setCookie, "Secure")
 | 
				
			||||||
	// SameSite=Lax might be omitted in Go 1.23+ as it's the default
 | 
						// SameSite=Lax might be omitted in Go 1.24+ as it's the default
 | 
				
			||||||
	// assert.Contains(t, setCookie, "SameSite=Lax")
 | 
						// assert.Contains(t, setCookie, "SameSite=Lax")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// Test additional cookie attributes (Expires)
 | 
						// Test additional cookie attributes (Expires)
 | 
				
			||||||
@ -3364,7 +3377,7 @@ func TestContextSetCookieData(t *testing.T) {
 | 
				
			|||||||
	assert.Contains(t, setCookie, "Domain=localhost")
 | 
						assert.Contains(t, setCookie, "Domain=localhost")
 | 
				
			||||||
	assert.Contains(t, setCookie, "HttpOnly")
 | 
						assert.Contains(t, setCookie, "HttpOnly")
 | 
				
			||||||
	assert.Contains(t, setCookie, "Secure")
 | 
						assert.Contains(t, setCookie, "Secure")
 | 
				
			||||||
	// SameSite=Lax might be omitted in Go 1.23+ as it's the default
 | 
						// SameSite=Lax might be omitted in Go 1.24+ as it's the default
 | 
				
			||||||
	// assert.Contains(t, setCookie, "SameSite=Lax")
 | 
						// assert.Contains(t, setCookie, "SameSite=Lax")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// Test for Partitioned attribute (Go 1.18+)
 | 
						// Test for Partitioned attribute (Go 1.18+)
 | 
				
			||||||
@ -3384,7 +3397,7 @@ func TestContextSetCookieData(t *testing.T) {
 | 
				
			|||||||
	assert.Contains(t, setCookie, "Domain=localhost")
 | 
						assert.Contains(t, setCookie, "Domain=localhost")
 | 
				
			||||||
	assert.Contains(t, setCookie, "HttpOnly")
 | 
						assert.Contains(t, setCookie, "HttpOnly")
 | 
				
			||||||
	assert.Contains(t, setCookie, "Secure")
 | 
						assert.Contains(t, setCookie, "Secure")
 | 
				
			||||||
	// SameSite=Lax might be omitted in Go 1.23+ as it's the default
 | 
						// SameSite=Lax might be omitted in Go 1.24+ as it's the default
 | 
				
			||||||
	// assert.Contains(t, setCookie, "SameSite=Lax")
 | 
						// assert.Contains(t, setCookie, "SameSite=Lax")
 | 
				
			||||||
	// Not testing for Partitioned attribute as it may not be supported in all Go versions
 | 
						// Not testing for Partitioned attribute as it may not be supported in all Go versions
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										2
									
								
								debug.go
									
									
									
									
									
								
							
							
						
						
									
										2
									
								
								debug.go
									
									
									
									
									
								
							@ -78,7 +78,7 @@ func getMinVer(v string) (uint64, error) {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
func debugPrintWARNINGDefault() {
 | 
					func debugPrintWARNINGDefault() {
 | 
				
			||||||
	if v, e := getMinVer(runtime.Version()); e == nil && v < ginSupportMinGoVer {
 | 
						if v, e := getMinVer(runtime.Version()); e == nil && v < ginSupportMinGoVer {
 | 
				
			||||||
		debugPrint(`[WARNING] Now Gin requires Go 1.23+.
 | 
							debugPrint(`[WARNING] Now Gin requires Go 1.24+.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
`)
 | 
					`)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
				
			|||||||
@ -106,7 +106,7 @@ func TestDebugPrintWARNINGDefault(t *testing.T) {
 | 
				
			|||||||
	})
 | 
						})
 | 
				
			||||||
	m, e := getMinVer(runtime.Version())
 | 
						m, e := getMinVer(runtime.Version())
 | 
				
			||||||
	if e == nil && m < ginSupportMinGoVer {
 | 
						if e == nil && m < ginSupportMinGoVer {
 | 
				
			||||||
		assert.Equal(t, "[GIN-debug] [WARNING] Now Gin requires Go 1.23+.\n\n[GIN-debug] [WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached.\n\n", re)
 | 
							assert.Equal(t, "[GIN-debug] [WARNING] Now Gin requires Go 1.24+.\n\n[GIN-debug] [WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached.\n\n", re)
 | 
				
			||||||
	} else {
 | 
						} else {
 | 
				
			||||||
		assert.Equal(t, "[GIN-debug] [WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached.\n\n", re)
 | 
							assert.Equal(t, "[GIN-debug] [WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached.\n\n", re)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										14
									
								
								ginS/gins.go
									
									
									
									
									
								
							
							
						
						
									
										14
									
								
								ginS/gins.go
									
									
									
									
									
								
							@ -12,17 +12,9 @@ import (
 | 
				
			|||||||
	"github.com/gin-gonic/gin"
 | 
						"github.com/gin-gonic/gin"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
var (
 | 
					var engine = sync.OnceValue(func() *gin.Engine {
 | 
				
			||||||
	once           sync.Once
 | 
						return gin.Default()
 | 
				
			||||||
	internalEngine *gin.Engine
 | 
					})
 | 
				
			||||||
)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
func engine() *gin.Engine {
 | 
					 | 
				
			||||||
	once.Do(func() {
 | 
					 | 
				
			||||||
		internalEngine = gin.Default()
 | 
					 | 
				
			||||||
	})
 | 
					 | 
				
			||||||
	return internalEngine
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
// LoadHTMLGlob is a wrapper for Engine.LoadHTMLGlob.
 | 
					// LoadHTMLGlob is a wrapper for Engine.LoadHTMLGlob.
 | 
				
			||||||
func LoadHTMLGlob(pattern string) {
 | 
					func LoadHTMLGlob(pattern string) {
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										32
									
								
								go.mod
									
									
									
									
									
								
							
							
						
						
									
										32
									
								
								go.mod
									
									
									
									
									
								
							@ -1,29 +1,30 @@
 | 
				
			|||||||
module github.com/gin-gonic/gin
 | 
					module github.com/gin-gonic/gin
 | 
				
			||||||
 | 
					
 | 
				
			||||||
go 1.23.0
 | 
					go 1.24.0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
require (
 | 
					require (
 | 
				
			||||||
	github.com/bytedance/sonic v1.14.0
 | 
						github.com/bytedance/sonic v1.14.2
 | 
				
			||||||
	github.com/gin-contrib/sse v1.1.0
 | 
						github.com/gin-contrib/sse v1.1.0
 | 
				
			||||||
	github.com/go-playground/validator/v10 v10.27.0
 | 
						github.com/go-playground/validator/v10 v10.28.0
 | 
				
			||||||
	github.com/goccy/go-json v0.10.2
 | 
						github.com/goccy/go-json v0.10.2
 | 
				
			||||||
	github.com/goccy/go-yaml v1.18.0
 | 
						github.com/goccy/go-yaml v1.18.0
 | 
				
			||||||
	github.com/json-iterator/go v1.1.12
 | 
						github.com/json-iterator/go v1.1.12
 | 
				
			||||||
	github.com/mattn/go-isatty v0.0.20
 | 
						github.com/mattn/go-isatty v0.0.20
 | 
				
			||||||
	github.com/modern-go/reflect2 v1.0.2
 | 
						github.com/modern-go/reflect2 v1.0.2
 | 
				
			||||||
	github.com/pelletier/go-toml/v2 v2.2.4
 | 
						github.com/pelletier/go-toml/v2 v2.2.4
 | 
				
			||||||
	github.com/quic-go/quic-go v0.54.0
 | 
						github.com/quic-go/quic-go v0.55.0
 | 
				
			||||||
	github.com/stretchr/testify v1.11.1
 | 
						github.com/stretchr/testify v1.11.1
 | 
				
			||||||
	github.com/ugorji/go/codec v1.3.0
 | 
						github.com/ugorji/go/codec v1.3.1
 | 
				
			||||||
	golang.org/x/net v0.42.0
 | 
						golang.org/x/net v0.46.0
 | 
				
			||||||
	google.golang.org/protobuf v1.36.9
 | 
						google.golang.org/protobuf v1.36.10
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
require (
 | 
					require (
 | 
				
			||||||
	github.com/bytedance/sonic/loader v0.3.0 // indirect
 | 
						github.com/bytedance/gopkg v0.1.3 // indirect
 | 
				
			||||||
 | 
						github.com/bytedance/sonic/loader v0.4.0 // indirect
 | 
				
			||||||
	github.com/cloudwego/base64x v0.1.6 // indirect
 | 
						github.com/cloudwego/base64x v0.1.6 // indirect
 | 
				
			||||||
	github.com/davecgh/go-spew v1.1.1 // indirect
 | 
						github.com/davecgh/go-spew v1.1.1 // indirect
 | 
				
			||||||
	github.com/gabriel-vasile/mimetype v1.4.8 // indirect
 | 
						github.com/gabriel-vasile/mimetype v1.4.10 // indirect
 | 
				
			||||||
	github.com/go-playground/locales v0.14.1 // indirect
 | 
						github.com/go-playground/locales v0.14.1 // indirect
 | 
				
			||||||
	github.com/go-playground/universal-translator v0.18.1 // indirect
 | 
						github.com/go-playground/universal-translator v0.18.1 // indirect
 | 
				
			||||||
	github.com/klauspost/cpuid/v2 v2.3.0 // indirect
 | 
						github.com/klauspost/cpuid/v2 v2.3.0 // indirect
 | 
				
			||||||
@ -32,13 +33,12 @@ require (
 | 
				
			|||||||
	github.com/pmezard/go-difflib v1.0.0 // indirect
 | 
						github.com/pmezard/go-difflib v1.0.0 // indirect
 | 
				
			||||||
	github.com/quic-go/qpack v0.5.1 // indirect
 | 
						github.com/quic-go/qpack v0.5.1 // indirect
 | 
				
			||||||
	github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
 | 
						github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
 | 
				
			||||||
	go.uber.org/mock v0.5.0 // indirect
 | 
					 | 
				
			||||||
	golang.org/x/arch v0.20.0 // indirect
 | 
						golang.org/x/arch v0.20.0 // indirect
 | 
				
			||||||
	golang.org/x/crypto v0.40.0 // indirect
 | 
						golang.org/x/crypto v0.43.0 // indirect
 | 
				
			||||||
	golang.org/x/mod v0.25.0 // indirect
 | 
						golang.org/x/mod v0.28.0 // indirect
 | 
				
			||||||
	golang.org/x/sync v0.16.0 // indirect
 | 
						golang.org/x/sync v0.17.0 // indirect
 | 
				
			||||||
	golang.org/x/sys v0.35.0 // indirect
 | 
						golang.org/x/sys v0.37.0 // indirect
 | 
				
			||||||
	golang.org/x/text v0.27.0 // indirect
 | 
						golang.org/x/text v0.30.0 // indirect
 | 
				
			||||||
	golang.org/x/tools v0.34.0 // indirect
 | 
						golang.org/x/tools v0.37.0 // indirect
 | 
				
			||||||
	gopkg.in/yaml.v3 v3.0.1 // indirect
 | 
						gopkg.in/yaml.v3 v3.0.1 // indirect
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										66
									
								
								go.sum
									
									
									
									
									
								
							
							
						
						
									
										66
									
								
								go.sum
									
									
									
									
									
								
							@ -1,14 +1,16 @@
 | 
				
			|||||||
github.com/bytedance/sonic v1.14.0 h1:/OfKt8HFw0kh2rj8N0F6C/qPGRESq0BbaNZgcNXXzQQ=
 | 
					github.com/bytedance/gopkg v0.1.3 h1:TPBSwH8RsouGCBcMBktLt1AymVo2TVsBVCY4b6TnZ/M=
 | 
				
			||||||
github.com/bytedance/sonic v1.14.0/go.mod h1:WoEbx8WTcFJfzCe0hbmyTGrfjt8PzNEBdxlNUO24NhA=
 | 
					github.com/bytedance/gopkg v0.1.3/go.mod h1:576VvJ+eJgyCzdjS+c4+77QF3p7ubbtiKARP3TxducM=
 | 
				
			||||||
github.com/bytedance/sonic/loader v0.3.0 h1:dskwH8edlzNMctoruo8FPTJDF3vLtDT0sXZwvZJyqeA=
 | 
					github.com/bytedance/sonic v1.14.2 h1:k1twIoe97C1DtYUo+fZQy865IuHia4PR5RPiuGPPIIE=
 | 
				
			||||||
github.com/bytedance/sonic/loader v0.3.0/go.mod h1:N8A3vUdtUebEY2/VQC0MyhYeKUFosQU6FxH2JmUe6VI=
 | 
					github.com/bytedance/sonic v1.14.2/go.mod h1:T80iDELeHiHKSc0C9tubFygiuXoGzrkjKzX2quAx980=
 | 
				
			||||||
 | 
					github.com/bytedance/sonic/loader v0.4.0 h1:olZ7lEqcxtZygCK9EKYKADnpQoYkRQxaeY2NYzevs+o=
 | 
				
			||||||
 | 
					github.com/bytedance/sonic/loader v0.4.0/go.mod h1:AR4NYCk5DdzZizZ5djGqQ92eEhCCcdf5x77udYiSJRo=
 | 
				
			||||||
github.com/cloudwego/base64x v0.1.6 h1:t11wG9AECkCDk5fMSoxmufanudBtJ+/HemLstXDLI2M=
 | 
					github.com/cloudwego/base64x v0.1.6 h1:t11wG9AECkCDk5fMSoxmufanudBtJ+/HemLstXDLI2M=
 | 
				
			||||||
github.com/cloudwego/base64x v0.1.6/go.mod h1:OFcloc187FXDaYHvrNIjxSe8ncn0OOM8gEHfghB2IPU=
 | 
					github.com/cloudwego/base64x v0.1.6/go.mod h1:OFcloc187FXDaYHvrNIjxSe8ncn0OOM8gEHfghB2IPU=
 | 
				
			||||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 | 
					github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 | 
				
			||||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
 | 
					github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
 | 
				
			||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 | 
					github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 | 
				
			||||||
github.com/gabriel-vasile/mimetype v1.4.8 h1:FfZ3gj38NjllZIeJAmMhr+qKL8Wu+nOoI3GqacKw1NM=
 | 
					github.com/gabriel-vasile/mimetype v1.4.10 h1:zyueNbySn/z8mJZHLt6IPw0KoZsiQNszIpU+bX4+ZK0=
 | 
				
			||||||
github.com/gabriel-vasile/mimetype v1.4.8/go.mod h1:ByKUIKGjh1ODkGM1asKUbQZOLGrPjydw3hYPU2YU9t8=
 | 
					github.com/gabriel-vasile/mimetype v1.4.10/go.mod h1:d+9Oxyo1wTzWdyVUPMmXFvp4F9tea18J8ufA774AB3s=
 | 
				
			||||||
github.com/gin-contrib/sse v1.1.0 h1:n0w2GMuUpWDVp7qSpvze6fAu9iRxJY4Hmj6AmBOU05w=
 | 
					github.com/gin-contrib/sse v1.1.0 h1:n0w2GMuUpWDVp7qSpvze6fAu9iRxJY4Hmj6AmBOU05w=
 | 
				
			||||||
github.com/gin-contrib/sse v1.1.0/go.mod h1:hxRZ5gVpWMT7Z0B0gSNYqqsSCNIJMjzvm6fqCz9vjwM=
 | 
					github.com/gin-contrib/sse v1.1.0/go.mod h1:hxRZ5gVpWMT7Z0B0gSNYqqsSCNIJMjzvm6fqCz9vjwM=
 | 
				
			||||||
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
 | 
					github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
 | 
				
			||||||
@ -17,8 +19,8 @@ github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/o
 | 
				
			|||||||
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
 | 
					github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
 | 
				
			||||||
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
 | 
					github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
 | 
				
			||||||
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
 | 
					github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
 | 
				
			||||||
github.com/go-playground/validator/v10 v10.27.0 h1:w8+XrWVMhGkxOaaowyKH35gFydVHOvC0/uWoy2Fzwn4=
 | 
					github.com/go-playground/validator/v10 v10.28.0 h1:Q7ibns33JjyW48gHkuFT91qX48KG0ktULL6FgHdG688=
 | 
				
			||||||
github.com/go-playground/validator/v10 v10.27.0/go.mod h1:I5QpIEbmr8On7W0TktmJAumgzX4CA1XNl4ZmDuVHKKo=
 | 
					github.com/go-playground/validator/v10 v10.28.0/go.mod h1:GoI6I1SjPBh9p7ykNE/yj3fFYbyDOpwMn5KXd+m2hUU=
 | 
				
			||||||
github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
 | 
					github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
 | 
				
			||||||
github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
 | 
					github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
 | 
				
			||||||
github.com/goccy/go-yaml v1.18.0 h1:8W7wMFS12Pcas7KU+VVkaiCng+kG8QiFeFwzFb+rwuw=
 | 
					github.com/goccy/go-yaml v1.18.0 h1:8W7wMFS12Pcas7KU+VVkaiCng+kG8QiFeFwzFb+rwuw=
 | 
				
			||||||
@ -44,42 +46,44 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb
 | 
				
			|||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
 | 
					github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
 | 
				
			||||||
github.com/quic-go/qpack v0.5.1 h1:giqksBPnT/HDtZ6VhtFKgoLOWmlyo9Ei6u9PqzIMbhI=
 | 
					github.com/quic-go/qpack v0.5.1 h1:giqksBPnT/HDtZ6VhtFKgoLOWmlyo9Ei6u9PqzIMbhI=
 | 
				
			||||||
github.com/quic-go/qpack v0.5.1/go.mod h1:+PC4XFrEskIVkcLzpEkbLqq1uCoxPhQuvK5rH1ZgaEg=
 | 
					github.com/quic-go/qpack v0.5.1/go.mod h1:+PC4XFrEskIVkcLzpEkbLqq1uCoxPhQuvK5rH1ZgaEg=
 | 
				
			||||||
github.com/quic-go/quic-go v0.54.0 h1:6s1YB9QotYI6Ospeiguknbp2Znb/jZYjZLRXn9kMQBg=
 | 
					github.com/quic-go/quic-go v0.55.0 h1:zccPQIqYCXDt5NmcEabyYvOnomjs8Tlwl7tISjJh9Mk=
 | 
				
			||||||
github.com/quic-go/quic-go v0.54.0/go.mod h1:e68ZEaCdyviluZmy44P6Iey98v/Wfz6HCjQEm+l8zTY=
 | 
					github.com/quic-go/quic-go v0.55.0/go.mod h1:DR51ilwU1uE164KuWXhinFcKWGlEjzys2l8zUl5Ss1U=
 | 
				
			||||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
 | 
					github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
 | 
				
			||||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
 | 
					github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
 | 
				
			||||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
 | 
					github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
 | 
				
			||||||
 | 
					github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
 | 
				
			||||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
 | 
					github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
 | 
				
			||||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
 | 
					github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
 | 
				
			||||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
 | 
					github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
 | 
				
			||||||
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
 | 
					github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
 | 
				
			||||||
 | 
					github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
 | 
				
			||||||
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
 | 
					github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
 | 
				
			||||||
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
 | 
					github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
 | 
				
			||||||
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
 | 
					github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
 | 
				
			||||||
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
 | 
					github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
 | 
				
			||||||
github.com/ugorji/go/codec v1.3.0 h1:Qd2W2sQawAfG8XSvzwhBeoGq71zXOC/Q1E9y/wUcsUA=
 | 
					github.com/ugorji/go/codec v1.3.1 h1:waO7eEiFDwidsBN6agj1vJQ4AG7lh2yqXyOXqhgQuyY=
 | 
				
			||||||
github.com/ugorji/go/codec v1.3.0/go.mod h1:pRBVtBSKl77K30Bv8R2P+cLSGaTtex6fsA2Wjqmfxj4=
 | 
					github.com/ugorji/go/codec v1.3.1/go.mod h1:pRBVtBSKl77K30Bv8R2P+cLSGaTtex6fsA2Wjqmfxj4=
 | 
				
			||||||
go.uber.org/mock v0.5.0 h1:KAMbZvZPyBPWgD14IrIQ38QCyjwpvVVV6K/bHl1IwQU=
 | 
					go.uber.org/mock v0.5.2 h1:LbtPTcP8A5k9WPXj54PPPbjcI4Y6lhyOZXn+VS7wNko=
 | 
				
			||||||
go.uber.org/mock v0.5.0/go.mod h1:ge71pBPLYDk7QIi1LupWxdAykm7KIEFchiOqd6z7qMM=
 | 
					go.uber.org/mock v0.5.2/go.mod h1:wLlUxC2vVTPTaE3UD51E0BGOAElKrILxhVSDYQLld5o=
 | 
				
			||||||
golang.org/x/arch v0.20.0 h1:dx1zTU0MAE98U+TQ8BLl7XsJbgze2WnNKF/8tGp/Q6c=
 | 
					golang.org/x/arch v0.20.0 h1:dx1zTU0MAE98U+TQ8BLl7XsJbgze2WnNKF/8tGp/Q6c=
 | 
				
			||||||
golang.org/x/arch v0.20.0/go.mod h1:bdwinDaKcfZUGpH09BB7ZmOfhalA8lQdzl62l8gGWsk=
 | 
					golang.org/x/arch v0.20.0/go.mod h1:bdwinDaKcfZUGpH09BB7ZmOfhalA8lQdzl62l8gGWsk=
 | 
				
			||||||
golang.org/x/crypto v0.40.0 h1:r4x+VvoG5Fm+eJcxMaY8CQM7Lb0l1lsmjGBQ6s8BfKM=
 | 
					golang.org/x/crypto v0.43.0 h1:dduJYIi3A3KOfdGOHX8AVZ/jGiyPa3IbBozJ5kNuE04=
 | 
				
			||||||
golang.org/x/crypto v0.40.0/go.mod h1:Qr1vMER5WyS2dfPHAlsOj01wgLbsyWtFn/aY+5+ZdxY=
 | 
					golang.org/x/crypto v0.43.0/go.mod h1:BFbav4mRNlXJL4wNeejLpWxB7wMbc79PdRGhWKncxR0=
 | 
				
			||||||
golang.org/x/mod v0.25.0 h1:n7a+ZbQKQA/Ysbyb0/6IbB1H/X41mKgbhfv7AfG/44w=
 | 
					golang.org/x/mod v0.28.0 h1:gQBtGhjxykdjY9YhZpSlZIsbnaE2+PgjfLWUQTnoZ1U=
 | 
				
			||||||
golang.org/x/mod v0.25.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww=
 | 
					golang.org/x/mod v0.28.0/go.mod h1:yfB/L0NOf/kmEbXjzCPOx1iK1fRutOydrCMsqRhEBxI=
 | 
				
			||||||
golang.org/x/net v0.42.0 h1:jzkYrhi3YQWD6MLBJcsklgQsoAcw89EcZbJw8Z614hs=
 | 
					golang.org/x/net v0.46.0 h1:giFlY12I07fugqwPuWJi68oOnpfqFnJIJzaIIm2JVV4=
 | 
				
			||||||
golang.org/x/net v0.42.0/go.mod h1:FF1RA5d3u7nAYA4z2TkclSCKh68eSXtiFwcWQpPXdt8=
 | 
					golang.org/x/net v0.46.0/go.mod h1:Q9BGdFy1y4nkUwiLvT5qtyhAnEHgnQ/zd8PfU6nc210=
 | 
				
			||||||
golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw=
 | 
					golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug=
 | 
				
			||||||
golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
 | 
					golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
 | 
				
			||||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 | 
					golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 | 
				
			||||||
golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI=
 | 
					golang.org/x/sys v0.37.0 h1:fdNQudmxPjkdUTPnLn5mdQv7Zwvbvpaxqs831goi9kQ=
 | 
				
			||||||
golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
 | 
					golang.org/x/sys v0.37.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
 | 
				
			||||||
golang.org/x/text v0.27.0 h1:4fGWRpyh641NLlecmyl4LOe6yDdfaYNrGb2zdfo4JV4=
 | 
					golang.org/x/text v0.30.0 h1:yznKA/E9zq54KzlzBEAWn1NXSQ8DIp/NYMy88xJjl4k=
 | 
				
			||||||
golang.org/x/text v0.27.0/go.mod h1:1D28KMCvyooCX9hBiosv5Tz/+YLxj0j7XhWjpSUF7CU=
 | 
					golang.org/x/text v0.30.0/go.mod h1:yDdHFIX9t+tORqspjENWgzaCVXgk0yYnYuSZ8UzzBVM=
 | 
				
			||||||
golang.org/x/tools v0.34.0 h1:qIpSLOxeCYGg9TrcJokLBG4KFA6d795g0xkBkiESGlo=
 | 
					golang.org/x/tools v0.37.0 h1:DVSRzp7FwePZW356yEAChSdNcQo6Nsp+fex1SUW09lE=
 | 
				
			||||||
golang.org/x/tools v0.34.0/go.mod h1:pAP9OwEaY1CAW3HOmg3hLZC5Z0CCmzjAF2UQMSqNARg=
 | 
					golang.org/x/tools v0.37.0/go.mod h1:MBN5QPQtLMHVdvsbtarmTNukZDdgwdwlO5qGacAzF0w=
 | 
				
			||||||
google.golang.org/protobuf v1.36.9 h1:w2gp2mA27hUeUzj9Ex9FBjsBm40zfaDtEWow293U7Iw=
 | 
					google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE=
 | 
				
			||||||
google.golang.org/protobuf v1.36.9/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU=
 | 
					google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
 | 
				
			||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
 | 
					gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
 | 
				
			||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 | 
					gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 | 
				
			||||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
 | 
					gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
 | 
				
			||||||
 | 
				
			|||||||
@ -81,25 +81,25 @@ func TestStringToBytes(t *testing.T) {
 | 
				
			|||||||
// go test -v -run=none -bench=^BenchmarkBytesConv -benchmem=true
 | 
					// go test -v -run=none -bench=^BenchmarkBytesConv -benchmem=true
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func BenchmarkBytesConvBytesToStrRaw(b *testing.B) {
 | 
					func BenchmarkBytesConvBytesToStrRaw(b *testing.B) {
 | 
				
			||||||
	for i := 0; i < b.N; i++ {
 | 
						for b.Loop() {
 | 
				
			||||||
		rawBytesToStr(testBytes)
 | 
							rawBytesToStr(testBytes)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func BenchmarkBytesConvBytesToStr(b *testing.B) {
 | 
					func BenchmarkBytesConvBytesToStr(b *testing.B) {
 | 
				
			||||||
	for i := 0; i < b.N; i++ {
 | 
						for b.Loop() {
 | 
				
			||||||
		BytesToString(testBytes)
 | 
							BytesToString(testBytes)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func BenchmarkBytesConvStrToBytesRaw(b *testing.B) {
 | 
					func BenchmarkBytesConvStrToBytesRaw(b *testing.B) {
 | 
				
			||||||
	for i := 0; i < b.N; i++ {
 | 
						for b.Loop() {
 | 
				
			||||||
		rawStrToBytes(testString)
 | 
							rawStrToBytes(testString)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func BenchmarkBytesConvStrToBytes(b *testing.B) {
 | 
					func BenchmarkBytesConvStrToBytes(b *testing.B) {
 | 
				
			||||||
	for i := 0; i < b.N; i++ {
 | 
						for b.Loop() {
 | 
				
			||||||
		StringToBytes(testString)
 | 
							StringToBytes(testString)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										38
									
								
								logger.go
									
									
									
									
									
								
							
							
						
						
									
										38
									
								
								logger.go
									
									
									
									
									
								
							@ -103,6 +103,27 @@ func (p *LogFormatterParams) StatusCodeColor() string {
 | 
				
			|||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// LatencyColor is the ANSI color for latency
 | 
				
			||||||
 | 
					func (p *LogFormatterParams) LatencyColor() string {
 | 
				
			||||||
 | 
						latency := p.Latency
 | 
				
			||||||
 | 
						switch {
 | 
				
			||||||
 | 
						case latency < time.Millisecond*100:
 | 
				
			||||||
 | 
							return white
 | 
				
			||||||
 | 
						case latency < time.Millisecond*200:
 | 
				
			||||||
 | 
							return green
 | 
				
			||||||
 | 
						case latency < time.Millisecond*300:
 | 
				
			||||||
 | 
							return cyan
 | 
				
			||||||
 | 
						case latency < time.Millisecond*500:
 | 
				
			||||||
 | 
							return blue
 | 
				
			||||||
 | 
						case latency < time.Second:
 | 
				
			||||||
 | 
							return yellow
 | 
				
			||||||
 | 
						case latency < time.Second*2:
 | 
				
			||||||
 | 
							return magenta
 | 
				
			||||||
 | 
						default:
 | 
				
			||||||
 | 
							return red
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// MethodColor is the ANSI color for appropriately logging http method to a terminal.
 | 
					// MethodColor is the ANSI color for appropriately logging http method to a terminal.
 | 
				
			||||||
func (p *LogFormatterParams) MethodColor() string {
 | 
					func (p *LogFormatterParams) MethodColor() string {
 | 
				
			||||||
	method := p.Method
 | 
						method := p.Method
 | 
				
			||||||
@ -139,20 +160,27 @@ func (p *LogFormatterParams) IsOutputColor() bool {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
// defaultLogFormatter is the default log format function Logger middleware uses.
 | 
					// defaultLogFormatter is the default log format function Logger middleware uses.
 | 
				
			||||||
var defaultLogFormatter = func(param LogFormatterParams) string {
 | 
					var defaultLogFormatter = func(param LogFormatterParams) string {
 | 
				
			||||||
	var statusColor, methodColor, resetColor string
 | 
						var statusColor, methodColor, resetColor, latencyColor string
 | 
				
			||||||
	if param.IsOutputColor() {
 | 
						if param.IsOutputColor() {
 | 
				
			||||||
		statusColor = param.StatusCodeColor()
 | 
							statusColor = param.StatusCodeColor()
 | 
				
			||||||
		methodColor = param.MethodColor()
 | 
							methodColor = param.MethodColor()
 | 
				
			||||||
		resetColor = param.ResetColor()
 | 
							resetColor = param.ResetColor()
 | 
				
			||||||
 | 
							latencyColor = param.LatencyColor()
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if param.Latency > time.Minute {
 | 
						switch {
 | 
				
			||||||
		param.Latency = param.Latency.Truncate(time.Second)
 | 
						case param.Latency > time.Minute:
 | 
				
			||||||
 | 
							param.Latency = param.Latency.Truncate(time.Second * 10)
 | 
				
			||||||
 | 
						case param.Latency > time.Second:
 | 
				
			||||||
 | 
							param.Latency = param.Latency.Truncate(time.Millisecond * 10)
 | 
				
			||||||
 | 
						case param.Latency > time.Millisecond:
 | 
				
			||||||
 | 
							param.Latency = param.Latency.Truncate(time.Microsecond * 10)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	return fmt.Sprintf("[GIN] %v |%s %3d %s| %13v | %15s |%s %-7s %s %#v\n%s",
 | 
					
 | 
				
			||||||
 | 
						return fmt.Sprintf("[GIN] %v |%s %3d %s|%s %8v %s| %15s |%s %-7s %s %#v\n%s",
 | 
				
			||||||
		param.TimeStamp.Format("2006/01/02 - 15:04:05"),
 | 
							param.TimeStamp.Format("2006/01/02 - 15:04:05"),
 | 
				
			||||||
		statusColor, param.StatusCode, resetColor,
 | 
							statusColor, param.StatusCode, resetColor,
 | 
				
			||||||
		param.Latency,
 | 
							latencyColor, param.Latency, resetColor,
 | 
				
			||||||
		param.ClientIP,
 | 
							param.ClientIP,
 | 
				
			||||||
		methodColor, param.Method, resetColor,
 | 
							methodColor, param.Method, resetColor,
 | 
				
			||||||
		param.Path,
 | 
							param.Path,
 | 
				
			||||||
 | 
				
			|||||||
@ -39,7 +39,7 @@ func TestLogger(t *testing.T) {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	// I wrote these first (extending the above) but then realized they are more
 | 
						// I wrote these first (extending the above) but then realized they are more
 | 
				
			||||||
	// like integration tests because they test the whole logging process rather
 | 
						// like integration tests because they test the whole logging process rather
 | 
				
			||||||
	// than individual functions.  Im not sure where these should go.
 | 
						// than individual functions. I'm not sure where these should go.
 | 
				
			||||||
	buffer.Reset()
 | 
						buffer.Reset()
 | 
				
			||||||
	PerformRequest(router, http.MethodPost, "/example")
 | 
						PerformRequest(router, http.MethodPost, "/example")
 | 
				
			||||||
	assert.Contains(t, buffer.String(), "200")
 | 
						assert.Contains(t, buffer.String(), "200")
 | 
				
			||||||
@ -103,7 +103,7 @@ func TestLoggerWithConfig(t *testing.T) {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	// I wrote these first (extending the above) but then realized they are more
 | 
						// I wrote these first (extending the above) but then realized they are more
 | 
				
			||||||
	// like integration tests because they test the whole logging process rather
 | 
						// like integration tests because they test the whole logging process rather
 | 
				
			||||||
	// than individual functions.  Im not sure where these should go.
 | 
						// than individual functions. I'm not sure where these should go.
 | 
				
			||||||
	buffer.Reset()
 | 
						buffer.Reset()
 | 
				
			||||||
	PerformRequest(router, http.MethodPost, "/example")
 | 
						PerformRequest(router, http.MethodPost, "/example")
 | 
				
			||||||
	assert.Contains(t, buffer.String(), "200")
 | 
						assert.Contains(t, buffer.String(), "200")
 | 
				
			||||||
@ -277,11 +277,11 @@ func TestDefaultLogFormatter(t *testing.T) {
 | 
				
			|||||||
		isTerm:       false,
 | 
							isTerm:       false,
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	assert.Equal(t, "[GIN] 2018/12/07 - 09:11:42 | 200 |            5s |     20.20.20.20 | GET      \"/\"\n", defaultLogFormatter(termFalseParam))
 | 
						assert.Equal(t, "[GIN] 2018/12/07 - 09:11:42 | 200 |       5s |     20.20.20.20 | GET      \"/\"\n", defaultLogFormatter(termFalseParam))
 | 
				
			||||||
	assert.Equal(t, "[GIN] 2018/12/07 - 09:11:42 | 200 |    2743h29m3s |     20.20.20.20 | GET      \"/\"\n", defaultLogFormatter(termFalseLongDurationParam))
 | 
						assert.Equal(t, "[GIN] 2018/12/07 - 09:11:42 | 200 | 2743h29m0s |     20.20.20.20 | GET      \"/\"\n", defaultLogFormatter(termFalseLongDurationParam))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	assert.Equal(t, "[GIN] 2018/12/07 - 09:11:42 |\x1b[97;42m 200 \x1b[0m|            5s |     20.20.20.20 |\x1b[97;44m GET     \x1b[0m \"/\"\n", defaultLogFormatter(termTrueParam))
 | 
						assert.Equal(t, "[GIN] 2018/12/07 - 09:11:42 |\x1b[97;42m 200 \x1b[0m|\x1b[97;41m       5s \x1b[0m|     20.20.20.20 |\x1b[97;44m GET     \x1b[0m \"/\"\n", defaultLogFormatter(termTrueParam))
 | 
				
			||||||
	assert.Equal(t, "[GIN] 2018/12/07 - 09:11:42 |\x1b[97;42m 200 \x1b[0m|    2743h29m3s |     20.20.20.20 |\x1b[97;44m GET     \x1b[0m \"/\"\n", defaultLogFormatter(termTrueLongDurationParam))
 | 
						assert.Equal(t, "[GIN] 2018/12/07 - 09:11:42 |\x1b[97;42m 200 \x1b[0m|\x1b[97;41m 2743h29m0s \x1b[0m|     20.20.20.20 |\x1b[97;44m GET     \x1b[0m \"/\"\n", defaultLogFormatter(termTrueLongDurationParam))
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func TestColorForMethod(t *testing.T) {
 | 
					func TestColorForMethod(t *testing.T) {
 | 
				
			||||||
@ -317,6 +317,23 @@ func TestColorForStatus(t *testing.T) {
 | 
				
			|||||||
	assert.Equal(t, red, colorForStatus(2), "other things should be red")
 | 
						assert.Equal(t, red, colorForStatus(2), "other things should be red")
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func TestColorForLatency(t *testing.T) {
 | 
				
			||||||
 | 
						colorForLantency := func(latency time.Duration) string {
 | 
				
			||||||
 | 
							p := LogFormatterParams{
 | 
				
			||||||
 | 
								Latency: latency,
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							return p.LatencyColor()
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						assert.Equal(t, white, colorForLantency(time.Duration(0)), "0 should be white")
 | 
				
			||||||
 | 
						assert.Equal(t, white, colorForLantency(time.Millisecond*20), "20ms should be white")
 | 
				
			||||||
 | 
						assert.Equal(t, green, colorForLantency(time.Millisecond*150), "150ms should be green")
 | 
				
			||||||
 | 
						assert.Equal(t, cyan, colorForLantency(time.Millisecond*250), "250ms should be cyan")
 | 
				
			||||||
 | 
						assert.Equal(t, yellow, colorForLantency(time.Millisecond*600), "600ms should be yellow")
 | 
				
			||||||
 | 
						assert.Equal(t, magenta, colorForLantency(time.Millisecond*1500), "1.5s should be magenta")
 | 
				
			||||||
 | 
						assert.Equal(t, red, colorForLantency(time.Second*3), "other things should be red")
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func TestResetColor(t *testing.T) {
 | 
					func TestResetColor(t *testing.T) {
 | 
				
			||||||
	p := LogFormatterParams{}
 | 
						p := LogFormatterParams{}
 | 
				
			||||||
	assert.Equal(t, string([]byte{27, 91, 48, 109}), p.ResetColor())
 | 
						assert.Equal(t, string([]byte{27, 91, 48, 109}), p.ResetColor())
 | 
				
			||||||
 | 
				
			|||||||
@ -17,7 +17,7 @@ const (
 | 
				
			|||||||
	defaultStatus = http.StatusOK
 | 
						defaultStatus = http.StatusOK
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
var errHijackAlreadyWritten = errors.New("gin: response already written")
 | 
					var errHijackAlreadyWritten = errors.New("gin: response body already written")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// ResponseWriter ...
 | 
					// ResponseWriter ...
 | 
				
			||||||
type ResponseWriter interface {
 | 
					type ResponseWriter interface {
 | 
				
			||||||
@ -109,7 +109,9 @@ func (w *responseWriter) Written() bool {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
// Hijack implements the http.Hijacker interface.
 | 
					// Hijack implements the http.Hijacker interface.
 | 
				
			||||||
func (w *responseWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) {
 | 
					func (w *responseWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) {
 | 
				
			||||||
	if w.Written() {
 | 
						// Allow hijacking before any data is written (size == -1) or after headers are written (size == 0),
 | 
				
			||||||
 | 
						// but not after body data is written (size > 0). For compatibility with websocket libraries (e.g., github.com/coder/websocket)
 | 
				
			||||||
 | 
						if w.size > 0 {
 | 
				
			||||||
		return nil, nil, errHijackAlreadyWritten
 | 
							return nil, nil, errHijackAlreadyWritten
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	if w.size < 0 {
 | 
						if w.size < 0 {
 | 
				
			||||||
 | 
				
			|||||||
@ -194,6 +194,64 @@ func TestResponseWriterHijackAfterWrite(t *testing.T) {
 | 
				
			|||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Test: WebSocket compatibility - allow hijack after WriteHeaderNow(), but block after body data.
 | 
				
			||||||
 | 
					func TestResponseWriterHijackAfterWriteHeaderNow(t *testing.T) {
 | 
				
			||||||
 | 
						tests := []struct {
 | 
				
			||||||
 | 
							name                      string
 | 
				
			||||||
 | 
							action                    func(w ResponseWriter) error
 | 
				
			||||||
 | 
							expectWrittenBeforeHijack bool
 | 
				
			||||||
 | 
							expectHijackSuccess       bool
 | 
				
			||||||
 | 
							expectWrittenAfterHijack  bool
 | 
				
			||||||
 | 
							expectError               error
 | 
				
			||||||
 | 
						}{
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								name: "hijack after WriteHeaderNow only should succeed (websocket pattern)",
 | 
				
			||||||
 | 
								action: func(w ResponseWriter) error {
 | 
				
			||||||
 | 
									w.WriteHeaderNow() // Simulate websocket.Accept() behavior
 | 
				
			||||||
 | 
									return nil
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								expectWrittenBeforeHijack: true,
 | 
				
			||||||
 | 
								expectHijackSuccess:       true, // NEW BEHAVIOR: allow hijack after just header write
 | 
				
			||||||
 | 
								expectWrittenAfterHijack:  true,
 | 
				
			||||||
 | 
								expectError:               nil,
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								name: "hijack after WriteHeaderNow + Write should fail",
 | 
				
			||||||
 | 
								action: func(w ResponseWriter) error {
 | 
				
			||||||
 | 
									w.WriteHeaderNow()
 | 
				
			||||||
 | 
									_, err := w.Write([]byte("test"))
 | 
				
			||||||
 | 
									return err
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								expectWrittenBeforeHijack: true,
 | 
				
			||||||
 | 
								expectHijackSuccess:       false,
 | 
				
			||||||
 | 
								expectWrittenAfterHijack:  true,
 | 
				
			||||||
 | 
								expectError:               errHijackAlreadyWritten,
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						for _, tc := range tests {
 | 
				
			||||||
 | 
							t.Run(tc.name, func(t *testing.T) {
 | 
				
			||||||
 | 
								hijacker := &mockHijacker{ResponseRecorder: httptest.NewRecorder()}
 | 
				
			||||||
 | 
								writer := &responseWriter{}
 | 
				
			||||||
 | 
								writer.reset(hijacker)
 | 
				
			||||||
 | 
								w := ResponseWriter(writer)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								require.NoError(t, tc.action(w), "unexpected error during pre-hijack action")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								assert.Equal(t, tc.expectWrittenBeforeHijack, w.Written(), "unexpected w.Written() state before hijack")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								_, _, hijackErr := w.Hijack()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								if tc.expectError == nil {
 | 
				
			||||||
 | 
									require.NoError(t, hijackErr, "expected hijack to succeed")
 | 
				
			||||||
 | 
								} else {
 | 
				
			||||||
 | 
									require.ErrorIs(t, hijackErr, tc.expectError, "unexpected error from Hijack()")
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								assert.Equal(t, tc.expectHijackSuccess, hijacker.hijacked, "unexpected hijacker.hijacked state")
 | 
				
			||||||
 | 
								assert.Equal(t, tc.expectWrittenAfterHijack, w.Written(), "unexpected w.Written() state after hijack")
 | 
				
			||||||
 | 
							})
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func TestResponseWriterFlush(t *testing.T) {
 | 
					func TestResponseWriterFlush(t *testing.T) {
 | 
				
			||||||
	testServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
 | 
						testServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
 | 
				
			||||||
		writer := &responseWriter{}
 | 
							writer := &responseWriter{}
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										18
									
								
								tree.go
									
									
									
									
									
								
							
							
						
						
									
										18
									
								
								tree.go
									
									
									
									
									
								
							@ -5,7 +5,6 @@
 | 
				
			|||||||
package gin
 | 
					package gin
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import (
 | 
					import (
 | 
				
			||||||
	"bytes"
 | 
					 | 
				
			||||||
	"net/url"
 | 
						"net/url"
 | 
				
			||||||
	"strings"
 | 
						"strings"
 | 
				
			||||||
	"unicode"
 | 
						"unicode"
 | 
				
			||||||
@ -14,12 +13,6 @@ import (
 | 
				
			|||||||
	"github.com/gin-gonic/gin/internal/bytesconv"
 | 
						"github.com/gin-gonic/gin/internal/bytesconv"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
var (
 | 
					 | 
				
			||||||
	strColon = []byte(":")
 | 
					 | 
				
			||||||
	strStar  = []byte("*")
 | 
					 | 
				
			||||||
	strSlash = []byte("/")
 | 
					 | 
				
			||||||
)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// Param is a single URL parameter, consisting of a key and a value.
 | 
					// Param is a single URL parameter, consisting of a key and a value.
 | 
				
			||||||
type Param struct {
 | 
					type Param struct {
 | 
				
			||||||
	Key   string
 | 
						Key   string
 | 
				
			||||||
@ -85,16 +78,13 @@ func (n *node) addChild(child *node) {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func countParams(path string) uint16 {
 | 
					func countParams(path string) uint16 {
 | 
				
			||||||
	var n uint16
 | 
						colons := strings.Count(path, ":")
 | 
				
			||||||
	s := bytesconv.StringToBytes(path)
 | 
						stars := strings.Count(path, "*")
 | 
				
			||||||
	n += uint16(bytes.Count(s, strColon))
 | 
						return uint16(colons + stars)
 | 
				
			||||||
	n += uint16(bytes.Count(s, strStar))
 | 
					 | 
				
			||||||
	return n
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func countSections(path string) uint16 {
 | 
					func countSections(path string) uint16 {
 | 
				
			||||||
	s := bytesconv.StringToBytes(path)
 | 
						return uint16(strings.Count(path, "/"))
 | 
				
			||||||
	return uint16(bytes.Count(s, strSlash))
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
type nodeType uint8
 | 
					type nodeType uint8
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user