[
  {
    "path": ".github/ISSUE_TEMPLATE/bug-report.yaml",
    "content": "name: Bug Report\ndescription: Found something you weren't expecting? Report it here!\nlabels: [\"type/bug\"]\nbody:\n  - type: markdown\n    attributes:\n      value: |\n        NOTE: If your issue is a security concern, please send an email to appleboy.tw@gmail.com instead of opening a public issue.\n  - type: markdown\n    attributes:\n      value: |\n        1. Please speak English, this is the language all maintainers can speak and write.\n        2. Please ask questions problems on our Discussions Forum (https://github.com/gin-gonic/gin/discussions).\n        3. Make sure you are using the latest release and\n           take a moment to check that your issue hasn't been reported before.\n  - type: textarea\n    id: description\n    attributes:\n      label: Description\n      description: |\n        Please provide a description of your issue here, with a URL if you were able to reproduce the issue (see below)\n  - type: input\n    id: gin-ver\n    attributes:\n      label: Gin Version\n      description: Gin version (or commit reference) of your instance\n    validations:\n      required: true\n  - type: dropdown\n    id: can-reproduce\n    attributes:\n      label: Can you reproduce the bug?\n      description: |\n        If so, please write the steps to reproduce the bug.\n      options:\n        - \"Yes\"\n        - \"No\"\n    validations:\n      required: true\n  - type: markdown\n    attributes:\n      value: |\n        It's really important to provide pertinent logs\n        Please read https://docs.gitea.com/administration/logging-config#collecting-logs-for-help\n        In addition, if your problem relates to git commands set `RUN_MODE=dev` at the top of app.ini\n  - type: textarea\n    id: source-code\n    attributes:\n      label: Source Code\n      description: If this issue involves source code, please provide a minimal reproducible example\n  - type: input\n    id: go-ver\n    attributes:\n      label: Go Version\n      description: The version of Go running on the server\n  - type: input\n    id: os-ver\n    attributes:\n      label: Operating System\n      description: The operating system you are using to run Gin\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/config.yml",
    "content": "blank_issues_enabled: false\ncontact_links:\n  - name: Go.dev API Documentation\n    url: https://pkg.go.dev/github.com/gin-gonic/gin\n    about: Comprehensive API documentation for Gin.\n  - name: Gin User Guides\n    url: https://gin-gonic.com/\n    about: In-depth user guides and tutorials for using Gin.\n  - name: Discussions Forum\n    url: https://github.com/gin-gonic/gin/discussions\n    about: Questions and configuration or deployment problems can also be discussed.\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/feature-request.yaml",
    "content": "name: Feature Request\ndescription: Got an idea for a feature that Gin doesn't have currently?  Submit your idea here!\nlabels: [\"type/proposal\"]\nbody:\n  - type: markdown\n    attributes:\n      value: |\n        1. Please speak English, this is the language all maintainers can speak and write.\n        2. Please ask questions problems on our Discussions Forum (https://github.com/gin-gonic/gin/discussions).\n        3. Please take a moment to check that your feature hasn't already been suggested.\n  - type: textarea\n    id: description\n    attributes:\n      label: Feature Description\n      placeholder: |\n        I think it would be great if Gin had...\n    validations:\n      required: true\n"
  },
  {
    "path": ".github/PULL_REQUEST_TEMPLATE.md",
    "content": "# Pull Request Checklist\n\nPlease ensure your pull request meets the following requirements:\n\n- [ ] Open your pull request against the `master` branch.\n- [ ] All tests pass in available continuous integration systems (e.g., GitHub Actions).\n- [ ] Tests are added or modified as needed to cover code changes.\n- [ ] If the pull request introduces a new feature, the feature is documented in the `docs/doc.md`.\n\nThank you for contributing!\n"
  },
  {
    "path": ".github/dependabot.yml",
    "content": "version: 2\nupdates:\n  - package-ecosystem: gomod\n    directory: /\n    schedule:\n      interval: daily\n  - package-ecosystem: github-actions\n    directory: /\n    groups:\n      actions:\n        patterns:\n          - \"*\"\n    schedule:\n      interval: daily\n"
  },
  {
    "path": ".github/workflows/codeql.yml",
    "content": "# For most projects, this workflow file will not need changing; you simply need\n# to commit it to your repository.\n#\n# You may wish to alter this file to override the set of languages analyzed,\n# or to provide custom queries or build logic.\nname: \"CodeQL\"\n\non:\n  push:\n    branches: [master]\n  pull_request:\n    # The branches below must be a subset of the branches above\n    branches: [master]\n  schedule:\n    - cron: \"0 17 * * 5\"\n\njobs:\n  analyze:\n    name: Analyze\n    runs-on: ubuntu-latest\n\n    permissions:\n      # required for all workflows\n      security-events: write\n\n    strategy:\n      fail-fast: false\n      matrix:\n        # Override automatic language detection by changing the below list\n        # Supported options are ['csharp', 'cpp', 'go', 'java', 'javascript', 'python']\n        # TODO: Enable for javascript later\n        language: [\"go\"]\n\n    steps:\n      - name: Checkout repository\n        uses: actions/checkout@v6\n\n      # Initializes the CodeQL tools for scanning.\n      - name: Initialize CodeQL\n        uses: github/codeql-action/init@v4\n        with:\n          languages: ${{ matrix.language }}\n          # If you wish to specify custom queries, you can do so here or in a config file.\n          # By default, queries listed here will override any specified in a config file.\n          # Prefix the list here with \"+\" to use these queries and those in the config file.\n          # queries: ./path/to/local/query, your-org/your-repo/queries@main\n\n      - name: Perform CodeQL Analysis\n        uses: github/codeql-action/analyze@v4\n"
  },
  {
    "path": ".github/workflows/gin.yml",
    "content": "name: Run Tests\n\non:\n  push:\n    branches:\n      - master\n  pull_request:\n    branches:\n      - master\n\npermissions:\n  contents: read\n\njobs:\n  lint:\n    runs-on: ubuntu-latest\n    steps:\n      - name: Checkout\n        uses: actions/checkout@v6\n        with:\n          fetch-depth: 0\n      - name: Set up Go\n        uses: actions/setup-go@v6\n        with:\n          go-version: \"^1\"\n      - name: Setup golangci-lint\n        uses: golangci/golangci-lint-action@v9\n        with:\n          version: v2.11\n          args: --verbose\n  test:\n    needs: lint\n    strategy:\n      matrix:\n        os: [ubuntu-latest, macos-latest]\n        go: [\"1.25\", \"1.26\"]\n        test-tags:\n          [\n            \"\",\n            \"-tags nomsgpack\",\n            '--ldflags=\"-checklinkname=0\" -tags sonic',\n            \"-tags go_json\",\n            \"-race\",\n          ]\n        include:\n          - os: ubuntu-latest\n            go-build: ~/.cache/go-build\n          - os: macos-latest\n            go-build: ~/Library/Caches/go-build\n    name: ${{ matrix.os }} @ Go ${{ matrix.go }} ${{ matrix.test-tags }}\n    runs-on: ${{ matrix.os }}\n    env:\n      GO111MODULE: on\n      TESTTAGS: ${{ matrix.test-tags }}\n      GOPROXY: https://proxy.golang.org\n    steps:\n      - name: Set up Go ${{ matrix.go }}\n        uses: actions/setup-go@v6\n        with:\n          go-version: ${{ matrix.go }}\n          cache: false\n\n      - name: Checkout Code\n        uses: actions/checkout@v6\n        with:\n          ref: ${{ github.ref }}\n\n      - uses: actions/cache@v5\n        with:\n          path: |\n            ${{ matrix.go-build }}\n            ~/go/pkg/mod\n          key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}\n          restore-keys: |\n            ${{ runner.os }}-go-\n\n      - name: Run Tests\n        run: make test\n\n      - name: Upload coverage to Codecov\n        uses: codecov/codecov-action@v5\n        with:\n          flags: ${{ matrix.os }},go-${{ matrix.go }},${{ matrix.test-tags }}\n"
  },
  {
    "path": ".github/workflows/goreleaser.yml",
    "content": "name: Goreleaser\n\non:\n  push:\n    tags:\n      - \"*\"\n\npermissions:\n  contents: write\n\njobs:\n  goreleaser:\n    runs-on: ubuntu-latest\n    steps:\n      - name: Checkout\n        uses: actions/checkout@v6\n        with:\n          fetch-depth: 0\n      - name: Set up Go\n        uses: actions/setup-go@v6\n        with:\n          go-version: \"^1\"\n      - name: Run GoReleaser\n        uses: goreleaser/goreleaser-action@v7\n        with:\n          # either 'goreleaser' (default) or 'goreleaser-pro'\n          distribution: goreleaser\n          version: latest\n          args: release --clean\n        env:\n          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n\n      - name: Trigger Go module reindex (pkg.go.dev)\n        run: |\n          echo \"Triggering Go module reindex at proxy.golang.org\"\n          curl -sSf \"https://proxy.golang.org/github.com/${GITHUB_REPOSITORY,,}/@v/${GITHUB_REF_NAME}.info\"\n"
  },
  {
    "path": ".github/workflows/trivy-scan.yml",
    "content": "name: Trivy Security Scan\n\non:\n  push:\n    branches:\n      - master\n  pull_request:\n    branches:\n      - master\n  schedule:\n    # Run daily at 00:00 UTC\n    - cron: \"0 0 * * *\"\n  workflow_dispatch: # Allow manual trigger\n\npermissions:\n  contents: read\n  security-events: write # Required for uploading SARIF results\n\njobs:\n  trivy-scan:\n    name: Trivy Security Scan\n    runs-on: ubuntu-latest\n    steps:\n      - name: Checkout code\n        uses: actions/checkout@v6\n        with:\n          fetch-depth: 0\n\n      - name: Run Trivy vulnerability scanner (source code)\n        uses: aquasecurity/trivy-action@0.35.0\n        with:\n          scan-type: \"fs\"\n          scan-ref: \".\"\n          scanners: \"vuln,secret,misconfig\"\n          format: \"sarif\"\n          output: \"trivy-results.sarif\"\n          severity: \"CRITICAL,HIGH,MEDIUM\"\n          ignore-unfixed: true\n\n      - name: Upload Trivy results to GitHub Security tab\n        uses: github/codeql-action/upload-sarif@v4\n        if: always()\n        with:\n          sarif_file: \"trivy-results.sarif\"\n\n      - name: Run Trivy scanner (table output for logs)\n        uses: aquasecurity/trivy-action@0.35.0\n        if: always()\n        with:\n          scan-type: \"fs\"\n          scan-ref: \".\"\n          scanners: \"vuln,secret,misconfig\"\n          format: \"table\"\n          severity: \"CRITICAL,HIGH,MEDIUM\"\n          ignore-unfixed: true\n          exit-code: \"1\"\n"
  },
  {
    "path": ".gitignore",
    "content": "vendor/*\n!vendor/vendor.json\ncoverage.out\ncount.out\ntest\nprofile.out\ntmp.out\n\n# Develop tools\n.idea/\n.vscode/\n"
  },
  {
    "path": ".golangci.yml",
    "content": "version: \"2\"\nlinters:\n  enable:\n    - asciicheck\n    - copyloopvar\n    - dogsled\n    - durationcheck\n    - errorlint\n    - gosec\n    - misspell\n    - nakedret\n    - nilerr\n    - nolintlint\n    - perfsprint\n    - revive\n    - testifylint\n    - usestdlibvars\n    - wastedassign\n  settings:\n    gosec:\n      excludes:\n        - G115\n    perfsprint:\n      int-conversion: true\n      err-error: true\n      errorf: true\n      sprintf1: true\n      strconcat: true\n    testifylint:\n      enable-all: true\n  exclusions:\n    generated: lax\n    presets:\n      - comments\n      - common-false-positives\n      - legacy\n      - std-error-handling\n    rules:\n      - linters:\n          - structcheck\n          - unused\n        text: '`data` is unused'\n      - linters:\n          - staticcheck\n        text: 'SA1019:'\n      - linters:\n          - revive\n        text: 'var-naming:'\n      - linters:\n          - revive\n        text: 'exported:'\n      - linters:\n          - gosec\n        path: _test\\.go\n      - linters:\n          - revive\n        path: _test\\.go\n    paths:\n      - third_party$\n      - builtin$\n      - examples$\nformatters:\n  enable:\n    - gofmt\n    - gofumpt\n    - goimports\n  settings:\n    gofmt:\n      rewrite-rules:\n        - pattern: 'interface{}'\n          replacement: 'any'\n  exclusions:\n    generated: lax\n    paths:\n      - gin.go\n"
  },
  {
    "path": ".goreleaser.yaml",
    "content": "project_name: gin\n\nbuilds:\n  - # If true, skip the build.\n    # Useful for library projects.\n    # Default is false\n    skip: true\n\nchangelog:\n  # Set it to true if you wish to skip the changelog generation.\n  # This may result in an empty release notes on GitHub/GitLab/Gitea.\n  disable: false\n\n  # Changelog generation implementation to use.\n  #\n  # Valid options are:\n  # - `git`: uses `git log`;\n  # - `github`: uses the compare GitHub API, appending the author login to the changelog.\n  # - `gitlab`: uses the compare GitLab API, appending the author name and email to the changelog.\n  # - `github-native`: uses the GitHub release notes generation API, disables the groups feature.\n  #\n  # Defaults to `git`.\n  use: github\n\n  # Sorts the changelog by the commit's messages.\n  # Could either be asc, desc or empty\n  # Default is empty\n  sort: asc\n\n  # Group commits messages by given regex and title.\n  # Order value defines the order of the groups.\n  # Proving no regex means all commits will be grouped under the default group.\n  # Groups are disabled when using github-native, as it already groups things by itself.\n  #\n  # Default is no groups.\n  groups:\n    - title: Features\n      regexp: \"^.*feat[(\\\\w)]*:+.*$\"\n      order: 0\n    - title: \"Bug fixes\"\n      regexp: \"^.*fix[(\\\\w)]*:+.*$\"\n      order: 1\n    - title: \"Enhancements\"\n      regexp: \"^.*chore[(\\\\w)]*:+.*$\"\n      order: 2\n    - title: \"Refactor\"\n      regexp: \"^.*refactor[(\\\\w)]*:+.*$\"\n      order: 3\n    - title: \"Build process updates\"\n      regexp: ^.*?(build|ci)(\\(.+\\))??!?:.+$\n      order: 4\n    - title: \"Documentation updates\"\n      regexp: ^.*?docs?(\\(.+\\))??!?:.+$\n      order: 4\n    - title: Others\n      order: 999\n"
  },
  {
    "path": "BENCHMARKS.md",
    "content": "# Gin Benchmark Report\n\n**Machine:** Apple M4 Pro\n**OS:** macOS (Darwin 25.3.0), arm64\n**Date:** March 15th, 2026\n**Gin Version:** v1.12.0\n**Go Version:** 1.25.8 darwin/arm64\n**Source:** [Go HTTP Router Benchmark](https://github.com/gin-gonic/go-http-routing-benchmark)\n\n---\n\n## Table of Contents\n\n- [Summary](#summary)\n- [Memory Consumption](#memory-consumption)\n- [Benchmark Results](#benchmark-results)\n  - [GitHub API (203 routes)](#github-api-203-routes)\n  - [Google+ API (13 routes)](#google-api-13-routes)\n  - [Parse API (26 routes)](#parse-api-26-routes)\n  - [Static Routes (157 routes)](#static-routes-157-routes)\n- [Micro Benchmarks](#micro-benchmarks)\n  - [Single Param](#single-param)\n  - [5 Params](#5-params)\n  - [20 Params](#20-params)\n  - [Param Write](#param-write)\n\n---\n\n## Summary\n\nThe table below ranks all routers by **GitHub API throughput** (203 routes, all methods), which best represents real-world routing workloads. _Lower ns/op is better._\n\n| Rank | Router        |     ns/op |      B/op | allocs/op |     Zero-alloc     |\n| :--: | :------------ | --------: | --------: | --------: | :----------------: |\n|  1   | **Gin**       |     9,944 |         0 |         0 | :white_check_mark: |\n|  2   | **BunRouter** |    10,281 |         0 |         0 | :white_check_mark: |\n|  3   | **Echo**      |    11,072 |         0 |         0 | :white_check_mark: |\n|  4   | HttpRouter    |    15,059 |    13,792 |       167 |                    |\n|  5   | HttpTreeMux   |    49,302 |    65,856 |       671 |                    |\n|  6   | Chi           |    94,376 |   130,817 |       740 |                    |\n|  7   | Beego         |   101,941 |    71,456 |       609 |                    |\n|  8   | Fiber         |   109,148 |         0 |         0 | :white_check_mark: |\n|  9   | Macaron       |   121,785 |   147,784 |     1,624 |                    |\n|  10  | Goji v2       |   242,849 |   313,744 |     3,712 |                    |\n|  11  | GoRestful     |   885,678 | 1,006,744 |     3,009 |                    |\n|  12  | GorillaMux    | 1,316,844 |   225,667 |     1,588 |                    |\n\n**Key takeaways:**\n\n- **Gin**, **BunRouter**, and **Echo** form the top tier — all achieve zero heap allocations and route the full GitHub API in ~10 us.\n- **HttpRouter** remains extremely fast but incurs 1 alloc per parameterized route (167 allocs for 203 routes).\n- **Fiber** also achieves zero allocations, but its fasthttp-based benchmark infrastructure adds per-iteration reset overhead — do not compare its absolute ns/op directly with net/http routers.\n- **GorillaMux** and **GoRestful** are feature-rich but orders of magnitude slower, making them less suitable for latency-sensitive applications.\n\n> **Fiber caveat:** Fiber benchmarks use `fasthttp.RequestCtx` with per-iteration Reset, which adds constant overhead not present in net/http benchmarks. Fiber-vs-Fiber comparisons are valid; cross-framework comparisons should be interpreted with care.\n\n---\n\n## Memory Consumption\n\nMemory required for loading the routing structure (lower is better). Sorted by bytes ascending.\n\n### Static Routes: 157\n\n| Router         |      Bytes |\n| :------------- | ---------: |\n| **HttpRouter** | **21,680** |\n| **Gin**        | **34,408** |\n| **Macaron**    | **36,976** |\n| BunRouter      |     51,232 |\n| Fiber          |     59,248 |\n| HttpServeMux   |     71,728 |\n| HttpTreeMux    |     73,448 |\n| Chi            |     83,160 |\n| Echo           |     91,976 |\n| Beego          |     98,824 |\n| Goji v2        |    117,952 |\n| GorillaMux     |    599,496 |\n| GoRestful      |    819,704 |\n\n### GitHub API Routes: 203\n\n| Router          |      Bytes |\n| :-------------- | ---------: |\n| **HttpRouter**  | **37,072** |\n| **Gin**         | **58,840** |\n| **HttpTreeMux** | **78,800** |\n| Macaron         |     90,632 |\n| BunRouter       |     93,776 |\n| Chi             |     94,888 |\n| Echo            |    117,784 |\n| Goji v2         |    118,640 |\n| Beego           |    150,840 |\n| Fiber           |    163,832 |\n| GoRestful       |  1,270,848 |\n| GorillaMux      |  1,319,696 |\n\n### Google+ API Routes: 13\n\n| Router         |     Bytes |\n| :------------- | --------: |\n| **HttpRouter** | **2,776** |\n| **Gin**        | **4,576** |\n| **BunRouter**  | **7,360** |\n| HttpTreeMux    |     7,440 |\n| Chi            |     8,008 |\n| Goji v2        |     8,096 |\n| Macaron        |     8,672 |\n| Beego          |    10,256 |\n| Fiber          |    10,840 |\n| Echo           |    10,968 |\n| GorillaMux     |    68,000 |\n| GoRestful      |    72,536 |\n\n### Parse API Routes: 26\n\n| Router          |     Bytes |\n| :-------------- | --------: |\n| **HttpRouter**  | **5,024** |\n| **Gin**         | **7,896** |\n| **HttpTreeMux** | **7,848** |\n| BunRouter       |     9,336 |\n| Chi             |     9,656 |\n| Echo            |    13,816 |\n| Macaron         |    13,704 |\n| Fiber           |    15,352 |\n| Goji v2         |    16,064 |\n| Beego           |    19,256 |\n| GorillaMux      |   105,384 |\n| GoRestful       |   121,200 |\n\n---\n\n## Benchmark Results\n\n### GitHub API (203 routes)\n\nRouting all 203 GitHub API endpoints per operation.\n\n| Rank | Router        |     ns/op |      B/op | allocs/op |\n| :--: | :------------ | --------: | --------: | --------: |\n|  1   | **Gin**       |     9,944 |         0 |         0 |\n|  2   | **BunRouter** |    10,281 |         0 |         0 |\n|  3   | **Echo**      |    11,072 |         0 |         0 |\n|  4   | HttpRouter    |    15,059 |    13,792 |       167 |\n|  5   | HttpTreeMux   |    49,302 |    65,856 |       671 |\n|  6   | Chi           |    94,376 |   130,817 |       740 |\n|  7   | Beego         |   101,941 |    71,456 |       609 |\n|  8   | Fiber         |   109,148 |         0 |         0 |\n|  9   | Macaron       |   121,785 |   147,784 |     1,624 |\n|  10  | Goji v2       |   242,849 |   313,744 |     3,712 |\n|  11  | GoRestful     |   885,678 | 1,006,744 |     3,009 |\n|  12  | GorillaMux    | 1,316,844 |   225,667 |     1,588 |\n\n### Google+ API (13 routes)\n\nRouting all 13 Google+ API endpoints per operation.\n\n| Rank | Router        |  ns/op |   B/op | allocs/op |\n| :--: | :------------ | -----: | -----: | --------: |\n|  1   | **BunRouter** |  348.5 |      0 |         0 |\n|  2   | **Gin**       |  429.7 |      0 |         0 |\n|  3   | **Echo**      |  451.1 |      0 |         0 |\n|  4   | HttpRouter    |  668.6 |    640 |        11 |\n|  5   | HttpTreeMux   |  2,428 |  4,032 |        38 |\n|  6   | Fiber         |  2,506 |      0 |         0 |\n|  7   | Chi           |  5,333 |  8,480 |        48 |\n|  8   | Beego         |  5,927 |  4,576 |        39 |\n|  9   | Macaron       |  7,294 |  9,464 |       104 |\n|  10  | Goji v2       |  8,000 | 15,120 |       115 |\n|  11  | GorillaMux    | 14,707 | 14,448 |       102 |\n|  12  | GoRestful     | 24,189 | 60,720 |       193 |\n\n### Parse API (26 routes)\n\nRouting all 26 Parse API endpoints per operation.\n\n| Rank | Router        |  ns/op |    B/op | allocs/op |\n| :--: | :------------ | -----: | ------: | --------: |\n|  1   | **BunRouter** |  588.2 |       0 |         0 |\n|  2   | **Gin**       |  712.1 |       0 |         0 |\n|  3   | **Echo**      |  742.1 |       0 |         0 |\n|  4   | HttpRouter    |  948.5 |     640 |        16 |\n|  5   | HttpTreeMux   |  3,372 |   5,728 |        51 |\n|  6   | Fiber         |  4,250 |       0 |         0 |\n|  7   | Chi           |  8,863 |  14,944 |        84 |\n|  8   | Beego         | 10,541 |   9,152 |        78 |\n|  9   | Macaron       | 13,635 |  18,928 |       208 |\n|  10  | Goji v2       | 13,264 |  29,456 |       199 |\n|  11  | GorillaMux    | 25,886 |  26,960 |       198 |\n|  12  | GoRestful     | 54,780 | 131,728 |       380 |\n\n### Static Routes (157 routes)\n\nRouting all 157 static routes per operation. Includes http.ServeMux as baseline.\n\n| Rank | Router          |   ns/op |    B/op | allocs/op |\n| :--: | :-------------- | ------: | ------: | --------: |\n|  1   | **HttpRouter**  |   4,177 |       0 |         0 |\n|  2   | **HttpTreeMux** |   5,363 |       0 |         0 |\n|  3   | **Gin**         |   5,528 |       0 |         0 |\n|  4   | BunRouter       |   5,997 |       0 |         0 |\n|  5   | Echo            |   6,897 |       0 |         0 |\n|  —   | HttpServeMux    |  18,172 |       0 |         0 |\n|  6   | Fiber           |  29,310 |       0 |         0 |\n|  7   | Chi             |  41,317 |  57,776 |       314 |\n|  8   | Beego           |  68,255 |  55,264 |       471 |\n|  9   | Macaron         |  81,824 | 114,296 |     1,256 |\n|  10  | Goji v2         |  84,459 | 175,840 |     1,099 |\n|  11  | GorillaMux      | 302,825 | 133,137 |     1,099 |\n|  12  | GoRestful       | 436,510 | 677,824 |     2,193 |\n\n---\n\n## Micro Benchmarks\n\n### Single Param\n\nRoute: `/user/:name` — Request: `GET /user/gordon`\n\n| Rank | Router        | ns/op |  B/op | allocs/op |\n| :--: | :------------ | ----: | ----: | --------: |\n|  1   | **BunRouter** | 12.22 |     0 |         0 |\n|  2   | **Echo**      | 17.75 |     0 |         0 |\n|  3   | **Gin**       | 23.31 |     0 |         0 |\n|  4   | HttpRouter    | 31.88 |    32 |         1 |\n|  5   | Fiber         | 114.4 |     0 |         0 |\n|  6   | HttpTreeMux   | 165.0 |   352 |         3 |\n|  7   | Chi           | 332.2 |   704 |         4 |\n|  8   | Beego         | 348.8 |   352 |         3 |\n|  9   | Goji v2       | 494.3 | 1,136 |         8 |\n|  10  | GorillaMux    | 630.6 | 1,152 |         8 |\n|  11  | Macaron       | 708.0 | 1,064 |        10 |\n|  12  | GoRestful     | 1,394 | 4,600 |        15 |\n\n### 5 Params\n\nRoute: `/:a/:b/:c/:d/:e` — Request: `GET /test/test/test/test/test`\n\n| Rank | Router        | ns/op |  B/op | allocs/op |\n| :--: | :------------ | ----: | ----: | --------: |\n|  1   | **BunRouter** | 41.86 |     0 |         0 |\n|  2   | **Echo**      | 43.76 |     0 |         0 |\n|  3   | **Gin**       | 44.20 |     0 |         0 |\n|  4   | HttpRouter    | 83.74 |   160 |         1 |\n|  5   | Fiber         | 271.6 |     0 |         0 |\n|  6   | HttpTreeMux   | 358.8 |   576 |         6 |\n|  7   | Chi           | 453.7 |   704 |         4 |\n|  8   | Beego         | 480.3 |   352 |         3 |\n|  9   | Goji v2       | 532.4 | 1,200 |         8 |\n|  10  | Macaron       | 799.7 | 1,064 |        10 |\n|  11  | GorillaMux    | 972.6 | 1,216 |         8 |\n|  12  | GoRestful     | 1,579 | 4,712 |        15 |\n\n### 20 Params\n\nRoute: `/:a/:b/.../:t` (20 segments) — Request: `GET /a/b/.../t`\n\n| Rank | Router        | ns/op |  B/op | allocs/op |\n| :--: | :------------ | ----: | ----: | --------: |\n|  1   | **Gin**       | 121.7 |     0 |         0 |\n|  2   | **Echo**      | 127.5 |     0 |         0 |\n|  3   | **BunRouter** | 211.4 |     0 |         0 |\n|  4   | HttpRouter    | 290.2 |   704 |         1 |\n|  5   | Fiber         | 466.1 |     0 |         0 |\n|  6   | Goji v2       | 745.3 | 1,440 |         8 |\n|  7   | Beego         | 1,099 |   352 |         3 |\n|  8   | Chi           | 1,805 | 2,504 |         9 |\n|  9   | HttpTreeMux   | 1,857 | 3,144 |        13 |\n|  10  | Macaron       | 2,058 | 2,864 |        15 |\n|  11  | GorillaMux    | 2,223 | 3,272 |        13 |\n|  12  | GoRestful     | 3,337 | 7,008 |        20 |\n\n### Param Write\n\nRoute: `/user/:name` with response write — Request: `GET /user/gordon`\n\n| Rank | Router        | ns/op |  B/op | allocs/op |\n| :--: | :------------ | ----: | ----: | --------: |\n|  1   | **BunRouter** | 25.86 |     0 |         0 |\n|  2   | **Gin**       | 27.65 |     0 |         0 |\n|  3   | HttpRouter    | 37.40 |    32 |         1 |\n|  4   | Echo          | 47.94 |     8 |         1 |\n|  5   | Fiber         | 125.7 |     0 |         0 |\n|  6   | HttpTreeMux   | 180.4 |   352 |         3 |\n|  7   | Chi           | 348.3 |   704 |         4 |\n|  8   | Beego         | 386.1 |   360 |         4 |\n|  9   | Goji v2       | 516.9 | 1,168 |        10 |\n|  10  | GorillaMux    | 665.5 | 1,152 |         8 |\n|  11  | Macaron       | 784.3 | 1,112 |        13 |\n|  12  | GoRestful     | 1,534 | 4,608 |        16 |\n"
  },
  {
    "path": "CHANGELOG.md",
    "content": "# Gin ChangeLog\n\n## Gin v1.12.0\n\n### Features\n\n- feat(render): add bson protocol ([#4145](https://github.com/gin-gonic/gin/pull/4145))\n- feat(context): add GetError and GetErrorSlice methods for error retrieval ([#4502](https://github.com/gin-gonic/gin/pull/4502))\n- feat(binding): add support for encoding.UnmarshalText in uri/query binding ([#4203](https://github.com/gin-gonic/gin/pull/4203))\n- feat(gin): add option to use escaped path ([#4420](https://github.com/gin-gonic/gin/pull/4420))\n- feat(context): add Protocol Buffers support to content negotiation ([#4423](https://github.com/gin-gonic/gin/pull/4423))\n- feat(context): implemented Delete method ([#38e7651](https://github.com/gin-gonic/gin/commit/38e7651))\n- feat(logger): color latency ([#4146](https://github.com/gin-gonic/gin/pull/4146))\n\n### Enhancements\n\n- perf(tree): reduce allocations in findCaseInsensitivePath ([#4417](https://github.com/gin-gonic/gin/pull/4417))\n- perf(recovery): optimize line reading in stack function ([#4466](https://github.com/gin-gonic/gin/pull/4466))\n- perf(path): replace regex with custom functions in redirectTrailingSlash ([#4414](https://github.com/gin-gonic/gin/pull/4414))\n- perf(tree): optimize path parsing using strings.Count ([#4246](https://github.com/gin-gonic/gin/pull/4246))\n- chore(logger): allow skipping query string output ([#4547](https://github.com/gin-gonic/gin/pull/4547))\n- chore(context): always trust xff headers from unix socket ([#3359](https://github.com/gin-gonic/gin/pull/3359))\n- chore(response): prevent Flush() panic when the underlying ResponseWriter does not implement `http.Flusher` ([#4479](https://github.com/gin-gonic/gin/pull/4479))\n- refactor(recovery): smart error comparison ([#4142](https://github.com/gin-gonic/gin/pull/4142))\n- refactor(context): replace hardcoded localhost IPs with constants ([#4481](https://github.com/gin-gonic/gin/pull/4481))\n- refactor(utils): move util functions to utils.go ([#4467](https://github.com/gin-gonic/gin/pull/4467))\n- refactor(binding): use maps.Copy for cleaner map handling ([#4352](https://github.com/gin-gonic/gin/pull/4352))\n- refactor(context): using maps.Clone ([#4333](https://github.com/gin-gonic/gin/pull/4333))\n- refactor(ginS): use sync.OnceValue to simplify engine function ([#4314](https://github.com/gin-gonic/gin/pull/4314))\n- refactor: replace magic numbers with named constants in bodyAllowedForStatus ([#4529](https://github.com/gin-gonic/gin/pull/4529))\n- refactor: for loop can be modernized using range over int ([#4392](https://github.com/gin-gonic/gin/pull/4392))\n\n### Bug Fixes\n\n- fix(tree): panic in findCaseInsensitivePathRec with RedirectFixedPath ([#4535](https://github.com/gin-gonic/gin/pull/4535))\n- fix(render): write content length in Data.Render ([#4206](https://github.com/gin-gonic/gin/pull/4206))\n- fix(context): ClientIP handling for multiple X-Forwarded-For header values ([#4472](https://github.com/gin-gonic/gin/pull/4472))\n- fix(binding): empty value error ([#2169](https://github.com/gin-gonic/gin/pull/2169))\n- fix(recover): suppress http.ErrAbortHandler in recover ([#4336](https://github.com/gin-gonic/gin/pull/4336))\n- fix(gin): literal colon routes not working with engine.Handler() ([#4415](https://github.com/gin-gonic/gin/pull/4415))\n- fix(gin): close os.File in RunFd to prevent resource leak ([#4422](https://github.com/gin-gonic/gin/pull/4422))\n- fix(response): refine hijack behavior for response lifecycle ([#4373](https://github.com/gin-gonic/gin/pull/4373))\n- fix(binding): improve empty slice/array handling in form binding ([#4380](https://github.com/gin-gonic/gin/pull/4380))\n- fix(debug): version mismatch ([#4403](https://github.com/gin-gonic/gin/pull/4403))\n- fix: correct typos, improve documentation clarity, and remove dead code ([#4511](https://github.com/gin-gonic/gin/pull/4511))\n\n### Build process updates / CI\n\n- ci: update Go version support to 1.25+ across CI and docs ([#4550](https://github.com/gin-gonic/gin/pull/4550))\n- chore(binding): upgrade bson dependency to mongo-driver v2 ([#4549](https://github.com/gin-gonic/gin/pull/4549))\n\n## Gin v1.11.0\n\n### Features\n\n- feat(gin): Experimental support for HTTP/3 using quic-go/quic-go ([#3210](https://github.com/gin-gonic/gin/pull/3210))\n- feat(form): add array collection format in form binding ([#3986](https://github.com/gin-gonic/gin/pull/3986)), add custom string slice for form tag unmarshal ([#3970](https://github.com/gin-gonic/gin/pull/3970))\n- feat(binding): add BindPlain ([#3904](https://github.com/gin-gonic/gin/pull/3904))\n- feat(fs): Export, test and document OnlyFilesFS ([#3939](https://github.com/gin-gonic/gin/pull/3939))\n- feat(binding): add support for unixMilli and unixMicro ([#4190](https://github.com/gin-gonic/gin/pull/4190))\n- feat(form): Support default values for collections in form binding ([#4048](https://github.com/gin-gonic/gin/pull/4048))\n- feat(context): GetXxx added support for more go native types ([#3633](https://github.com/gin-gonic/gin/pull/3633))\n\n### Enhancements\n\n- perf(context): optimize getMapFromFormData performance ([#4339](https://github.com/gin-gonic/gin/pull/4339))\n- refactor(tree): replace string(/) with \"/\" in node.insertChild ([#4354](https://github.com/gin-gonic/gin/pull/4354))\n- refactor(render): remove headers parameter from writeHeader ([#4353](https://github.com/gin-gonic/gin/pull/4353))\n- refactor(context): simplify \"GetType()\" functions ([#4080](https://github.com/gin-gonic/gin/pull/4080))\n- refactor(slice): simplify SliceValidationError Error method ([#3910](https://github.com/gin-gonic/gin/pull/3910))\n- refactor(context):Avoid using filepath.Dir twice in SaveUploadedFile ([#4181](https://github.com/gin-gonic/gin/pull/4181))\n- refactor(context): refactor context handling and improve test robustness ([#4066](https://github.com/gin-gonic/gin/pull/4066))\n- refactor(binding): use strings.Cut to replace strings.Index ([#3522](https://github.com/gin-gonic/gin/pull/3522))\n- refactor(context): add an optional permission parameter to SaveUploadedFile ([#4068](https://github.com/gin-gonic/gin/pull/4068))\n- refactor(context): verify URL is Non-nil in initQueryCache() ([#3969](https://github.com/gin-gonic/gin/pull/3969))\n- refactor(context): YAML judgment logic in Negotiate ([#3966](https://github.com/gin-gonic/gin/pull/3966))\n- tree: replace the self-defined 'min' to official one ([#3975](https://github.com/gin-gonic/gin/pull/3975))\n- context: Remove redundant filepath.Dir usage ([#4181](https://github.com/gin-gonic/gin/pull/4181))\n\n### Bug Fixes\n\n- fix: prevent middleware re-entry issue in HandleContext ([#3987](https://github.com/gin-gonic/gin/pull/3987))\n- fix(binding): prevent duplicate decoding and add validation in decodeToml ([#4193](https://github.com/gin-gonic/gin/pull/4193))\n- fix(gin): Do not panic when handling method not allowed on empty tree ([#4003](https://github.com/gin-gonic/gin/pull/4003))\n- fix(gin): data race warning for gin mode ([#1580](https://github.com/gin-gonic/gin/pull/1580))\n- fix(context): verify URL is Non-nil in initQueryCache() ([#3969](https://github.com/gin-gonic/gin/pull/3969))\n- fix(context): YAML judgment logic in Negotiate ([#3966](https://github.com/gin-gonic/gin/pull/3966))\n- fix(context): check handler is nil ([#3413](https://github.com/gin-gonic/gin/pull/3413))\n- fix(readme): fix broken link to English documentation ([#4222](https://github.com/gin-gonic/gin/pull/4222))\n- fix(tree): Keep panic infos consistent when wildcard type build faild ([#4077](https://github.com/gin-gonic/gin/pull/4077))\n\n### Build process updates / CI\n\n- ci: integrate Trivy vulnerability scanning into CI workflow ([#4359](https://github.com/gin-gonic/gin/pull/4359))\n- ci: support Go 1.25 in CI/CD ([#4341](https://github.com/gin-gonic/gin/pull/4341))\n- build(deps): upgrade github.com/bytedance/sonic from v1.13.2 to v1.14.0 ([#4342](https://github.com/gin-gonic/gin/pull/4342))\n- ci: add Go version 1.24 to GitHub Actions ([#4154](https://github.com/gin-gonic/gin/pull/4154))\n- build: update Gin minimum Go version to 1.21 ([#3960](https://github.com/gin-gonic/gin/pull/3960))\n- ci(lint): enable new linters (testifylint, usestdlibvars, perfsprint, etc.) ([#4010](https://github.com/gin-gonic/gin/pull/4010), [#4091](https://github.com/gin-gonic/gin/pull/4091), [#4090](https://github.com/gin-gonic/gin/pull/4090))\n- ci(lint): update workflows and improve test request consistency ([#4126](https://github.com/gin-gonic/gin/pull/4126))\n\n### Dependency updates\n\n- chore(deps): bump google.golang.org/protobuf from 1.36.6 to 1.36.9 ([#4346](https://github.com/gin-gonic/gin/pull/4346), [#4356](https://github.com/gin-gonic/gin/pull/4356))\n- chore(deps): bump github.com/stretchr/testify from 1.10.0 to 1.11.1 ([#4347](https://github.com/gin-gonic/gin/pull/4347))\n- chore(deps): bump actions/setup-go from 5 to 6 ([#4351](https://github.com/gin-gonic/gin/pull/4351))\n- chore(deps): bump github.com/quic-go/quic-go from 0.53.0 to 0.54.0 ([#4328](https://github.com/gin-gonic/gin/pull/4328))\n- chore(deps): bump golang.org/x/net from 0.33.0 to 0.38.0 ([#4178](https://github.com/gin-gonic/gin/pull/4178), [#4221](https://github.com/gin-gonic/gin/pull/4221))\n- chore(deps): bump github.com/go-playground/validator/v10 from 10.20.0 to 10.22.1 ([#4052](https://github.com/gin-gonic/gin/pull/4052))\n\n### Documentation updates\n\n- docs(changelog): update release notes for Gin v1.10.1 ([#4360](https://github.com/gin-gonic/gin/pull/4360))\n- docs: Fixing English grammar mistakes and awkward sentence structure in doc/doc.md ([#4207](https://github.com/gin-gonic/gin/pull/4207))\n- docs: update documentation and release notes for Gin v1.10.0 ([#3953](https://github.com/gin-gonic/gin/pull/3953))\n- docs: fix typo in Gin Quick Start ([#3997](https://github.com/gin-gonic/gin/pull/3997))\n- docs: fix comment and link issues ([#4205](https://github.com/gin-gonic/gin/pull/4205), [#3938](https://github.com/gin-gonic/gin/pull/3938))\n- docs: fix route group example code ([#4020](https://github.com/gin-gonic/gin/pull/4020))\n- docs(readme): add Portuguese documentation ([#4078](https://github.com/gin-gonic/gin/pull/4078))\n- docs(context): fix some function names in comment ([#4079](https://github.com/gin-gonic/gin/pull/4079))\n\n---\n\n## Gin v1.10.1\n\n### Features\n\n- refactor: strengthen HTTPS security and improve code organization\n- feat(binding): Support custom BindUnmarshaler for binding. (#3933)\n\n### Enhancements\n\n- chore(deps): bump github.com/bytedance/sonic from 1.11.3 to 1.11.6 (#3940)\n- chore(deps): bump golangci/golangci-lint-action from 4 to 5 (#3941)\n- chore: update external dependencies to latest versions (#3950)\n- chore: update various Go dependencies to latest versions (#3901)\n- chore: refactor configuration files for better readability (#3951)\n- chore: update changelog categories and improve documentation (#3917)\n- feat: update version constant to v1.10.0 (#3952)\n\n### Build process updates\n\n- ci(release): refactor changelog regex patterns and exclusions (#3914)\n- ci(Makefile): vet command add .PHONY (#3915)\n\n## Gin v1.10.0\n\n### Features\n\n- feat(auth): add proxy-server authentication (#3877) (@EndlessParadox1)\n- feat(bind): ShouldBindBodyWith shortcut and change doc (#3871) (@RedCrazyGhost)\n- feat(binding): Support custom BindUnmarshaler for binding. (#3933) (@dkkb)\n- feat(binding): support override default binding implement (#3514) (@ssfyn)\n- feat(engine): Added `OptionFunc` and `With` (#3572) (@flc1125)\n- feat(logger): ability to skip logs based on user-defined logic (#3593) (@palvaneh)\n\n### Bug fixes\n\n- Revert \"fix(uri): query binding bug (#3236)\" (#3899) (@appleboy)\n- fix(binding): binding error while not upload file (#3819) (#3820) (@clearcodecn)\n- fix(binding): dereference pointer to struct (#3199) (@echovl)\n- fix(context): make context Value method adhere to Go standards (#3897) (@FarmerChillax)\n- fix(engine): fix unit test (#3878) (@flc1125)\n- fix(header): Allow header according to RFC 7231 (HTTP 405) (#3759) (@Crocmagnon)\n- fix(route): Add fullPath in context copy (#3784) (@KarthikReddyPuli)\n- fix(router): catch-all conflicting wildcard (#3812) (@FirePing32)\n- fix(sec): upgrade golang.org/x/crypto to 0.17.0 (#3832) (@chncaption)\n- fix(tree): correctly expand the capacity of params (#3502) (@georgijd-form3)\n- fix(uri): query binding bug (#3236) (@illiafox)\n- fix: Add pointer support for url query params (#3659) (#3666) (@omkar-foss)\n- fix: protect Context.Keys map when call Copy method (#3873) (@kingcanfish)\n\n### Enhancements\n\n- chore(CI): update release args (#3595) (@qloog)\n- chore(IP): add TrustedPlatform constant for Fly.io. (#3839) (@ab)\n- chore(debug): add ability to override the debugPrint statement (#2337) (@josegonzalez)\n- chore(deps): update dependencies to latest versions (#3835) (@appleboy)\n- chore(header): Add support for RFC 9512: application/yaml (#3851) (@vincentbernat)\n- chore(http): use white color for HTTP 1XX (#3741) (@viralparmarme)\n- chore(optimize): the ShouldBindUri method of the Context struct (#3911) (@1911860538)\n- chore(perf): Optimize the Copy method of the Context struct (#3859) (@1911860538)\n- chore(refactor): modify interface check way (#3855) (@demoManito)\n- chore(request): check reader if it's nil before reading (#3419) (@noahyao1024)\n- chore(security): upgrade Protobuf for CVE-2024-24786 (#3893) (@Fotkurz)\n- chore: refactor CI and update dependencies (#3848) (@appleboy)\n- chore: refactor configuration files for better readability (#3951) (@appleboy)\n- chore: update GitHub Actions configuration (#3792) (@appleboy)\n- chore: update changelog categories and improve documentation (#3917) (@appleboy)\n- chore: update dependencies to latest versions (#3694) (@appleboy)\n- chore: update external dependencies to latest versions (#3950) (@appleboy)\n- chore: update various Go dependencies to latest versions (#3901) (@appleboy)\n\n### Build process updates\n\n- build(codecov): Added a codecov configuration (#3891) (@flc1125)\n- ci(Makefile): vet command add .PHONY (#3915) (@imalasong)\n- ci(lint): update tooling and workflows for consistency (#3834) (@appleboy)\n- ci(release): refactor changelog regex patterns and exclusions (#3914) (@appleboy)\n- ci(testing): add go1.22 version (#3842) (@appleboy)\n\n### Documentation updates\n\n- docs(context): Added deprecation comments to BindWith (#3880) (@flc1125)\n- docs(middleware): comments to function `BasicAuthForProxy` (#3881) (@EndlessParadox1)\n- docs: Add document to constant `AuthProxyUserKey` and `BasicAuthForProxy`. (#3887) (@EndlessParadox1)\n- docs: fix typo in comment (#3868) (@testwill)\n- docs: fix typo in function documentation (#3872) (@TotomiEcio)\n- docs: remove redundant comments (#3765) (@WeiTheShinobi)\n- feat: update version constant to v1.10.0 (#3952) (@appleboy)\n\n### Others\n\n- Upgrade golang.org/x/net -> v0.13.0 (#3684) (@cpcf)\n- test(git): gitignore add develop tools (#3370) (@demoManito)\n- test(http): use constant instead of numeric literal (#3863) (@testwill)\n- test(path): Optimize unit test execution results (#3883) (@flc1125)\n- test(render): increased unit tests coverage (#3691) (@araujo88)\n\n## Gin v1.9.1\n\n### BUG FIXES\n\n- fix Request.Context() checks [#3512](https://github.com/gin-gonic/gin/pull/3512)\n\n### SECURITY\n\n- fix lack of escaping of filename in Content-Disposition [#3556](https://github.com/gin-gonic/gin/pull/3556)\n\n### ENHANCEMENTS\n\n- refactor: use bytes.ReplaceAll directly [#3455](https://github.com/gin-gonic/gin/pull/3455)\n- convert strings and slices using the officially recommended way [#3344](https://github.com/gin-gonic/gin/pull/3344)\n- improve render code coverage [#3525](https://github.com/gin-gonic/gin/pull/3525)\n\n### DOCS\n\n- docs: changed documentation link for trusted proxies [#3575](https://github.com/gin-gonic/gin/pull/3575)\n- chore: improve linting, testing, and GitHub Actions setup [#3583](https://github.com/gin-gonic/gin/pull/3583)\n\n## Gin v1.9.0\n\n### BREAK CHANGES\n\n- Stop useless panicking in context and render [#2150](https://github.com/gin-gonic/gin/pull/2150)\n\n### BUG FIXES\n\n- fix(router): tree bug where loop index is not decremented. [#3460](https://github.com/gin-gonic/gin/pull/3460)\n- fix(context): panic on NegotiateFormat - index out of range [#3397](https://github.com/gin-gonic/gin/pull/3397)\n- Add escape logic for header [#3500](https://github.com/gin-gonic/gin/pull/3500) and [#3503](https://github.com/gin-gonic/gin/pull/3503)\n\n### SECURITY\n\n- Fix the GO-2022-0969 and GO-2022-0288 vulnerabilities [#3333](https://github.com/gin-gonic/gin/pull/3333)\n- fix(security): vulnerability GO-2023-1571 [#3505](https://github.com/gin-gonic/gin/pull/3505)\n\n### ENHANCEMENTS\n\n- feat: add sonic json support [#3184](https://github.com/gin-gonic/gin/pull/3184)\n- chore(file): Creates a directory named path [#3316](https://github.com/gin-gonic/gin/pull/3316)\n- fix: modify interface check way [#3327](https://github.com/gin-gonic/gin/pull/3327)\n- remove deprecated of package io/ioutil [#3395](https://github.com/gin-gonic/gin/pull/3395)\n- refactor: avoid calling strings.ToLower twice [#3343](https://github.com/gin-gonic/gin/pull/3433)\n- console logger HTTP status code bug fixed [#3453](https://github.com/gin-gonic/gin/pull/3453)\n- chore(yaml): upgrade dependency to v3 version [#3456](https://github.com/gin-gonic/gin/pull/3456)\n- chore(router): match method added to routergroup for multiple HTTP methods supporting [#3464](https://github.com/gin-gonic/gin/pull/3464)\n- chore(http): add support for go1.20 http.rwUnwrapper to gin.responseWriter [#3489](https://github.com/gin-gonic/gin/pull/3489)\n\n### DOCS\n\n- docs: update markdown format [#3260](https://github.com/gin-gonic/gin/pull/3260)\n- docs(readme): Add the TOML rendering example [#3400](https://github.com/gin-gonic/gin/pull/3400)\n- docs(readme): move more example to docs/doc.md [#3449](https://github.com/gin-gonic/gin/pull/3449)\n- docs: update markdown format [#3446](https://github.com/gin-gonic/gin/pull/3446)\n\n## Gin v1.8.2\n\n### BUG FIXES\n\n- fix(route): redirectSlash bug ([#3227](<(https://github.com/gin-gonic/gin/pull/3227)>))\n- fix(engine): missing route params for CreateTestContext ([#2778](<(https://github.com/gin-gonic/gin/pull/2778)>)) ([#2803](<(https://github.com/gin-gonic/gin/pull/2803)>))\n\n### SECURITY\n\n- Fix the GO-2022-1144 vulnerability ([#3432](<(https://github.com/gin-gonic/gin/pull/3432)>))\n\n## Gin v1.8.1\n\n### ENHANCEMENTS\n\n- feat(context): add ContextWithFallback feature flag [#3172](https://github.com/gin-gonic/gin/pull/3172)\n\n## Gin v1.8.0\n\n### BREAK CHANGES\n\n- TrustedProxies: Add default IPv6 support and refactor [#2967](https://github.com/gin-gonic/gin/pull/2967). Please replace `RemoteIP() (net.IP, bool)` with `RemoteIP() net.IP`\n- gin.Context with fallback value from gin.Context.Request.Context() [#2751](https://github.com/gin-gonic/gin/pull/2751)\n\n### BUG FIXES\n\n- Fixed SetOutput() panics on go 1.17 [#2861](https://github.com/gin-gonic/gin/pull/2861)\n- Fix: wrong when wildcard follows named param [#2983](https://github.com/gin-gonic/gin/pull/2983)\n- Fix: missing sameSite when do context.reset() [#3123](https://github.com/gin-gonic/gin/pull/3123)\n\n### ENHANCEMENTS\n\n- Use Header() instead of deprecated HeaderMap [#2694](https://github.com/gin-gonic/gin/pull/2694)\n- RouterGroup.Handle regular match optimization of http method [#2685](https://github.com/gin-gonic/gin/pull/2685)\n- Add support go-json, another drop-in json replacement [#2680](https://github.com/gin-gonic/gin/pull/2680)\n- Use errors.New to replace fmt.Errorf will much better [#2707](https://github.com/gin-gonic/gin/pull/2707)\n- Use Duration.Truncate for truncating precision [#2711](https://github.com/gin-gonic/gin/pull/2711)\n- Get client IP when using Cloudflare [#2723](https://github.com/gin-gonic/gin/pull/2723)\n- Optimize code adjust [#2700](https://github.com/gin-gonic/gin/pull/2700/files)\n- Optimize code and reduce code cyclomatic complexity [#2737](https://github.com/gin-gonic/gin/pull/2737)\n- Improve sliceValidateError.Error performance [#2765](https://github.com/gin-gonic/gin/pull/2765)\n- Support custom struct tag [#2720](https://github.com/gin-gonic/gin/pull/2720)\n- Improve router group tests [#2787](https://github.com/gin-gonic/gin/pull/2787)\n- Fallback Context.Deadline() Context.Done() Context.Err() to Context.Request.Context() [#2769](https://github.com/gin-gonic/gin/pull/2769)\n- Some codes optimize [#2830](https://github.com/gin-gonic/gin/pull/2830) [#2834](https://github.com/gin-gonic/gin/pull/2834) [#2838](https://github.com/gin-gonic/gin/pull/2838) [#2837](https://github.com/gin-gonic/gin/pull/2837) [#2788](https://github.com/gin-gonic/gin/pull/2788) [#2848](https://github.com/gin-gonic/gin/pull/2848) [#2851](https://github.com/gin-gonic/gin/pull/2851) [#2701](https://github.com/gin-gonic/gin/pull/2701)\n- TrustedProxies: Add default IPv6 support and refactor [#2967](https://github.com/gin-gonic/gin/pull/2967)\n- Test(route): expose performRequest func [#3012](https://github.com/gin-gonic/gin/pull/3012)\n- Support h2c with prior knowledge [#1398](https://github.com/gin-gonic/gin/pull/1398)\n- Feat attachment filename support utf8 [#3071](https://github.com/gin-gonic/gin/pull/3071)\n- Feat: add StaticFileFS [#2749](https://github.com/gin-gonic/gin/pull/2749)\n- Feat(context): return GIN Context from Value method [#2825](https://github.com/gin-gonic/gin/pull/2825)\n- Feat: automatically SetMode to TestMode when run go test [#3139](https://github.com/gin-gonic/gin/pull/3139)\n- Add TOML bining for gin [#3081](https://github.com/gin-gonic/gin/pull/3081)\n- IPv6 add default trusted proxies [#3033](https://github.com/gin-gonic/gin/pull/3033)\n\n### DOCS\n\n- Add note about nomsgpack tag to the readme [#2703](https://github.com/gin-gonic/gin/pull/2703)\n\n## Gin v1.7.7\n\n### BUG FIXES\n\n- Fixed X-Forwarded-For unsafe handling of CVE-2020-28483 [#2844](https://github.com/gin-gonic/gin/pull/2844), closed issue [#2862](https://github.com/gin-gonic/gin/issues/2862).\n- Tree: updated the code logic for `latestNode` [#2897](https://github.com/gin-gonic/gin/pull/2897), closed issue [#2894](https://github.com/gin-gonic/gin/issues/2894) [#2878](https://github.com/gin-gonic/gin/issues/2878).\n- Tree: fixed the misplacement of adding slashes [#2847](https://github.com/gin-gonic/gin/pull/2847), closed issue [#2843](https://github.com/gin-gonic/gin/issues/2843).\n- Tree: fixed tsr with mixed static and wildcard paths [#2924](https://github.com/gin-gonic/gin/pull/2924), closed issue [#2918](https://github.com/gin-gonic/gin/issues/2918).\n\n### ENHANCEMENTS\n\n- TrustedProxies: make it backward-compatible [#2887](https://github.com/gin-gonic/gin/pull/2887), closed issue [#2819](https://github.com/gin-gonic/gin/issues/2819).\n- TrustedPlatform: provide custom options for another CDN services [#2906](https://github.com/gin-gonic/gin/pull/2906).\n\n### DOCS\n\n- NoMethod: added usage annotation ([#2832](https://github.com/gin-gonic/gin/pull/2832#issuecomment-929954463)).\n\n## Gin v1.7.6\n\n### BUG FIXES\n\n- bump new release to fix v1.7.5 release error by using v1.7.4 codes.\n\n## Gin v1.7.4\n\n### BUG FIXES\n\n- bump new release to fix checksum mismatch\n\n## Gin v1.7.3\n\n### BUG FIXES\n\n- fix level 1 router match [#2767](https://github.com/gin-gonic/gin/issues/2767), [#2796](https://github.com/gin-gonic/gin/issues/2796)\n\n## Gin v1.7.2\n\n### BUG FIXES\n\n- Fix conflict between param and exact path [#2706](https://github.com/gin-gonic/gin/issues/2706). Close issue [#2682](https://github.com/gin-gonic/gin/issues/2682) [#2696](https://github.com/gin-gonic/gin/issues/2696).\n\n## Gin v1.7.1\n\n### BUG FIXES\n\n- fix: data race with trustedCIDRs from [#2674](https://github.com/gin-gonic/gin/issues/2674)([#2675](https://github.com/gin-gonic/gin/pull/2675))\n\n## Gin v1.7.0\n\n### BUG FIXES\n\n- fix compile error from [#2572](https://github.com/gin-gonic/gin/pull/2572) ([#2600](https://github.com/gin-gonic/gin/pull/2600))\n- fix: print headers without Authorization header on broken pipe ([#2528](https://github.com/gin-gonic/gin/pull/2528))\n- fix(tree): reassign fullpath when register new node ([#2366](https://github.com/gin-gonic/gin/pull/2366))\n\n### ENHANCEMENTS\n\n- Support params and exact routes without creating conflicts ([#2663](https://github.com/gin-gonic/gin/pull/2663))\n- chore: improve render string performance ([#2365](https://github.com/gin-gonic/gin/pull/2365))\n- Sync route tree to httprouter latest code ([#2368](https://github.com/gin-gonic/gin/pull/2368))\n- chore: rename getQueryCache/getFormCache to initQueryCache/initFormCa ([#2375](https://github.com/gin-gonic/gin/pull/2375))\n- chore(performance): improve countParams ([#2378](https://github.com/gin-gonic/gin/pull/2378))\n- Remove some functions that have the same effect as the bytes package ([#2387](https://github.com/gin-gonic/gin/pull/2387))\n- update:SetMode function ([#2321](https://github.com/gin-gonic/gin/pull/2321))\n- remove an unused type SecureJSONPrefix ([#2391](https://github.com/gin-gonic/gin/pull/2391))\n- Add a redirect sample for POST method ([#2389](https://github.com/gin-gonic/gin/pull/2389))\n- Add CustomRecovery builtin middleware ([#2322](https://github.com/gin-gonic/gin/pull/2322))\n- binding: avoid 2038 problem on 32-bit architectures ([#2450](https://github.com/gin-gonic/gin/pull/2450))\n- Prevent panic in Context.GetQuery() when there is no Request ([#2412](https://github.com/gin-gonic/gin/pull/2412))\n- Add GetUint and GetUint64 method on gin.context ([#2487](https://github.com/gin-gonic/gin/pull/2487))\n- update content-disposition header to MIME-style ([#2512](https://github.com/gin-gonic/gin/pull/2512))\n- reduce allocs and improve the render `WriteString` ([#2508](https://github.com/gin-gonic/gin/pull/2508))\n- implement \".Unwrap() error\" on Error type ([#2525](https://github.com/gin-gonic/gin/pull/2525)) ([#2526](https://github.com/gin-gonic/gin/pull/2526))\n- Allow bind with a map[string]string ([#2484](https://github.com/gin-gonic/gin/pull/2484))\n- chore: update tree ([#2371](https://github.com/gin-gonic/gin/pull/2371))\n- Support binding for slice/array obj [Rewrite] ([#2302](https://github.com/gin-gonic/gin/pull/2302))\n- basic auth: fix timing oracle ([#2609](https://github.com/gin-gonic/gin/pull/2609))\n- Add mixed param and non-param paths (port of httprouter[#329](https://github.com/gin-gonic/gin/pull/329)) ([#2663](https://github.com/gin-gonic/gin/pull/2663))\n- feat(engine): add trustedproxies and remoteIP ([#2632](https://github.com/gin-gonic/gin/pull/2632))\n\n## Gin v1.6.3\n\n### ENHANCEMENTS\n\n- Improve performance: Change `*sync.RWMutex` to `sync.RWMutex` in context. [#2351](https://github.com/gin-gonic/gin/pull/2351)\n\n## Gin v1.6.2\n\n### BUG FIXES\n\n- fix missing initial sync.RWMutex [#2305](https://github.com/gin-gonic/gin/pull/2305)\n\n### ENHANCEMENTS\n\n- Add set samesite in cookie. [#2306](https://github.com/gin-gonic/gin/pull/2306)\n\n## Gin v1.6.1\n\n### BUG FIXES\n\n- Revert \"fix accept incoming network connections\" [#2294](https://github.com/gin-gonic/gin/pull/2294)\n\n## Gin v1.6.0\n\n### BREAKING\n\n- chore(performance): Improve performance for adding RemoveExtraSlash flag [#2159](https://github.com/gin-gonic/gin/pull/2159)\n- drop support govendor [#2148](https://github.com/gin-gonic/gin/pull/2148)\n- Added support for SameSite cookie flag [#1615](https://github.com/gin-gonic/gin/pull/1615)\n\n### FEATURES\n\n- add yaml negotiation [#2220](https://github.com/gin-gonic/gin/pull/2220)\n- FileFromFS [#2112](https://github.com/gin-gonic/gin/pull/2112)\n\n### BUG FIXES\n\n- Unix Socket Handling [#2280](https://github.com/gin-gonic/gin/pull/2280)\n- Use json marshall in context json to fix breaking new line issue. Fixes #2209 [#2228](https://github.com/gin-gonic/gin/pull/2228)\n- fix accept incoming network connections [#2216](https://github.com/gin-gonic/gin/pull/2216)\n- Fixed a bug in the calculation of the maximum number of parameters [#2166](https://github.com/gin-gonic/gin/pull/2166)\n- [FIX] allow empty headers on DataFromReader [#2121](https://github.com/gin-gonic/gin/pull/2121)\n- Add mutex for protect Context.Keys map [#1391](https://github.com/gin-gonic/gin/pull/1391)\n\n### ENHANCEMENTS\n\n- Add mitigation for log injection [#2277](https://github.com/gin-gonic/gin/pull/2277)\n- tree: range over nodes values [#2229](https://github.com/gin-gonic/gin/pull/2229)\n- tree: remove duplicate assignment [#2222](https://github.com/gin-gonic/gin/pull/2222)\n- chore: upgrade go-isatty and json-iterator/go [#2215](https://github.com/gin-gonic/gin/pull/2215)\n- path: sync code with httprouter [#2212](https://github.com/gin-gonic/gin/pull/2212)\n- Use zero-copy approach to convert types between string and byte slice [#2206](https://github.com/gin-gonic/gin/pull/2206)\n- Reuse bytes when cleaning the URL paths [#2179](https://github.com/gin-gonic/gin/pull/2179)\n- tree: remove one else statement [#2177](https://github.com/gin-gonic/gin/pull/2177)\n- tree: sync httprouter update (#2173) (#2172) [#2171](https://github.com/gin-gonic/gin/pull/2171)\n- tree: sync part httprouter codes and reduce if/else [#2163](https://github.com/gin-gonic/gin/pull/2163)\n- use http method constant [#2155](https://github.com/gin-gonic/gin/pull/2155)\n- upgrade go-validator to v10 [#2149](https://github.com/gin-gonic/gin/pull/2149)\n- Refactor redirect request in gin.go [#1970](https://github.com/gin-gonic/gin/pull/1970)\n- Add build tag nomsgpack [#1852](https://github.com/gin-gonic/gin/pull/1852)\n\n### DOCS\n\n- docs(path): improve comments [#2223](https://github.com/gin-gonic/gin/pull/2223)\n- Renew README to fit the modification of SetCookie method [#2217](https://github.com/gin-gonic/gin/pull/2217)\n- Fix spelling [#2202](https://github.com/gin-gonic/gin/pull/2202)\n- Remove broken link from README. [#2198](https://github.com/gin-gonic/gin/pull/2198)\n- Update docs on Context.Done(), Context.Deadline() and Context.Err() [#2196](https://github.com/gin-gonic/gin/pull/2196)\n- Update validator to v10 [#2190](https://github.com/gin-gonic/gin/pull/2190)\n- upgrade go-validator to v10 for README [#2189](https://github.com/gin-gonic/gin/pull/2189)\n- Update to currently output [#2188](https://github.com/gin-gonic/gin/pull/2188)\n- Fix \"Custom Validators\" example [#2186](https://github.com/gin-gonic/gin/pull/2186)\n- Add project to README [#2165](https://github.com/gin-gonic/gin/pull/2165)\n- docs(benchmarks): for gin v1.5 [#2153](https://github.com/gin-gonic/gin/pull/2153)\n- Changed wording for clarity in README.md [#2122](https://github.com/gin-gonic/gin/pull/2122)\n\n### MISC\n\n- ci support go1.14 [#2262](https://github.com/gin-gonic/gin/pull/2262)\n- chore: upgrade depend version [#2231](https://github.com/gin-gonic/gin/pull/2231)\n- Drop support go1.10 [#2147](https://github.com/gin-gonic/gin/pull/2147)\n- fix comment in `mode.go` [#2129](https://github.com/gin-gonic/gin/pull/2129)\n\n## Gin v1.5.0\n\n- [FIX] Use DefaultWriter and DefaultErrorWriter for debug messages [#1891](https://github.com/gin-gonic/gin/pull/1891)\n- [NEW] Now you can parse the inline lowercase start structure [#1893](https://github.com/gin-gonic/gin/pull/1893)\n- [FIX] Some code improvements [#1909](https://github.com/gin-gonic/gin/pull/1909)\n- [FIX] Use encode replace json marshal increase json encoder speed [#1546](https://github.com/gin-gonic/gin/pull/1546)\n- [NEW] Hold matched route full path in the Context [#1826](https://github.com/gin-gonic/gin/pull/1826)\n- [FIX] Fix context.Params race condition on Copy() [#1841](https://github.com/gin-gonic/gin/pull/1841)\n- [NEW] Add context param query cache [#1450](https://github.com/gin-gonic/gin/pull/1450)\n- [FIX] Improve GetQueryMap performance [#1918](https://github.com/gin-gonic/gin/pull/1918)\n- [FIX] Improve get post data [#1920](https://github.com/gin-gonic/gin/pull/1920)\n- [FIX] Use context instead of x/net/context [#1922](https://github.com/gin-gonic/gin/pull/1922)\n- [FIX] Attempt to fix PostForm cache bug [#1931](https://github.com/gin-gonic/gin/pull/1931)\n- [NEW] Add support of multipart multi files [#1949](https://github.com/gin-gonic/gin/pull/1949)\n- [NEW] Support bind http header param [#1957](https://github.com/gin-gonic/gin/pull/1957)\n- [FIX] Drop support for go1.8 and go1.9 [#1933](https://github.com/gin-gonic/gin/pull/1933)\n- [FIX] Bugfix for the FullPath feature [#1919](https://github.com/gin-gonic/gin/pull/1919)\n- [FIX] Gin1.5 bytes.Buffer to strings.Builder [#1939](https://github.com/gin-gonic/gin/pull/1939)\n- [FIX] Upgrade github.com/ugorji/go/codec [#1969](https://github.com/gin-gonic/gin/pull/1969)\n- [NEW] Support bind unix time [#1980](https://github.com/gin-gonic/gin/pull/1980)\n- [FIX] Simplify code [#2004](https://github.com/gin-gonic/gin/pull/2004)\n- [NEW] Support negative Content-Length in DataFromReader [#1981](https://github.com/gin-gonic/gin/pull/1981)\n- [FIX] Identify terminal on a RISC-V architecture for auto-colored logs [#2019](https://github.com/gin-gonic/gin/pull/2019)\n- [BREAKING] `Context.JSONP()` now expects a semicolon (`;`) at the end [#2007](https://github.com/gin-gonic/gin/pull/2007)\n- [BREAKING] Upgrade default `binding.Validator` to v9 (see [its changelog](https://github.com/go-playground/validator/releases/tag/v9.0.0)) [#1015](https://github.com/gin-gonic/gin/pull/1015)\n- [NEW] Add `DisallowUnknownFields()` in `Context.BindJSON()` [#2028](https://github.com/gin-gonic/gin/pull/2028)\n- [NEW] Use specific `net.Listener` with `Engine.RunListener()` [#2023](https://github.com/gin-gonic/gin/pull/2023)\n- [FIX] Fix some typo [#2079](https://github.com/gin-gonic/gin/pull/2079) [#2080](https://github.com/gin-gonic/gin/pull/2080)\n- [FIX] Relocate binding body tests [#2086](https://github.com/gin-gonic/gin/pull/2086)\n- [FIX] Use Writer in Context.Status [#1606](https://github.com/gin-gonic/gin/pull/1606)\n- [FIX] `Engine.RunUnix()` now returns the error if it can't change the file mode [#2093](https://github.com/gin-gonic/gin/pull/2093)\n- [FIX] `RouterGroup.StaticFS()` leaked files. Now it closes them. [#2118](https://github.com/gin-gonic/gin/pull/2118)\n- [FIX] `Context.Request.FormFile` leaked file. Now it closes it. [#2114](https://github.com/gin-gonic/gin/pull/2114)\n- [FIX] Ignore walking on `form:\"-\"` mapping [#1943](https://github.com/gin-gonic/gin/pull/1943)\n\n### Gin v1.4.0\n\n- [NEW] Support for [Go Modules](https://github.com/golang/go/wiki/Modules) [#1569](https://github.com/gin-gonic/gin/pull/1569)\n- [NEW] Refactor of form mapping multipart request [#1829](https://github.com/gin-gonic/gin/pull/1829)\n- [FIX] Truncate Latency precision in long running request [#1830](https://github.com/gin-gonic/gin/pull/1830)\n- [FIX] IsTerm flag should not be affected by DisableConsoleColor method. [#1802](https://github.com/gin-gonic/gin/pull/1802)\n- [NEW] Supporting file binding [#1264](https://github.com/gin-gonic/gin/pull/1264)\n- [NEW] Add support for mapping arrays [#1797](https://github.com/gin-gonic/gin/pull/1797)\n- [FIX] Readme updates [#1793](https://github.com/gin-gonic/gin/pull/1793) [#1788](https://github.com/gin-gonic/gin/pull/1788) [1789](https://github.com/gin-gonic/gin/pull/1789)\n- [FIX] StaticFS: Fixed Logging two log lines on 404. [#1805](https://github.com/gin-gonic/gin/pull/1805), [#1804](https://github.com/gin-gonic/gin/pull/1804)\n- [NEW] Make context.Keys available as LogFormatterParams [#1779](https://github.com/gin-gonic/gin/pull/1779)\n- [NEW] Use internal/json for Marshal/Unmarshal [#1791](https://github.com/gin-gonic/gin/pull/1791)\n- [NEW] Support mapping time.Duration [#1794](https://github.com/gin-gonic/gin/pull/1794)\n- [NEW] Refactor form mappings [#1749](https://github.com/gin-gonic/gin/pull/1749)\n- [NEW] Added flag to context.Stream indicates if client disconnected in middle of stream [#1252](https://github.com/gin-gonic/gin/pull/1252)\n- [FIX] Moved [examples](https://github.com/gin-gonic/examples) to stand alone Repo [#1775](https://github.com/gin-gonic/gin/pull/1775)\n- [NEW] Extend context.File to allow for the content-disposition attachments via a new method context.Attachment [#1260](https://github.com/gin-gonic/gin/pull/1260)\n- [FIX] Support HTTP content negotiation wildcards [#1112](https://github.com/gin-gonic/gin/pull/1112)\n- [NEW] Add prefix from X-Forwarded-Prefix in redirectTrailingSlash [#1238](https://github.com/gin-gonic/gin/pull/1238)\n- [FIX] context.Copy() race condition [#1020](https://github.com/gin-gonic/gin/pull/1020)\n- [NEW] Add context.HandlerNames() [#1729](https://github.com/gin-gonic/gin/pull/1729)\n- [FIX] Change color methods to public in the defaultLogger. [#1771](https://github.com/gin-gonic/gin/pull/1771)\n- [FIX] Update writeHeaders method to use http.Header.Set [#1722](https://github.com/gin-gonic/gin/pull/1722)\n- [NEW] Add response size to LogFormatterParams [#1752](https://github.com/gin-gonic/gin/pull/1752)\n- [NEW] Allow ignoring field on form mapping [#1733](https://github.com/gin-gonic/gin/pull/1733)\n- [NEW] Add a function to force color in console output. [#1724](https://github.com/gin-gonic/gin/pull/1724)\n- [FIX] Context.Next() - recheck len of handlers on every iteration. [#1745](https://github.com/gin-gonic/gin/pull/1745)\n- [FIX] Fix all errcheck warnings [#1739](https://github.com/gin-gonic/gin/pull/1739) [#1653](https://github.com/gin-gonic/gin/pull/1653)\n- [NEW] context: inherits context cancellation and deadline from http.Request context for Go>=1.7 [#1690](https://github.com/gin-gonic/gin/pull/1690)\n- [NEW] Binding for URL Params [#1694](https://github.com/gin-gonic/gin/pull/1694)\n- [NEW] Add LoggerWithFormatter method [#1677](https://github.com/gin-gonic/gin/pull/1677)\n- [FIX] CI testing updates [#1671](https://github.com/gin-gonic/gin/pull/1671) [#1670](https://github.com/gin-gonic/gin/pull/1670) [#1682](https://github.com/gin-gonic/gin/pull/1682) [#1669](https://github.com/gin-gonic/gin/pull/1669)\n- [FIX] StaticFS(): Send 404 when path does not exist [#1663](https://github.com/gin-gonic/gin/pull/1663)\n- [FIX] Handle nil body for JSON binding [#1638](https://github.com/gin-gonic/gin/pull/1638)\n- [FIX] Support bind uri param [#1612](https://github.com/gin-gonic/gin/pull/1612)\n- [FIX] recovery: fix issue with syscall import on google app engine [#1640](https://github.com/gin-gonic/gin/pull/1640)\n- [FIX] Make sure the debug log contains line breaks [#1650](https://github.com/gin-gonic/gin/pull/1650)\n- [FIX] Panic stack trace being printed during recovery of broken pipe [#1089](https://github.com/gin-gonic/gin/pull/1089) [#1259](https://github.com/gin-gonic/gin/pull/1259)\n- [NEW] RunFd method to run http.Server through a file descriptor [#1609](https://github.com/gin-gonic/gin/pull/1609)\n- [NEW] Yaml binding support [#1618](https://github.com/gin-gonic/gin/pull/1618)\n- [FIX] Pass MaxMultipartMemory when FormFile is called [#1600](https://github.com/gin-gonic/gin/pull/1600)\n- [FIX] LoadHTML\\* tests [#1559](https://github.com/gin-gonic/gin/pull/1559)\n- [FIX] Removed use of sync.pool from HandleContext [#1565](https://github.com/gin-gonic/gin/pull/1565)\n- [FIX] Format output log to os.Stderr [#1571](https://github.com/gin-gonic/gin/pull/1571)\n- [FIX] Make logger use a yellow background and a darkgray text for legibility [#1570](https://github.com/gin-gonic/gin/pull/1570)\n- [FIX] Remove sensitive request information from panic log. [#1370](https://github.com/gin-gonic/gin/pull/1370)\n- [FIX] log.Println() does not print timestamp [#829](https://github.com/gin-gonic/gin/pull/829) [#1560](https://github.com/gin-gonic/gin/pull/1560)\n- [NEW] Add PureJSON renderer [#694](https://github.com/gin-gonic/gin/pull/694)\n- [FIX] Add missing copyright and update if/else [#1497](https://github.com/gin-gonic/gin/pull/1497)\n- [FIX] Update msgpack usage [#1498](https://github.com/gin-gonic/gin/pull/1498)\n- [FIX] Use protobuf on render [#1496](https://github.com/gin-gonic/gin/pull/1496)\n- [FIX] Add support for Protobuf format response [#1479](https://github.com/gin-gonic/gin/pull/1479)\n- [NEW] Set default time format in form binding [#1487](https://github.com/gin-gonic/gin/pull/1487)\n- [FIX] Add BindXML and ShouldBindXML [#1485](https://github.com/gin-gonic/gin/pull/1485)\n- [NEW] Upgrade dependency libraries [#1491](https://github.com/gin-gonic/gin/pull/1491)\n\n## Gin v1.3.0\n\n- [NEW] Add [`func (*Context) QueryMap`](https://godoc.org/github.com/gin-gonic/gin#Context.QueryMap), [`func (*Context) GetQueryMap`](https://godoc.org/github.com/gin-gonic/gin#Context.GetQueryMap), [`func (*Context) PostFormMap`](https://godoc.org/github.com/gin-gonic/gin#Context.PostFormMap) and [`func (*Context) GetPostFormMap`](https://godoc.org/github.com/gin-gonic/gin#Context.GetPostFormMap) to support `type map[string]string` as query string or form parameters, see [#1383](https://github.com/gin-gonic/gin/pull/1383)\n- [NEW] Add [`func (*Context) AsciiJSON`](https://godoc.org/github.com/gin-gonic/gin#Context.AsciiJSON), see [#1358](https://github.com/gin-gonic/gin/pull/1358)\n- [NEW] Add `Pusher()` in [`type ResponseWriter`](https://godoc.org/github.com/gin-gonic/gin#ResponseWriter) for supporting http2 push, see [#1273](https://github.com/gin-gonic/gin/pull/1273)\n- [NEW] Add [`func (*Context) DataFromReader`](https://godoc.org/github.com/gin-gonic/gin#Context.DataFromReader) for serving dynamic data, see [#1304](https://github.com/gin-gonic/gin/pull/1304)\n- [NEW] Add [`func (*Context) ShouldBindBodyWith`](https://godoc.org/github.com/gin-gonic/gin#Context.ShouldBindBodyWith) allowing to call binding multiple times, see [#1341](https://github.com/gin-gonic/gin/pull/1341)\n- [NEW] Support pointers in form binding, see [#1336](https://github.com/gin-gonic/gin/pull/1336)\n- [NEW] Add [`func (*Context) JSONP`](https://godoc.org/github.com/gin-gonic/gin#Context.JSONP), see [#1333](https://github.com/gin-gonic/gin/pull/1333)\n- [NEW] Support default value in form binding, see [#1138](https://github.com/gin-gonic/gin/pull/1138)\n- [NEW] Expose validator engine in [`type StructValidator`](https://godoc.org/github.com/gin-gonic/gin/binding#StructValidator), see [#1277](https://github.com/gin-gonic/gin/pull/1277)\n- [NEW] Add [`func (*Context) ShouldBind`](https://godoc.org/github.com/gin-gonic/gin#Context.ShouldBind), [`func (*Context) ShouldBindQuery`](https://godoc.org/github.com/gin-gonic/gin#Context.ShouldBindQuery) and [`func (*Context) ShouldBindJSON`](https://godoc.org/github.com/gin-gonic/gin#Context.ShouldBindJSON), see [#1047](https://github.com/gin-gonic/gin/pull/1047)\n- [NEW] Add support for `time.Time` location in form binding, see [#1117](https://github.com/gin-gonic/gin/pull/1117)\n- [NEW] Add [`func (*Context) BindQuery`](https://godoc.org/github.com/gin-gonic/gin#Context.BindQuery), see [#1029](https://github.com/gin-gonic/gin/pull/1029)\n- [NEW] Make [jsonite](https://github.com/json-iterator/go) optional with build tags, see [#1026](https://github.com/gin-gonic/gin/pull/1026)\n- [NEW] Show query string in logger, see [#999](https://github.com/gin-gonic/gin/pull/999)\n- [NEW] Add [`func (*Context) SecureJSON`](https://godoc.org/github.com/gin-gonic/gin#Context.SecureJSON), see [#987](https://github.com/gin-gonic/gin/pull/987) and [#993](https://github.com/gin-gonic/gin/pull/993)\n- [DEPRECATE] `func (*Context) GetCookie` for [`func (*Context) Cookie`](https://godoc.org/github.com/gin-gonic/gin#Context.Cookie)\n- [FIX] Don't display color tags if [`func DisableConsoleColor`](https://godoc.org/github.com/gin-gonic/gin#DisableConsoleColor) called, see [#1072](https://github.com/gin-gonic/gin/pull/1072)\n- [FIX] Gin Mode `\"\"` when calling [`func Mode`](https://godoc.org/github.com/gin-gonic/gin#Mode) now returns `const DebugMode`, see [#1250](https://github.com/gin-gonic/gin/pull/1250)\n- [FIX] `Flush()` now doesn't overwrite `responseWriter` status code, see [#1460](https://github.com/gin-gonic/gin/pull/1460)\n\n## Gin 1.2.0\n\n- [NEW] Switch from godeps to govendor\n- [NEW] Add support for Let's Encrypt via gin-gonic/autotls\n- [NEW] Improve README examples and add extra at examples folder\n- [NEW] Improved support with App Engine\n- [NEW] Add custom template delimiters, see #860\n- [NEW] Add Template Func Maps, see #962\n- [NEW] Add \\*context.Handler(), see #928\n- [NEW] Add \\*context.GetRawData()\n- [NEW] Add \\*context.GetHeader() (request)\n- [NEW] Add \\*context.AbortWithStatusJSON() (JSON content type)\n- [NEW] Add \\*context.Keys type cast helpers\n- [NEW] Add \\*context.ShouldBindWith()\n- [NEW] Add \\*context.MustBindWith()\n- [NEW] Add \\*engine.SetFuncMap()\n- [DEPRECATE] On next release: \\*context.BindWith(), see #855\n- [FIX] Refactor render\n- [FIX] Reworked tests\n- [FIX] logger now supports cygwin\n- [FIX] Use X-Forwarded-For before X-Real-IP\n- [FIX] time.Time binding (#904)\n\n## Gin 1.1.4\n\n- [NEW] Support google appengine for IsTerminal func\n\n## Gin 1.1.3\n\n- [FIX] Reverted Logger: skip ANSI color commands\n\n## Gin 1.1\n\n- [NEW] Implement QueryArray and PostArray methods\n- [NEW] Refactor GetQuery and GetPostForm\n- [NEW] Add contribution guide\n- [FIX] Corrected typos in README\n- [FIX] Removed additional Iota\n- [FIX] Changed imports to gopkg instead of github in README (#733)\n- [FIX] Logger: skip ANSI color commands if output is not a tty\n\n## Gin 1.0rc2 (...)\n\n- [PERFORMANCE] Fast path for writing Content-Type.\n- [PERFORMANCE] Much faster 404 routing\n- [PERFORMANCE] Allocation optimizations\n- [PERFORMANCE] Faster root tree lookup\n- [PERFORMANCE] Zero overhead, String() and JSON() rendering.\n- [PERFORMANCE] Faster ClientIP parsing\n- [PERFORMANCE] Much faster SSE implementation\n- [NEW] Benchmarks suite\n- [NEW] Bind validation can be disabled and replaced with custom validators.\n- [NEW] More flexible HTML render\n- [NEW] Multipart and PostForm bindings\n- [NEW] Adds method to return all the registered routes\n- [NEW] Context.HandlerName() returns the main handler's name\n- [NEW] Adds Error.IsType() helper\n- [FIX] Binding multipart form\n- [FIX] Integration tests\n- [FIX] Crash when binding non struct object in Context.\n- [FIX] RunTLS() implementation\n- [FIX] Logger() unit tests\n- [FIX] Adds SetHTMLTemplate() warning\n- [FIX] Context.IsAborted()\n- [FIX] More unit tests\n- [FIX] JSON, XML, HTML renders accept custom content-types\n- [FIX] gin.AbortIndex is unexported\n- [FIX] Better approach to avoid directory listing in StaticFS()\n- [FIX] Context.ClientIP() always returns the IP with trimmed spaces.\n- [FIX] Better warning when running in debug mode.\n- [FIX] Google App Engine integration. debugPrint does not use os.Stdout\n- [FIX] Fixes integer overflow in error type\n- [FIX] Error implements the json.Marshaller interface\n- [FIX] MIT license in every file\n\n## Gin 1.0rc1 (May 22, 2015)\n\n- [PERFORMANCE] Zero allocation router\n- [PERFORMANCE] Faster JSON, XML and text rendering\n- [PERFORMANCE] Custom hand optimized HttpRouter for Gin\n- [PERFORMANCE] Misc code optimizations. Inlining, tail call optimizations\n- [NEW] Built-in support for golang.org/x/net/context\n- [NEW] Any(path, handler). Create a route that matches any path\n- [NEW] Refactored rendering pipeline (faster and static typed)\n- [NEW] Refactored errors API\n- [NEW] IndentedJSON() prints pretty JSON\n- [NEW] Added gin.DefaultWriter\n- [NEW] UNIX socket support\n- [NEW] RouterGroup.BasePath is exposed\n- [NEW] JSON validation using go-validate-yourself (very powerful options)\n- [NEW] Completed suite of unit tests\n- [NEW] HTTP streaming with c.Stream()\n- [NEW] StaticFile() creates a router for serving just one file.\n- [NEW] StaticFS() has an option to disable directory listing.\n- [NEW] StaticFS() for serving static files through virtual filesystems\n- [NEW] Server-Sent Events native support\n- [NEW] WrapF() and WrapH() helpers for wrapping http.HandlerFunc and http.Handler\n- [NEW] Added LoggerWithWriter() middleware\n- [NEW] Added RecoveryWithWriter() middleware\n- [NEW] Added DefaultPostFormValue()\n- [NEW] Added DefaultFormValue()\n- [NEW] Added DefaultParamValue()\n- [FIX] BasicAuth() when using custom realm\n- [FIX] Bug when serving static files in nested routing group\n- [FIX] Redirect using built-in http.Redirect()\n- [FIX] Logger when printing the requested path\n- [FIX] Documentation typos\n- [FIX] Context.Engine renamed to Context.engine\n- [FIX] Better debugging messages\n- [FIX] ErrorLogger\n- [FIX] Debug HTTP render\n- [FIX] Refactored binding and render modules\n- [FIX] Refactored Context initialization\n- [FIX] Refactored BasicAuth()\n- [FIX] NoMethod/NoRoute handlers\n- [FIX] Hijacking http\n- [FIX] Better support for Google App Engine (using log instead of fmt)\n\n## Gin 0.6 (Mar 9, 2015)\n\n- [NEW] Support multipart/form-data\n- [NEW] NoMethod handler\n- [NEW] Validate sub structures\n- [NEW] Support for HTTP Realm Auth\n- [FIX] Unsigned integers in binding\n- [FIX] Improve color logger\n\n## Gin 0.5 (Feb 7, 2015)\n\n- [NEW] Content Negotiation\n- [FIX] Solved security bug that allow a client to spoof ip\n- [FIX] Fix unexported/ignored fields in binding\n\n## Gin 0.4 (Aug 21, 2014)\n\n- [NEW] Development mode\n- [NEW] Unit tests\n- [NEW] Add Content.Redirect()\n- [FIX] Deferring WriteHeader()\n- [FIX] Improved documentation for model binding\n\n## Gin 0.3 (Jul 18, 2014)\n\n- [PERFORMANCE] Normal log and error log are printed in the same call.\n- [PERFORMANCE] Improve performance of NoRouter()\n- [PERFORMANCE] Improve context's memory locality, reduce CPU cache faults.\n- [NEW] Flexible rendering API\n- [NEW] Add Context.File()\n- [NEW] Add shortcut RunTLS() for http.ListenAndServeTLS\n- [FIX] Rename NotFound404() to NoRoute()\n- [FIX] Errors in context are purged\n- [FIX] Adds HEAD method in Static file serving\n- [FIX] Refactors Static() file serving\n- [FIX] Using keyed initialization to fix app-engine integration\n- [FIX] Can't unmarshal JSON array, #63\n- [FIX] Renaming Context.Req to Context.Request\n- [FIX] Check application/x-www-form-urlencoded when parsing form\n\n## Gin 0.2b (Jul 08, 2014)\n\n- [PERFORMANCE] Using sync.Pool to allocatio/gc overhead\n- [NEW] Travis CI integration\n- [NEW] Completely new logger\n- [NEW] New API for serving static files. gin.Static()\n- [NEW] gin.H() can be serialized into XML\n- [NEW] Typed errors. Errors can be typed. Internet/external/custom.\n- [NEW] Support for Godeps\n- [NEW] Travis/Godocs badges in README\n- [NEW] New Bind() and BindWith() methods for parsing request body.\n- [NEW] Add Content.Copy()\n- [NEW] Add context.LastError()\n- [NEW] Add shortcut for OPTIONS HTTP method\n- [FIX] Tons of README fixes\n- [FIX] Header is written before body\n- [FIX] BasicAuth() and changes API a little bit\n- [FIX] Recovery() middleware only prints panics\n- [FIX] Context.Get() does not panic anymore. Use MustGet() instead.\n- [FIX] Multiple http.WriteHeader() in NotFound handlers\n- [FIX] Engine.Run() panics if http server can't be set up\n- [FIX] Crash when route path doesn't start with '/'\n- [FIX] Do not update header when status code is negative\n- [FIX] Setting response headers before calling WriteHeader in context.String()\n- [FIX] Add MIT license\n- [FIX] Changes behaviour of ErrorLogger() and Logger()\n"
  },
  {
    "path": "CODE_OF_CONDUCT.md",
    "content": "# Contributor Covenant Code of Conduct\n\n## Our Pledge\n\nIn the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation.\n\n## Our Standards\n\nExamples of behavior that contributes to creating a positive environment include:\n\n* Using welcoming and inclusive language\n* Being respectful of differing viewpoints and experiences\n* Gracefully accepting constructive criticism\n* Focusing on what is best for the community\n* Showing empathy towards other community members\n\nExamples of unacceptable behavior by participants include:\n\n* The use of sexualized language or imagery and unwelcome sexual attention or advances\n* Trolling, insulting/derogatory comments, and personal or political attacks\n* Public or private harassment\n* Publishing others' private information, such as a physical or electronic address, without explicit permission\n* Other conduct which could reasonably be considered inappropriate in a professional setting\n\n## Our Responsibilities\n\nProject maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior.\n\nProject maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful.\n\n## Scope\n\nThis Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers.\n\n## Enforcement\n\nInstances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at teamgingonic@gmail.com. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately.\n\nProject maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership.\n\n## Attribution\n\nThis Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version]\n\n[homepage]: http://contributor-covenant.org\n[version]: http://contributor-covenant.org/version/1/4/\n"
  },
  {
    "path": "CONTRIBUTING.md",
    "content": "# Contributing\n\nWe welcome both issue reports and pull requests! Please follow these guidelines to help maintainers respond effectively.\n\n## Issues\n\n- **Before opening a new issue:**\n  - Use the search tool to check for existing issues or feature requests.\n  - Review existing issues and provide feedback or react to them.\n  - Use English for all communications — it is the language all maintainers read and write.\n  - For questions, configuration or deployment problems, please use the [Discussions Forum](https://github.com/gin-gonic/gin/discussions).\n  - For bug reports involving sensitive security issues, email <appleboy.tw@gmail.com> instead of posting publicly.\n\n- **Reporting a bug:**\n  - Please provide a clear description of your issue, and a minimal reproducible code example if possible.\n  - Include the Gin version (or commit reference), Go version, and operating system.\n  - Indicate whether you can reproduce the bug and describe steps to do so.\n  - Attach relevant logs per [Logging Documentation](https://docs.gitea.com/administration/logging-config#collecting-logs-for-help).\n\n- **Feature requests:**\n  - Before opening a request, check that a similar idea hasn’t already been suggested.\n  - Clearly describe your proposed feature and its benefits.\n\n_For API Documentation, User Guides, and more, see:_\n\n- [Go.dev API Documentation](https://pkg.go.dev/github.com/gin-gonic/gin)\n- [Gin User Guides](https://gin-gonic.com/)\n- [Discussions Forum](https://github.com/gin-gonic/gin/discussions)\n\n## Pull Requests\n\nPlease ensure your pull request meets the following requirements:\n\n- Open your pull request against the `master` branch.\n- Your pull request should have no more than two commits — squash them if necessary.\n- All tests pass in available continuous integration systems (e.g., GitHub Actions).\n- Add or modify tests to cover your code changes.\n- If your pull request introduces a new feature, document it in [`docs/doc.md`](docs/doc.md), not in the README.\n- Follow the checklist in the [Pull Request Template](.github/PULL_REQUEST_TEMPLATE.md).\n\nThank you for contributing!\n"
  },
  {
    "path": "LICENSE",
    "content": "The MIT License (MIT)\n\nCopyright (c) 2014-present Manuel Martínez-Almeida\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in\nall copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\nTHE SOFTWARE.\n"
  },
  {
    "path": "Makefile",
    "content": "GO ?= go\nGOFMT ?= gofmt \"-s\"\nGO_VERSION=$(shell $(GO) version | cut -c 14- | cut -d' ' -f1 | cut -d'.' -f2)\nPACKAGES ?= $(shell $(GO) list ./...)\nVETPACKAGES ?= $(shell $(GO) list ./... | grep -v /examples/)\nGOFILES := $(shell find . -name \"*.go\")\nTESTFOLDER := $(shell $(GO) list ./... | grep -E 'gin$$|ginS$$|binding$$|render$$' | grep -v examples)\nTESTTAGS ?= \"\"\n\n.PHONY: test\n# Run tests to verify code functionality.\ntest:\n\techo \"mode: count\" > coverage.out\n\tfor d in $(TESTFOLDER); do \\\n\t\tif [ -n \"$(TESTTAGS)\" ]; then \\\n\t\t\t$(GO) test $(TESTTAGS) -v -covermode=count -coverprofile=profile.out $$d > tmp.out; \\\n\t\telse \\\n\t\t\t$(GO) test -v -covermode=count -coverprofile=profile.out $$d > tmp.out; \\\n\t\tfi; \\\n\t\tcat tmp.out; \\\n\t\tif grep -q \"^--- FAIL\" tmp.out; then \\\n\t\t\trm tmp.out; \\\n\t\t\texit 1; \\\n\t\telif grep -q \"build failed\" tmp.out; then \\\n\t\t\trm tmp.out; \\\n\t\t\texit 1; \\\n\t\telif grep -q \"setup failed\" tmp.out; then \\\n\t\t\trm tmp.out; \\\n\t\t\texit 1; \\\n\t\tfi; \\\n\t\tif [ -f profile.out ]; then \\\n\t\t\tcat profile.out | grep -v \"mode:\" >> coverage.out; \\\n\t\t\trm profile.out; \\\n\t\tfi; \\\n\tdone\n\n.PHONY: fmt\n# Ensure consistent code formatting.\nfmt:\n\t$(GOFMT) -w $(GOFILES)\n\n.PHONY: fmt-check\n# format (check only).\nfmt-check:\n\t@diff=$$($(GOFMT) -d $(GOFILES)); \\\n\tif [ -n \"$$diff\" ]; then \\\n\t\techo \"Please run 'make fmt' and commit the result:\"; \\\n\t\techo \"$${diff}\"; \\\n\t\texit 1; \\\n\tfi;\n\n.PHONY: vet\n# Examine packages and report suspicious constructs if any.\nvet:\n\t$(GO) vet $(VETPACKAGES)\n\n.PHONY: lint\n# Inspect source code for stylistic errors or potential bugs.\nlint:\n\t@hash golint > /dev/null 2>&1; if [ $$? -ne 0 ]; then \\\n\t\t$(GO) get -u golang.org/x/lint/golint; \\\n\tfi\n\tfor PKG in $(PACKAGES); do golint -set_exit_status $$PKG || exit 1; done;\n\n.PHONY: misspell\n# Correct commonly misspelled English words in source code.\nmisspell:\n\t@hash misspell > /dev/null 2>&1; if [ $$? -ne 0 ]; then \\\n\t\t$(GO) get -u github.com/client9/misspell/cmd/misspell; \\\n\tfi\n\tmisspell -w $(GOFILES)\n\n.PHONY: misspell-check\n# misspell (check only).\nmisspell-check:\n\t@hash misspell > /dev/null 2>&1; if [ $$? -ne 0 ]; then \\\n\t\t$(GO) get -u github.com/client9/misspell/cmd/misspell; \\\n\tfi\n\tmisspell -error $(GOFILES)\n\n.PHONY: tools\n# Install tools (golint and misspell).\ntools:\n\t@if [ $(GO_VERSION) -gt 15 ]; then \\\n\t\t$(GO) install golang.org/x/lint/golint@latest; \\\n\t\t$(GO) install github.com/client9/misspell/cmd/misspell@latest; \\\n\telif [ $(GO_VERSION) -lt 16 ]; then \\\n\t\t$(GO) install golang.org/x/lint/golint; \\\n\t\t$(GO) install github.com/client9/misspell/cmd/misspell; \\\n\tfi\n\n.PHONY: help\n# Help.\nhelp:\n\t@echo ''\n\t@echo 'Usage:'\n\t@echo ' make [target]'\n\t@echo ''\n\t@echo 'Targets:'\n\t@awk '/^[a-zA-Z\\-\\0-9]+:/ { \\\n\thelpMessage = match(lastLine, /^# (.*)/); \\\n\t\tif (helpMessage) { \\\n\t\t\thelpCommand = substr($$1, 0, index($$1, \":\")-1); \\\n\t\t\thelpMessage = substr(lastLine, RSTART + 2, RLENGTH); \\\n\t\t\tprintf \" - \\033[36m%-20s\\033[0m %s\\n\", helpCommand, helpMessage; \\\n\t\t} \\\n\t} \\\n\t{ lastLine = $$0 }' $(MAKEFILE_LIST)\n\n.DEFAULT_GOAL := help\n"
  },
  {
    "path": "README.md",
    "content": "# Gin Web Framework\n\n<img align=\"right\" width=\"159px\" src=\"https://raw.githubusercontent.com/gin-gonic/logo/master/color.png\">\n\n[![Build Status](https://github.com/gin-gonic/gin/actions/workflows/gin.yml/badge.svg?branch=master)](https://github.com/gin-gonic/gin/actions/workflows/gin.yml)\n[![Trivy Security Scan](https://github.com/gin-gonic/gin/actions/workflows/trivy-scan.yml/badge.svg)](https://github.com/gin-gonic/gin/actions/workflows/trivy-scan.yml)\n[![codecov](https://codecov.io/gh/gin-gonic/gin/branch/master/graph/badge.svg)](https://codecov.io/gh/gin-gonic/gin)\n[![Go Report Card](https://goreportcard.com/badge/github.com/gin-gonic/gin)](https://goreportcard.com/report/github.com/gin-gonic/gin)\n[![Go Reference](https://pkg.go.dev/badge/github.com/gin-gonic/gin?status.svg)](https://pkg.go.dev/github.com/gin-gonic/gin?tab=doc)\n[![Sourcegraph](https://sourcegraph.com/github.com/gin-gonic/gin/-/badge.svg)](https://sourcegraph.com/github.com/gin-gonic/gin?badge)\n[![Open Source Helpers](https://www.codetriage.com/gin-gonic/gin/badges/users.svg)](https://www.codetriage.com/gin-gonic/gin)\n[![Release](https://img.shields.io/github/release/gin-gonic/gin.svg?style=flat-square)](https://github.com/gin-gonic/gin/releases)\n\n## 📰 Gin 1.12.0 is now available!\n\nWe're excited to announce the release of **[Gin 1.12.0](https://gin-gonic.com/en/blog/news/gin-1-12-0-release-announcement/)**! This release brings new features, performance improvements, and important bug fixes. Check out the [release announcement](https://gin-gonic.com/en/blog/news/gin-1-12-0-release-announcement/) on our official blog for the full details.\n\n---\n\nGin 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.\n\n**Why choose Gin?**\n\nGin combines the simplicity of Express.js-style routing with Go's performance characteristics, making it ideal for:\n\n- Building high-throughput REST APIs\n- Developing microservices that need to handle many concurrent requests\n- Creating web applications that require fast response times\n- Prototyping web services quickly with minimal boilerplate\n\n**Gin's key features:**\n\n- **Zero allocation router** - Extremely memory-efficient routing with no heap allocations\n- **High performance** - Benchmarks show superior speed compared to other Go web frameworks\n- **Middleware support** - Extensible middleware system for authentication, logging, CORS, etc.\n- **Crash-free** - Built-in recovery middleware prevents panics from crashing your server\n- **JSON validation** - Automatic request/response JSON binding and validation\n- **Route grouping** - Organize related routes and apply common middleware\n- **Error management** - Centralized error handling and logging\n- **Built-in rendering** - Support for JSON, XML, HTML templates, and more\n- **Extensible** - Large ecosystem of community middleware and plugins\n\n## Getting Started\n\n### Prerequisites\n\n- **Go version**: Gin requires [Go](https://go.dev/) version [1.25](https://go.dev/doc/devel/release#go1.25.0) or above\n- **Basic Go knowledge**: Familiarity with Go syntax and package management is helpful\n\n### Installation\n\nWith [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:\n\n```go\nimport \"github.com/gin-gonic/gin\"\n```\n\n### Your First Gin Application\n\nHere's a complete example that demonstrates Gin's simplicity:\n\n```go\npackage main\n\nimport (\n  \"log\"\n  \"net/http\"\n\n  \"github.com/gin-gonic/gin\"\n)\n\nfunc main() {\n  // Create a Gin router with default middleware (logger and recovery)\n  r := gin.Default()\n\n  // Define a simple GET endpoint\n  r.GET(\"/ping\", func(c *gin.Context) {\n    // Return JSON response\n    c.JSON(http.StatusOK, gin.H{\n      \"message\": \"pong\",\n    })\n  })\n\n  // Start server on port 8080 (default)\n  // Server will listen on 0.0.0.0:8080 (localhost:8080 on Windows)\n  if err := r.Run(); err != nil {\n    log.Fatalf(\"failed to run server: %v\", err)\n  }\n}\n```\n\n**Running the application:**\n\n1. Save the code above as `main.go`\n2. Run the application:\n\n   ```sh\n   go run main.go\n   ```\n\n3. Open your browser and visit [`http://localhost:8080/ping`](http://localhost:8080/ping)\n4. You should see: `{\"message\":\"pong\"}`\n\n**What this example demonstrates:**\n\n- Creating a Gin router with default middleware\n- Defining HTTP endpoints with simple handler functions\n- Returning JSON responses\n- Starting an HTTP server\n\n### Next Steps\n\nAfter running your first Gin application, explore these resources to learn more:\n\n#### 📚 Learning Resources\n\n- **[Gin Quick Start Guide](docs/doc.md)** - Comprehensive tutorial with API examples and build configurations\n- **[Example Repository](https://github.com/gin-gonic/examples)** - Ready-to-run examples demonstrating various Gin use cases:\n  - REST API development\n  - Authentication & middleware\n  - File uploads and downloads\n  - WebSocket connections\n  - Template rendering\n\n## 📖 Documentation\n\n### API Reference\n\n- **[Go.dev API Documentation](https://pkg.go.dev/github.com/gin-gonic/gin)** - Complete API reference with examples\n\n### User Guides\n\nThe comprehensive documentation is available on [gin-gonic.com](https://gin-gonic.com) in multiple languages:\n\n- [English](https://gin-gonic.com/en/docs/) | [简体中文](https://gin-gonic.com/zh-cn/docs/) | [繁體中文](https://gin-gonic.com/zh-tw/docs/)\n- [日本語](https://gin-gonic.com/ja/docs/) | [한국어](https://gin-gonic.com/ko-kr/docs/) | [Español](https://gin-gonic.com/es/docs/)\n- [Turkish](https://gin-gonic.com/tr/docs/) | [Persian](https://gin-gonic.com/fa/docs/) | [Português](https://gin-gonic.com/pt/docs/)\n- [Russian](https://gin-gonic.com/ru/docs/) | [Indonesian](https://gin-gonic.com/id/docs/)\n\n### Official Tutorials\n\n- [Go.dev Tutorial: Developing a RESTful API with Go and Gin](https://go.dev/doc/tutorial/web-service-gin)\n\n## ⚡ Performance Benchmarks\n\nGin 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)\n\n**Gin vs. Other Go Frameworks** (GitHub API routing benchmark):\n\n| Benchmark name                 |       (1) |             (2) |          (3) |             (4) |\n| ------------------------------ | --------: | --------------: | -----------: | --------------: |\n| BenchmarkGin_GithubAll         | **43550** | **27364 ns/op** |   **0 B/op** | **0 allocs/op** |\n| BenchmarkAce_GithubAll         |     40543 |     29670 ns/op |       0 B/op |     0 allocs/op |\n| BenchmarkAero_GithubAll        |     57632 |     20648 ns/op |       0 B/op |     0 allocs/op |\n| BenchmarkBear_GithubAll        |      9234 |    216179 ns/op |   86448 B/op |   943 allocs/op |\n| BenchmarkBeego_GithubAll       |      7407 |    243496 ns/op |   71456 B/op |   609 allocs/op |\n| BenchmarkBone_GithubAll        |       420 |   2922835 ns/op |  720160 B/op |  8620 allocs/op |\n| BenchmarkChi_GithubAll         |      7620 |    238331 ns/op |   87696 B/op |   609 allocs/op |\n| BenchmarkDenco_GithubAll       |     18355 |     64494 ns/op |   20224 B/op |   167 allocs/op |\n| BenchmarkEcho_GithubAll        |     31251 |     38479 ns/op |       0 B/op |     0 allocs/op |\n| BenchmarkGocraftWeb_GithubAll  |      4117 |    300062 ns/op |  131656 B/op |  1686 allocs/op |\n| BenchmarkGoji_GithubAll        |      3274 |    416158 ns/op |   56112 B/op |   334 allocs/op |\n| BenchmarkGojiv2_GithubAll      |      1402 |    870518 ns/op |  352720 B/op |  4321 allocs/op |\n| BenchmarkGoJsonRest_GithubAll  |      2976 |    401507 ns/op |  134371 B/op |  2737 allocs/op |\n| BenchmarkGoRestful_GithubAll   |       410 |   2913158 ns/op |  910144 B/op |  2938 allocs/op |\n| BenchmarkGorillaMux_GithubAll  |       346 |   3384987 ns/op |  251650 B/op |  1994 allocs/op |\n| BenchmarkGowwwRouter_GithubAll |     10000 |    143025 ns/op |   72144 B/op |   501 allocs/op |\n| BenchmarkHttpRouter_GithubAll  |     55938 |     21360 ns/op |       0 B/op |     0 allocs/op |\n| BenchmarkHttpTreeMux_GithubAll |     10000 |    153944 ns/op |   65856 B/op |   671 allocs/op |\n| BenchmarkKocha_GithubAll       |     10000 |    106315 ns/op |   23304 B/op |   843 allocs/op |\n| BenchmarkLARS_GithubAll        |     47779 |     25084 ns/op |       0 B/op |     0 allocs/op |\n| BenchmarkMacaron_GithubAll     |      3266 |    371907 ns/op |  149409 B/op |  1624 allocs/op |\n| BenchmarkMartini_GithubAll     |       331 |   3444706 ns/op |  226551 B/op |  2325 allocs/op |\n| BenchmarkPat_GithubAll         |       273 |   4381818 ns/op | 1483152 B/op | 26963 allocs/op |\n| BenchmarkPossum_GithubAll      |     10000 |    164367 ns/op |   84448 B/op |   609 allocs/op |\n| BenchmarkR2router_GithubAll    |     10000 |    160220 ns/op |   77328 B/op |   979 allocs/op |\n| BenchmarkRivet_GithubAll       |     14625 |     82453 ns/op |   16272 B/op |   167 allocs/op |\n| BenchmarkTango_GithubAll       |      6255 |    279611 ns/op |   63826 B/op |  1618 allocs/op |\n| BenchmarkTigerTonic_GithubAll  |      2008 |    687874 ns/op |  193856 B/op |  4474 allocs/op |\n| BenchmarkTraffic_GithubAll     |       355 |   3478508 ns/op |  820744 B/op | 14114 allocs/op |\n| BenchmarkVulcan_GithubAll      |      6885 |    193333 ns/op |   19894 B/op |   609 allocs/op |\n\n- (1): Total Repetitions achieved in constant time, higher means more confident result\n- (2): Single Repetition Duration (ns/op), lower is better\n- (3): Heap Memory (B/op), lower is better\n- (4): Average Allocations per Repetition (allocs/op), lower is better\n\n## 🔌 Middleware Ecosystem\n\nGin has a rich ecosystem of middleware for common web development needs. Explore community-contributed middleware:\n\n- **[gin-contrib](https://github.com/gin-contrib)** - Official middleware collection including:\n  - Authentication (JWT, Basic Auth, Sessions)\n  - CORS, Rate limiting, Compression\n  - Logging, Metrics, Tracing\n  - Static file serving, Template engines\n- **[gin-gonic/contrib](https://github.com/gin-gonic/contrib)** - Additional community middleware\n\n## 🏢 Production Usage\n\nGin powers many high-traffic applications and services in production:\n\n- **[gorush](https://github.com/appleboy/gorush)** - High-performance push notification server\n- **[fnproject](https://github.com/fnproject/fn)** - Container-native, serverless platform\n- **[photoprism](https://github.com/photoprism/photoprism)** - AI-powered personal photo management\n- **[lura](https://github.com/luraproject/lura)** - Ultra-performant API Gateway framework\n- **[picfit](https://github.com/thoas/picfit)** - Real-time image processing server\n- **[dkron](https://github.com/distribworks/dkron)** - Distributed job scheduling system\n\n## 🤝 Contributing\n\nGin is the work of hundreds of contributors from around the world. We welcome and appreciate your contributions! See the full list of [contributors](https://github.com/gin-gonic/gin/graphs/contributors).\n\n### How to Contribute\n\n- 🐛 **Report bugs** - Help us identify and fix issues\n- 💡 **Suggest features** - Share your ideas for improvements\n- 📝 **Improve documentation** - Help make our docs clearer\n- 🔧 **Submit code** - Fix bugs or implement new features\n- 🧪 **Write tests** - Improve our test coverage\n\n### Getting Started with Contributing\n\n1. Check out our [CONTRIBUTING.md](CONTRIBUTING.md) for detailed guidelines\n2. Join our community discussions and ask questions\n\n**All contributions are valued and help make Gin better for everyone!**\n"
  },
  {
    "path": "auth.go",
    "content": "// Copyright 2014 Manu Martinez-Almeida. All rights reserved.\n// Use of this source code is governed by a MIT style\n// license that can be found in the LICENSE file.\n\npackage gin\n\nimport (\n\t\"crypto/subtle\"\n\t\"encoding/base64\"\n\t\"net/http\"\n\t\"strconv\"\n\n\t\"github.com/gin-gonic/gin/internal/bytesconv\"\n)\n\n// AuthUserKey is the cookie name for user credential in basic auth.\nconst AuthUserKey = \"user\"\n\n// AuthProxyUserKey is the cookie name for proxy_user credential in basic auth for proxy.\nconst AuthProxyUserKey = \"proxy_user\"\n\n// Accounts defines a key/value for user/pass list of authorized logins.\ntype Accounts map[string]string\n\ntype authPair struct {\n\tvalue string\n\tuser  string\n}\n\ntype authPairs []authPair\n\nfunc (a authPairs) searchCredential(authValue string) (string, bool) {\n\tif authValue == \"\" {\n\t\treturn \"\", false\n\t}\n\tfor _, pair := range a {\n\t\tif subtle.ConstantTimeCompare(bytesconv.StringToBytes(pair.value), bytesconv.StringToBytes(authValue)) == 1 {\n\t\t\treturn pair.user, true\n\t\t}\n\t}\n\treturn \"\", false\n}\n\n// BasicAuthForRealm returns a Basic HTTP Authorization middleware. It takes as arguments a map[string]string where\n// the key is the user name and the value is the password, as well as the name of the Realm.\n// If the realm is empty, \"Authorization Required\" will be used by default.\n// (see http://tools.ietf.org/html/rfc2617#section-1.2)\nfunc BasicAuthForRealm(accounts Accounts, realm string) HandlerFunc {\n\tif realm == \"\" {\n\t\trealm = \"Authorization Required\"\n\t}\n\trealm = \"Basic realm=\" + strconv.Quote(realm)\n\tpairs := processAccounts(accounts)\n\treturn func(c *Context) {\n\t\t// Search user in the slice of allowed credentials\n\t\tuser, found := pairs.searchCredential(c.requestHeader(\"Authorization\"))\n\t\tif !found {\n\t\t\t// Credentials doesn't match, we return 401 and abort handlers chain.\n\t\t\tc.Header(\"WWW-Authenticate\", realm)\n\t\t\tc.AbortWithStatus(http.StatusUnauthorized)\n\t\t\treturn\n\t\t}\n\n\t\t// The user credentials was found, set user's id to key AuthUserKey in this context, the user's id can be read later using\n\t\t// c.MustGet(gin.AuthUserKey).\n\t\tc.Set(AuthUserKey, user)\n\t}\n}\n\n// BasicAuth returns a Basic HTTP Authorization middleware. It takes as argument a map[string]string where\n// the key is the user name and the value is the password.\nfunc BasicAuth(accounts Accounts) HandlerFunc {\n\treturn BasicAuthForRealm(accounts, \"\")\n}\n\nfunc processAccounts(accounts Accounts) authPairs {\n\tlength := len(accounts)\n\tassert1(length > 0, \"Empty list of authorized credentials\")\n\tpairs := make(authPairs, 0, length)\n\tfor user, password := range accounts {\n\t\tassert1(user != \"\", \"User can not be empty\")\n\t\tvalue := authorizationHeader(user, password)\n\t\tpairs = append(pairs, authPair{\n\t\t\tvalue: value,\n\t\t\tuser:  user,\n\t\t})\n\t}\n\treturn pairs\n}\n\nfunc authorizationHeader(user, password string) string {\n\tbase := user + \":\" + password\n\treturn \"Basic \" + base64.StdEncoding.EncodeToString(bytesconv.StringToBytes(base))\n}\n\n// BasicAuthForProxy returns a Basic HTTP Proxy-Authorization middleware.\n// If the realm is empty, \"Proxy Authorization Required\" will be used by default.\nfunc BasicAuthForProxy(accounts Accounts, realm string) HandlerFunc {\n\tif realm == \"\" {\n\t\trealm = \"Proxy Authorization Required\"\n\t}\n\trealm = \"Basic realm=\" + strconv.Quote(realm)\n\tpairs := processAccounts(accounts)\n\treturn func(c *Context) {\n\t\tproxyUser, found := pairs.searchCredential(c.requestHeader(\"Proxy-Authorization\"))\n\t\tif !found {\n\t\t\t// Credentials doesn't match, we return 407 and abort handlers chain.\n\t\t\tc.Header(\"Proxy-Authenticate\", realm)\n\t\t\tc.AbortWithStatus(http.StatusProxyAuthRequired)\n\t\t\treturn\n\t\t}\n\t\t// The proxy_user credentials was found, set proxy_user's id to key AuthProxyUserKey in this context, the proxy_user's id can be read later using\n\t\t// c.MustGet(gin.AuthProxyUserKey).\n\t\tc.Set(AuthProxyUserKey, proxyUser)\n\t}\n}\n"
  },
  {
    "path": "auth_test.go",
    "content": "// Copyright 2014 Manu Martinez-Almeida. All rights reserved.\n// Use of this source code is governed by a MIT style\n// license that can be found in the LICENSE file.\n\npackage gin\n\nimport (\n\t\"encoding/base64\"\n\t\"net/http\"\n\t\"net/http/httptest\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n)\n\nfunc TestBasicAuth(t *testing.T) {\n\tpairs := processAccounts(Accounts{\n\t\t\"admin\": \"password\",\n\t\t\"foo\":   \"bar\",\n\t\t\"bar\":   \"foo\",\n\t})\n\n\tassert.Len(t, pairs, 3)\n\tassert.Contains(t, pairs, authPair{\n\t\tuser:  \"bar\",\n\t\tvalue: \"Basic YmFyOmZvbw==\",\n\t})\n\tassert.Contains(t, pairs, authPair{\n\t\tuser:  \"foo\",\n\t\tvalue: \"Basic Zm9vOmJhcg==\",\n\t})\n\tassert.Contains(t, pairs, authPair{\n\t\tuser:  \"admin\",\n\t\tvalue: \"Basic YWRtaW46cGFzc3dvcmQ=\",\n\t})\n}\n\nfunc TestBasicAuthFails(t *testing.T) {\n\tassert.Panics(t, func() { processAccounts(nil) })\n\tassert.Panics(t, func() {\n\t\tprocessAccounts(Accounts{\n\t\t\t\"\":    \"password\",\n\t\t\t\"foo\": \"bar\",\n\t\t})\n\t})\n}\n\nfunc TestBasicAuthSearchCredential(t *testing.T) {\n\tpairs := processAccounts(Accounts{\n\t\t\"admin\": \"password\",\n\t\t\"foo\":   \"bar\",\n\t\t\"bar\":   \"foo\",\n\t})\n\n\tuser, found := pairs.searchCredential(authorizationHeader(\"admin\", \"password\"))\n\tassert.Equal(t, \"admin\", user)\n\tassert.True(t, found)\n\n\tuser, found = pairs.searchCredential(authorizationHeader(\"foo\", \"bar\"))\n\tassert.Equal(t, \"foo\", user)\n\tassert.True(t, found)\n\n\tuser, found = pairs.searchCredential(authorizationHeader(\"bar\", \"foo\"))\n\tassert.Equal(t, \"bar\", user)\n\tassert.True(t, found)\n\n\tuser, found = pairs.searchCredential(authorizationHeader(\"admins\", \"password\"))\n\tassert.Empty(t, user)\n\tassert.False(t, found)\n\n\tuser, found = pairs.searchCredential(authorizationHeader(\"foo\", \"bar \"))\n\tassert.Empty(t, user)\n\tassert.False(t, found)\n\n\tuser, found = pairs.searchCredential(\"\")\n\tassert.Empty(t, user)\n\tassert.False(t, found)\n}\n\nfunc TestBasicAuthAuthorizationHeader(t *testing.T) {\n\tassert.Equal(t, \"Basic YWRtaW46cGFzc3dvcmQ=\", authorizationHeader(\"admin\", \"password\"))\n}\n\nfunc TestBasicAuthSucceed(t *testing.T) {\n\taccounts := Accounts{\"admin\": \"password\"}\n\trouter := New()\n\trouter.Use(BasicAuth(accounts))\n\trouter.GET(\"/login\", func(c *Context) {\n\t\tc.String(http.StatusOK, c.MustGet(AuthUserKey).(string))\n\t})\n\n\tw := httptest.NewRecorder()\n\treq, _ := http.NewRequest(http.MethodGet, \"/login\", nil)\n\treq.Header.Set(\"Authorization\", authorizationHeader(\"admin\", \"password\"))\n\trouter.ServeHTTP(w, req)\n\n\tassert.Equal(t, http.StatusOK, w.Code)\n\tassert.Equal(t, \"admin\", w.Body.String())\n}\n\nfunc TestBasicAuth401(t *testing.T) {\n\tcalled := false\n\taccounts := Accounts{\"foo\": \"bar\"}\n\trouter := New()\n\trouter.Use(BasicAuth(accounts))\n\trouter.GET(\"/login\", func(c *Context) {\n\t\tcalled = true\n\t\tc.String(http.StatusOK, c.MustGet(AuthUserKey).(string))\n\t})\n\n\tw := httptest.NewRecorder()\n\treq, _ := http.NewRequest(http.MethodGet, \"/login\", nil)\n\treq.Header.Set(\"Authorization\", \"Basic \"+base64.StdEncoding.EncodeToString([]byte(\"admin:password\")))\n\trouter.ServeHTTP(w, req)\n\n\tassert.False(t, called)\n\tassert.Equal(t, http.StatusUnauthorized, w.Code)\n\tassert.Equal(t, \"Basic realm=\\\"Authorization Required\\\"\", w.Header().Get(\"WWW-Authenticate\"))\n}\n\nfunc TestBasicAuth401WithCustomRealm(t *testing.T) {\n\tcalled := false\n\taccounts := Accounts{\"foo\": \"bar\"}\n\trouter := New()\n\trouter.Use(BasicAuthForRealm(accounts, \"My Custom \\\"Realm\\\"\"))\n\trouter.GET(\"/login\", func(c *Context) {\n\t\tcalled = true\n\t\tc.String(http.StatusOK, c.MustGet(AuthUserKey).(string))\n\t})\n\n\tw := httptest.NewRecorder()\n\treq, _ := http.NewRequest(http.MethodGet, \"/login\", nil)\n\treq.Header.Set(\"Authorization\", \"Basic \"+base64.StdEncoding.EncodeToString([]byte(\"admin:password\")))\n\trouter.ServeHTTP(w, req)\n\n\tassert.False(t, called)\n\tassert.Equal(t, http.StatusUnauthorized, w.Code)\n\tassert.Equal(t, \"Basic realm=\\\"My Custom \\\\\\\"Realm\\\\\\\"\\\"\", w.Header().Get(\"WWW-Authenticate\"))\n}\n\nfunc TestBasicAuthForProxySucceed(t *testing.T) {\n\taccounts := Accounts{\"admin\": \"password\"}\n\trouter := New()\n\trouter.Use(BasicAuthForProxy(accounts, \"\"))\n\trouter.Any(\"/*proxyPath\", func(c *Context) {\n\t\tc.String(http.StatusOK, c.MustGet(AuthProxyUserKey).(string))\n\t})\n\n\tw := httptest.NewRecorder()\n\treq, _ := http.NewRequest(http.MethodGet, \"/test\", nil)\n\treq.Header.Set(\"Proxy-Authorization\", authorizationHeader(\"admin\", \"password\"))\n\trouter.ServeHTTP(w, req)\n\n\tassert.Equal(t, http.StatusOK, w.Code)\n\tassert.Equal(t, \"admin\", w.Body.String())\n}\n\nfunc TestBasicAuthForProxy407(t *testing.T) {\n\tcalled := false\n\taccounts := Accounts{\"foo\": \"bar\"}\n\trouter := New()\n\trouter.Use(BasicAuthForProxy(accounts, \"\"))\n\trouter.Any(\"/*proxyPath\", func(c *Context) {\n\t\tcalled = true\n\t\tc.String(http.StatusOK, c.MustGet(AuthProxyUserKey).(string))\n\t})\n\n\tw := httptest.NewRecorder()\n\treq, _ := http.NewRequest(http.MethodGet, \"/test\", nil)\n\treq.Header.Set(\"Proxy-Authorization\", \"Basic \"+base64.StdEncoding.EncodeToString([]byte(\"admin:password\")))\n\trouter.ServeHTTP(w, req)\n\n\tassert.False(t, called)\n\tassert.Equal(t, http.StatusProxyAuthRequired, w.Code)\n\tassert.Equal(t, \"Basic realm=\\\"Proxy Authorization Required\\\"\", w.Header().Get(\"Proxy-Authenticate\"))\n}\n"
  },
  {
    "path": "benchmarks_test.go",
    "content": "// Copyright 2017 Manu Martinez-Almeida. All rights reserved.\n// Use of this source code is governed by a MIT style\n// license that can be found in the LICENSE file.\n\npackage gin\n\nimport (\n\t\"html/template\"\n\t\"net/http\"\n\t\"os\"\n\t\"testing\"\n)\n\nfunc BenchmarkOneRoute(B *testing.B) {\n\trouter := New()\n\trouter.GET(\"/ping\", func(c *Context) {})\n\trunRequest(B, router, http.MethodGet, \"/ping\")\n}\n\nfunc BenchmarkRecoveryMiddleware(B *testing.B) {\n\trouter := New()\n\trouter.Use(Recovery())\n\trouter.GET(\"/\", func(c *Context) {})\n\trunRequest(B, router, http.MethodGet, \"/\")\n}\n\nfunc BenchmarkLoggerMiddleware(B *testing.B) {\n\trouter := New()\n\trouter.Use(LoggerWithWriter(newMockWriter()))\n\trouter.GET(\"/\", func(c *Context) {})\n\trunRequest(B, router, http.MethodGet, \"/\")\n}\n\nfunc BenchmarkManyHandlers(B *testing.B) {\n\trouter := New()\n\trouter.Use(Recovery(), LoggerWithWriter(newMockWriter()))\n\trouter.Use(func(c *Context) {})\n\trouter.Use(func(c *Context) {})\n\trouter.GET(\"/ping\", func(c *Context) {})\n\trunRequest(B, router, http.MethodGet, \"/ping\")\n}\n\nfunc Benchmark5Params(B *testing.B) {\n\tDefaultWriter = os.Stdout\n\trouter := New()\n\trouter.Use(func(c *Context) {})\n\trouter.GET(\"/param/:param1/:params2/:param3/:param4/:param5\", func(c *Context) {})\n\trunRequest(B, router, http.MethodGet, \"/param/path/to/parameter/john/12345\")\n}\n\nfunc BenchmarkOneRouteJSON(B *testing.B) {\n\trouter := New()\n\tdata := struct {\n\t\tStatus string `json:\"status\"`\n\t}{\"ok\"}\n\trouter.GET(\"/json\", func(c *Context) {\n\t\tc.JSON(http.StatusOK, data)\n\t})\n\trunRequest(B, router, http.MethodGet, \"/json\")\n}\n\nfunc BenchmarkOneRouteHTML(B *testing.B) {\n\trouter := New()\n\tt := template.Must(template.New(\"index\").Parse(`\n\t\t<html><body><h1>{{.}}</h1></body></html>`))\n\trouter.SetHTMLTemplate(t)\n\n\trouter.GET(\"/html\", func(c *Context) {\n\t\tc.HTML(http.StatusOK, \"index\", \"hola\")\n\t})\n\trunRequest(B, router, http.MethodGet, \"/html\")\n}\n\nfunc BenchmarkOneRouteSet(B *testing.B) {\n\trouter := New()\n\trouter.GET(\"/ping\", func(c *Context) {\n\t\tc.Set(\"key\", \"value\")\n\t})\n\trunRequest(B, router, http.MethodGet, \"/ping\")\n}\n\nfunc BenchmarkOneRouteString(B *testing.B) {\n\trouter := New()\n\trouter.GET(\"/text\", func(c *Context) {\n\t\tc.String(http.StatusOK, \"this is a plain text\")\n\t})\n\trunRequest(B, router, http.MethodGet, \"/text\")\n}\n\nfunc BenchmarkManyRoutesFirst(B *testing.B) {\n\trouter := New()\n\trouter.Any(\"/ping\", func(c *Context) {})\n\trunRequest(B, router, http.MethodGet, \"/ping\")\n}\n\nfunc BenchmarkManyRoutesLast(B *testing.B) {\n\trouter := New()\n\trouter.Any(\"/ping\", func(c *Context) {})\n\trunRequest(B, router, \"OPTIONS\", \"/ping\")\n}\n\nfunc Benchmark404(B *testing.B) {\n\trouter := New()\n\trouter.Any(\"/something\", func(c *Context) {})\n\trouter.NoRoute(func(c *Context) {})\n\trunRequest(B, router, http.MethodGet, \"/ping\")\n}\n\nfunc Benchmark404Many(B *testing.B) {\n\trouter := New()\n\trouter.GET(\"/\", func(c *Context) {})\n\trouter.GET(\"/path/to/something\", func(c *Context) {})\n\trouter.GET(\"/post/:id\", func(c *Context) {})\n\trouter.GET(\"/view/:id\", func(c *Context) {})\n\trouter.GET(\"/favicon.ico\", func(c *Context) {})\n\trouter.GET(\"/robots.txt\", func(c *Context) {})\n\trouter.GET(\"/delete/:id\", func(c *Context) {})\n\trouter.GET(\"/user/:id/:mode\", func(c *Context) {})\n\n\trouter.NoRoute(func(c *Context) {})\n\trunRequest(B, router, http.MethodGet, \"/viewfake\")\n}\n\ntype mockWriter struct {\n\theaders http.Header\n}\n\nfunc newMockWriter() *mockWriter {\n\treturn &mockWriter{\n\t\thttp.Header{},\n\t}\n}\n\nfunc (m *mockWriter) Header() (h http.Header) {\n\treturn m.headers\n}\n\nfunc (m *mockWriter) Write(p []byte) (n int, err error) {\n\treturn len(p), nil\n}\n\nfunc (m *mockWriter) WriteString(s string) (n int, err error) {\n\treturn len(s), nil\n}\n\nfunc (m *mockWriter) WriteHeader(int) {}\n\nfunc runRequest(B *testing.B, r *Engine, method, path string) {\n\t// create fake request\n\treq, err := http.NewRequest(method, path, nil)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\tw := newMockWriter()\n\tB.ReportAllocs()\n\tB.ResetTimer()\n\tfor B.Loop() {\n\t\tr.ServeHTTP(w, req)\n\t}\n}\n"
  },
  {
    "path": "binding/binding.go",
    "content": "// Copyright 2014 Manu Martinez-Almeida. All rights reserved.\n// Use of this source code is governed by a MIT style\n// license that can be found in the LICENSE file.\n\n//go:build !nomsgpack\n\npackage binding\n\nimport \"net/http\"\n\n// Content-Type MIME of the most common data formats.\nconst (\n\tMIMEJSON              = \"application/json\"\n\tMIMEHTML              = \"text/html\"\n\tMIMEXML               = \"application/xml\"\n\tMIMEXML2              = \"text/xml\"\n\tMIMEPlain             = \"text/plain\"\n\tMIMEPOSTForm          = \"application/x-www-form-urlencoded\"\n\tMIMEMultipartPOSTForm = \"multipart/form-data\"\n\tMIMEPROTOBUF          = \"application/x-protobuf\"\n\tMIMEMSGPACK           = \"application/x-msgpack\"\n\tMIMEMSGPACK2          = \"application/msgpack\"\n\tMIMEYAML              = \"application/x-yaml\"\n\tMIMEYAML2             = \"application/yaml\"\n\tMIMETOML              = \"application/toml\"\n\tMIMEBSON              = \"application/bson\"\n)\n\n// Binding describes the interface which needs to be implemented for binding the\n// data present in the request such as JSON request body, query parameters or\n// the form POST.\ntype Binding interface {\n\tName() string\n\tBind(*http.Request, any) error\n}\n\n// BindingBody adds BindBody method to Binding. BindBody is similar with Bind,\n// but it reads the body from supplied bytes instead of req.Body.\ntype BindingBody interface {\n\tBinding\n\tBindBody([]byte, any) error\n}\n\n// BindingUri adds BindUri method to Binding. BindUri is similar with Bind,\n// but it reads the Params.\ntype BindingUri interface {\n\tName() string\n\tBindUri(map[string][]string, any) error\n}\n\n// StructValidator is the minimal interface which needs to be implemented in\n// order for it to be used as the validator engine for ensuring the correctness\n// of the request. Gin provides a default implementation for this using\n// https://github.com/go-playground/validator/tree/v10.6.1.\ntype StructValidator interface {\n\t// ValidateStruct can receive any kind of type and it should never panic, even if the configuration is not right.\n\t// If the received type is a slice|array, the validation should be performed travel on every element.\n\t// If the received type is not a struct or slice|array, any validation should be skipped and nil must be returned.\n\t// If the received type is a struct or pointer to a struct, the validation should be performed.\n\t// If the struct is not valid or the validation itself fails, a descriptive error should be returned.\n\t// Otherwise nil must be returned.\n\tValidateStruct(any) error\n\n\t// Engine returns the underlying validator engine which powers the\n\t// StructValidator implementation.\n\tEngine() any\n}\n\n// Validator is the default validator which implements the StructValidator\n// interface. It uses https://github.com/go-playground/validator/tree/v10.6.1\n// under the hood.\nvar Validator StructValidator = &defaultValidator{}\n\n// These implement the Binding interface and can be used to bind the data\n// present in the request to struct instances.\nvar (\n\tJSON          BindingBody = jsonBinding{}\n\tXML           BindingBody = xmlBinding{}\n\tForm          Binding     = formBinding{}\n\tQuery         Binding     = queryBinding{}\n\tFormPost      Binding     = formPostBinding{}\n\tFormMultipart Binding     = formMultipartBinding{}\n\tProtoBuf      BindingBody = protobufBinding{}\n\tMsgPack       BindingBody = msgpackBinding{}\n\tYAML          BindingBody = yamlBinding{}\n\tUri           BindingUri  = uriBinding{}\n\tHeader        Binding     = headerBinding{}\n\tPlain         BindingBody = plainBinding{}\n\tTOML          BindingBody = tomlBinding{}\n\tBSON          BindingBody = bsonBinding{}\n)\n\n// Default returns the appropriate Binding instance based on the HTTP method\n// and the content type.\nfunc Default(method, contentType string) Binding {\n\tif method == http.MethodGet {\n\t\treturn Form\n\t}\n\n\tswitch contentType {\n\tcase MIMEJSON:\n\t\treturn JSON\n\tcase MIMEXML, MIMEXML2:\n\t\treturn XML\n\tcase MIMEPROTOBUF:\n\t\treturn ProtoBuf\n\tcase MIMEMSGPACK, MIMEMSGPACK2:\n\t\treturn MsgPack\n\tcase MIMEYAML, MIMEYAML2:\n\t\treturn YAML\n\tcase MIMETOML:\n\t\treturn TOML\n\tcase MIMEMultipartPOSTForm:\n\t\treturn FormMultipart\n\tcase MIMEBSON:\n\t\treturn BSON\n\tdefault: // case MIMEPOSTForm:\n\t\treturn Form\n\t}\n}\n\nfunc validate(obj any) error {\n\tif Validator == nil {\n\t\treturn nil\n\t}\n\treturn Validator.ValidateStruct(obj)\n}\n"
  },
  {
    "path": "binding/binding_msgpack_test.go",
    "content": "// Copyright 2020 Gin Core Team. All rights reserved.\n// Use of this source code is governed by a MIT style\n// license that can be found in the LICENSE file.\n\n//go:build !nomsgpack\n\npackage binding\n\nimport (\n\t\"bytes\"\n\t\"net/http\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n\t\"github.com/ugorji/go/codec\"\n)\n\nfunc TestBindingMsgPack(t *testing.T) {\n\ttest := FooStruct{\n\t\tFoo: \"bar\",\n\t}\n\n\th := new(codec.MsgpackHandle)\n\tassert.NotNil(t, h)\n\tbuf := bytes.NewBuffer([]byte{})\n\tassert.NotNil(t, buf)\n\terr := codec.NewEncoder(buf, h).Encode(test)\n\trequire.NoError(t, err)\n\n\tdata := buf.Bytes()\n\n\ttestMsgPackBodyBinding(t,\n\t\tMsgPack, \"msgpack\",\n\t\t\"/\", \"/\",\n\t\tstring(data), string(data[1:]))\n}\n\nfunc testMsgPackBodyBinding(t *testing.T, b Binding, name, path, badPath, body, badBody string) {\n\tassert.Equal(t, name, b.Name())\n\n\tobj := FooStruct{}\n\treq := requestWithBody(http.MethodPost, path, body)\n\treq.Header.Add(\"Content-Type\", MIMEMSGPACK)\n\terr := b.Bind(req, &obj)\n\trequire.NoError(t, err)\n\tassert.Equal(t, \"bar\", obj.Foo)\n\n\tobj = FooStruct{}\n\treq = requestWithBody(http.MethodPost, badPath, badBody)\n\treq.Header.Add(\"Content-Type\", MIMEMSGPACK)\n\terr = MsgPack.Bind(req, &obj)\n\trequire.Error(t, err)\n}\n\nfunc TestBindingDefaultMsgPack(t *testing.T) {\n\tassert.Equal(t, MsgPack, Default(http.MethodPost, MIMEMSGPACK))\n\tassert.Equal(t, MsgPack, Default(http.MethodPut, MIMEMSGPACK2))\n}\n"
  },
  {
    "path": "binding/binding_nomsgpack.go",
    "content": "// Copyright 2020 Gin Core Team. All rights reserved.\n// Use of this source code is governed by a MIT style\n// license that can be found in the LICENSE file.\n\n//go:build nomsgpack\n\npackage binding\n\nimport \"net/http\"\n\n// Content-Type MIME of the most common data formats.\nconst (\n\tMIMEJSON              = \"application/json\"\n\tMIMEHTML              = \"text/html\"\n\tMIMEXML               = \"application/xml\"\n\tMIMEXML2              = \"text/xml\"\n\tMIMEPlain             = \"text/plain\"\n\tMIMEPOSTForm          = \"application/x-www-form-urlencoded\"\n\tMIMEMultipartPOSTForm = \"multipart/form-data\"\n\tMIMEPROTOBUF          = \"application/x-protobuf\"\n\tMIMEYAML              = \"application/x-yaml\"\n\tMIMEYAML2             = \"application/yaml\"\n\tMIMETOML              = \"application/toml\"\n\tMIMEBSON              = \"application/bson\"\n)\n\n// Binding describes the interface which needs to be implemented for binding the\n// data present in the request such as JSON request body, query parameters or\n// the form POST.\ntype Binding interface {\n\tName() string\n\tBind(*http.Request, any) error\n}\n\n// BindingBody adds BindBody method to Binding. BindBody is similar with Bind,\n// but it reads the body from supplied bytes instead of req.Body.\ntype BindingBody interface {\n\tBinding\n\tBindBody([]byte, any) error\n}\n\n// BindingUri adds BindUri method to Binding. BindUri is similar with Bind,\n// but it reads the Params.\ntype BindingUri interface {\n\tName() string\n\tBindUri(map[string][]string, any) error\n}\n\n// StructValidator is the minimal interface which needs to be implemented in\n// order for it to be used as the validator engine for ensuring the correctness\n// of the request. Gin provides a default implementation for this using\n// https://github.com/go-playground/validator/tree/v10.6.1.\ntype StructValidator interface {\n\t// ValidateStruct can receive any kind of type and it should never panic, even if the configuration is not right.\n\t// If the received type is not a struct, any validation should be skipped and nil must be returned.\n\t// If the received type is a struct or pointer to a struct, the validation should be performed.\n\t// If the struct is not valid or the validation itself fails, a descriptive error should be returned.\n\t// Otherwise nil must be returned.\n\tValidateStruct(any) error\n\n\t// Engine returns the underlying validator engine which powers the\n\t// StructValidator implementation.\n\tEngine() any\n}\n\n// Validator is the default validator which implements the StructValidator\n// interface. It uses https://github.com/go-playground/validator/tree/v10.6.1\n// under the hood.\nvar Validator StructValidator = &defaultValidator{}\n\n// These implement the Binding interface and can be used to bind the data\n// present in the request to struct instances.\nvar (\n\tJSON          = jsonBinding{}\n\tXML           = xmlBinding{}\n\tForm          = formBinding{}\n\tQuery         = queryBinding{}\n\tFormPost      = formPostBinding{}\n\tFormMultipart = formMultipartBinding{}\n\tProtoBuf      = protobufBinding{}\n\tYAML          = yamlBinding{}\n\tUri           = uriBinding{}\n\tHeader        = headerBinding{}\n\tTOML          = tomlBinding{}\n\tPlain         = plainBinding{}\n\tBSON          BindingBody = bsonBinding{}\n)\n\n// Default returns the appropriate Binding instance based on the HTTP method\n// and the content type.\nfunc Default(method, contentType string) Binding {\n\tif method == \"GET\" {\n\t\treturn Form\n\t}\n\n\tswitch contentType {\n\tcase MIMEJSON:\n\t\treturn JSON\n\tcase MIMEXML, MIMEXML2:\n\t\treturn XML\n\tcase MIMEPROTOBUF:\n\t\treturn ProtoBuf\n\tcase MIMEYAML, MIMEYAML2:\n\t\treturn YAML\n\tcase MIMEMultipartPOSTForm:\n\t\treturn FormMultipart\n\tcase MIMETOML:\n\t\treturn TOML\n\tcase MIMEBSON:\n\t\treturn BSON\n\tdefault: // case MIMEPOSTForm:\n\t\treturn Form\n\t}\n}\n\nfunc validate(obj any) error {\n\tif Validator == nil {\n\t\treturn nil\n\t}\n\treturn Validator.ValidateStruct(obj)\n}\n"
  },
  {
    "path": "binding/binding_test.go",
    "content": "// Copyright 2014 Manu Martinez-Almeida. All rights reserved.\n// Use of this source code is governed by a MIT style\n// license that can be found in the LICENSE file.\n\npackage binding\n\nimport (\n\t\"bytes\"\n\t\"encoding/json\"\n\t\"errors\"\n\t\"io\"\n\t\"mime/multipart\"\n\t\"net/http\"\n\t\"os\"\n\t\"reflect\"\n\t\"strconv\"\n\t\"strings\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/gin-gonic/gin/testdata/protoexample\"\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n\t\"go.mongodb.org/mongo-driver/v2/bson\"\n\t\"google.golang.org/protobuf/proto\"\n)\n\ntype appkey struct {\n\tAppkey string `json:\"appkey\" form:\"appkey\"`\n}\n\ntype QueryTest struct {\n\tPage int `json:\"page\" form:\"page\"`\n\tSize int `json:\"size\" form:\"size\"`\n\tappkey\n}\n\ntype FooStruct struct {\n\tFoo string `msgpack:\"foo\" json:\"foo\" form:\"foo\" xml:\"foo\" binding:\"required,max=32\"`\n}\n\ntype FooBarStruct struct {\n\tFooStruct\n\tBar string `msgpack:\"bar\" json:\"bar\" form:\"bar\" xml:\"bar\" binding:\"required\"`\n}\n\ntype FooBarFileStruct struct {\n\tFooBarStruct\n\tFile *multipart.FileHeader `form:\"file\" binding:\"required\"`\n}\n\ntype FooBarFileFailStruct struct {\n\tFooBarStruct\n\tFile *multipart.FileHeader `invalid_name:\"file\" binding:\"required\"`\n}\n\ntype FooDefaultBarStruct struct {\n\tFooStruct\n\tBar string `msgpack:\"bar\" json:\"bar\" form:\"bar,default=hello\" xml:\"bar\" binding:\"required\"`\n}\n\ntype FooStructUseNumber struct {\n\tFoo any `json:\"foo\" binding:\"required\"`\n}\n\ntype FooStructDisallowUnknownFields struct {\n\tFoo any `json:\"foo\" binding:\"required\"`\n}\n\ntype FooBarStructForTimeType struct {\n\tTimeFoo       time.Time `form:\"time_foo\" time_format:\"2006-01-02\" time_utc:\"1\" time_location:\"Asia/Chongqing\"`\n\tTimeBar       time.Time `form:\"time_bar\" time_format:\"2006-01-02\" time_utc:\"1\"`\n\tCreateTime    time.Time `form:\"createTime\" time_format:\"unixNano\"`\n\tUnixTime      time.Time `form:\"unixTime\" time_format:\"unix\"`\n\tUnixMilliTime time.Time `form:\"unixMilliTime\" time_format:\"unixmilli\"`\n\tUnixMicroTime time.Time `form:\"unixMicroTime\" time_format:\"uNiXmiCrO\"`\n}\n\ntype FooStructForTimeTypeNotUnixFormat struct {\n\tCreateTime    time.Time `form:\"createTime\" time_format:\"unixNano\"`\n\tUnixTime      time.Time `form:\"unixTime\" time_format:\"unix\"`\n\tUnixMilliTime time.Time `form:\"unixMilliTime\" time_format:\"unixMilli\"`\n\tUnixMicroTime time.Time `form:\"unixMicroTime\" time_format:\"unixMicro\"`\n}\n\ntype FooStructForTimeTypeNotFormat struct {\n\tTimeFoo time.Time `form:\"time_foo\"`\n}\n\ntype FooStructForTimeTypeFailFormat struct {\n\tTimeFoo time.Time `form:\"time_foo\" time_format:\"2017-11-15\"`\n}\n\ntype FooStructForTimeTypeFailLocation struct {\n\tTimeFoo time.Time `form:\"time_foo\" time_format:\"2006-01-02\" time_location:\"/asia/chongqing\"`\n}\n\ntype FooStructForMapType struct {\n\tMapFoo map[string]any `form:\"map_foo\"`\n}\n\ntype FooStructForIgnoreFormTag struct {\n\tFoo *string `form:\"-\"`\n}\n\ntype InvalidNameType struct {\n\tTestName string `invalid_name:\"test_name\"`\n}\n\ntype InvalidNameMapType struct {\n\tTestName struct {\n\t\tMapFoo map[string]any `form:\"map_foo\"`\n\t}\n}\n\ntype FooStructForSliceType struct {\n\tSliceFoo []int `form:\"slice_foo\"`\n}\n\ntype FooStructForStructType struct {\n\tStructFoo struct {\n\t\tIdx int `form:\"idx\"`\n\t}\n}\n\ntype FooStructForStructPointerType struct {\n\tStructPointerFoo *struct {\n\t\tName string `form:\"name\"`\n\t}\n}\n\ntype FooStructForSliceMapType struct {\n\t// Unknown type: not support map\n\tSliceMapFoo []map[string]any `form:\"slice_map_foo\"`\n}\n\ntype FooStructForBoolType struct {\n\tBoolFoo bool `form:\"bool_foo\"`\n}\n\ntype FooStructForStringPtrType struct {\n\tPtrFoo *string `form:\"ptr_foo\"`\n\tPtrBar *string `form:\"ptr_bar\" binding:\"required\"`\n}\n\ntype FooStructForMapPtrType struct {\n\tPtrBar *map[string]any `form:\"ptr_bar\"`\n}\n\nfunc TestBindingDefault(t *testing.T) {\n\tassert.Equal(t, Form, Default(http.MethodGet, \"\"))\n\tassert.Equal(t, Form, Default(http.MethodGet, MIMEJSON))\n\n\tassert.Equal(t, JSON, Default(http.MethodPost, MIMEJSON))\n\tassert.Equal(t, JSON, Default(http.MethodPut, MIMEJSON))\n\n\tassert.Equal(t, XML, Default(http.MethodPost, MIMEXML))\n\tassert.Equal(t, XML, Default(http.MethodPut, MIMEXML2))\n\n\tassert.Equal(t, Form, Default(http.MethodPost, MIMEPOSTForm))\n\tassert.Equal(t, Form, Default(http.MethodPut, MIMEPOSTForm))\n\n\tassert.Equal(t, FormMultipart, Default(http.MethodPost, MIMEMultipartPOSTForm))\n\tassert.Equal(t, FormMultipart, Default(http.MethodPut, MIMEMultipartPOSTForm))\n\n\tassert.Equal(t, ProtoBuf, Default(http.MethodPost, MIMEPROTOBUF))\n\tassert.Equal(t, ProtoBuf, Default(http.MethodPut, MIMEPROTOBUF))\n\n\tassert.Equal(t, YAML, Default(http.MethodPost, MIMEYAML))\n\tassert.Equal(t, YAML, Default(http.MethodPut, MIMEYAML))\n\tassert.Equal(t, YAML, Default(http.MethodPost, MIMEYAML2))\n\tassert.Equal(t, YAML, Default(http.MethodPut, MIMEYAML2))\n\n\tassert.Equal(t, TOML, Default(http.MethodPost, MIMETOML))\n\tassert.Equal(t, TOML, Default(http.MethodPut, MIMETOML))\n\n\tassert.Equal(t, BSON, Default(http.MethodPost, MIMEBSON))\n\tassert.Equal(t, BSON, Default(http.MethodPut, MIMEBSON))\n}\n\nfunc TestBindingJSONNilBody(t *testing.T) {\n\tvar obj FooStruct\n\treq, _ := http.NewRequest(http.MethodPost, \"/\", nil)\n\terr := JSON.Bind(req, &obj)\n\trequire.Error(t, err)\n}\n\nfunc TestBindingJSON(t *testing.T) {\n\ttestBodyBinding(t,\n\t\tJSON, \"json\",\n\t\t\"/\", \"/\",\n\t\t`{\"foo\": \"bar\"}`, `{\"bar\": \"foo\"}`)\n}\n\nfunc TestBindingJSONSlice(t *testing.T) {\n\tEnableDecoderDisallowUnknownFields = true\n\tdefer func() {\n\t\tEnableDecoderDisallowUnknownFields = false\n\t}()\n\n\ttestBodyBindingSlice(t, JSON, \"json\", \"/\", \"/\", `[]`, ``)\n\ttestBodyBindingSlice(t, JSON, \"json\", \"/\", \"/\", `[{\"foo\": \"123\"}]`, `[{}]`)\n\ttestBodyBindingSlice(t, JSON, \"json\", \"/\", \"/\", `[{\"foo\": \"123\"}]`, `[{\"foo\": \"\"}]`)\n\ttestBodyBindingSlice(t, JSON, \"json\", \"/\", \"/\", `[{\"foo\": \"123\"}]`, `[{\"foo\": 123}]`)\n\ttestBodyBindingSlice(t, JSON, \"json\", \"/\", \"/\", `[{\"foo\": \"123\"}]`, `[{\"bar\": 123}]`)\n\ttestBodyBindingSlice(t, JSON, \"json\", \"/\", \"/\", `[{\"foo\": \"123\"}]`, `[{\"foo\": \"123456789012345678901234567890123\"}]`)\n}\n\nfunc TestBindingJSONUseNumber(t *testing.T) {\n\ttestBodyBindingUseNumber(t,\n\t\tJSON, \"json\",\n\t\t\"/\", \"/\",\n\t\t`{\"foo\": 123}`, `{\"bar\": \"foo\"}`)\n}\n\nfunc TestBindingJSONUseNumber2(t *testing.T) {\n\ttestBodyBindingUseNumber2(t,\n\t\tJSON, \"json\",\n\t\t\"/\", \"/\",\n\t\t`{\"foo\": 123}`, `{\"bar\": \"foo\"}`)\n}\n\nfunc TestBindingJSONDisallowUnknownFields(t *testing.T) {\n\ttestBodyBindingDisallowUnknownFields(t, JSON,\n\t\t\"/\", \"/\",\n\t\t`{\"foo\": \"bar\"}`, `{\"foo\": \"bar\", \"what\": \"this\"}`)\n}\n\nfunc TestBindingJSONStringMap(t *testing.T) {\n\ttestBodyBindingStringMap(t, JSON,\n\t\t\"/\", \"/\",\n\t\t`{\"foo\": \"bar\", \"hello\": \"world\"}`, `{\"num\": 2}`)\n}\n\nfunc TestBindingForm(t *testing.T) {\n\ttestFormBinding(t, http.MethodPost,\n\t\t\"/\", \"/\",\n\t\t\"foo=bar&bar=foo\", \"bar2=foo\")\n}\n\nfunc TestBindingForm2(t *testing.T) {\n\ttestFormBinding(t, http.MethodGet,\n\t\t\"/?foo=bar&bar=foo\", \"/?bar2=foo\",\n\t\t\"\", \"\")\n}\n\nfunc TestBindingFormEmbeddedStruct(t *testing.T) {\n\ttestFormBindingEmbeddedStruct(t, http.MethodPost,\n\t\t\"/\", \"/\",\n\t\t\"page=1&size=2&appkey=test-appkey\", \"bar2=foo\")\n}\n\nfunc TestBindingFormEmbeddedStruct2(t *testing.T) {\n\ttestFormBindingEmbeddedStruct(t, http.MethodGet,\n\t\t\"/?page=1&size=2&appkey=test-appkey\", \"/?bar2=foo\",\n\t\t\"\", \"\")\n}\n\nfunc TestBindingFormDefaultValue(t *testing.T) {\n\ttestFormBindingDefaultValue(t, http.MethodPost,\n\t\t\"/\", \"/\",\n\t\t\"foo=bar\", \"bar2=foo\")\n}\n\nfunc TestBindingFormDefaultValue2(t *testing.T) {\n\ttestFormBindingDefaultValue(t, http.MethodGet,\n\t\t\"/?foo=bar\", \"/?bar2=foo\",\n\t\t\"\", \"\")\n}\n\nfunc TestBindingFormForTime(t *testing.T) {\n\ttestFormBindingForTime(t, http.MethodPost,\n\t\t\"/\", \"/\",\n\t\t\"time_foo=2017-11-15&time_bar=&createTime=1562400033000000123&unixTime=1562400033&unixMilliTime=1562400033001&unixMicroTime=1562400033000012\", \"bar2=foo\")\n\ttestFormBindingForTimeNotUnixFormat(t, http.MethodPost,\n\t\t\"/\", \"/\",\n\t\t\"time_foo=2017-11-15&createTime=bad&unixTime=bad&unixMilliTime=bad&unixMicroTime=bad\", \"bar2=foo\")\n\ttestFormBindingForTimeNotFormat(t, http.MethodPost,\n\t\t\"/\", \"/\",\n\t\t\"time_foo=2017-11-15\", \"bar2=foo\")\n\ttestFormBindingForTimeFailFormat(t, http.MethodPost,\n\t\t\"/\", \"/\",\n\t\t\"time_foo=2017-11-15\", \"bar2=foo\")\n\ttestFormBindingForTimeFailLocation(t, http.MethodPost,\n\t\t\"/\", \"/\",\n\t\t\"time_foo=2017-11-15\", \"bar2=foo\")\n}\n\nfunc TestBindingFormForTime2(t *testing.T) {\n\ttestFormBindingForTime(t, http.MethodGet,\n\t\t\"/?time_foo=2017-11-15&time_bar=&createTime=1562400033000000123&unixTime=1562400033&unixMilliTime=1562400033001&unixMicroTime=1562400033000012\", \"/?bar2=foo\",\n\t\t\"\", \"\")\n\ttestFormBindingForTimeNotUnixFormat(t, http.MethodPost,\n\t\t\"/\", \"/\",\n\t\t\"time_foo=2017-11-15&createTime=bad&unixTime=bad&unixMilliTime=bad&unixMicroTime=bad\", \"bar2=foo\")\n\ttestFormBindingForTimeNotFormat(t, http.MethodGet,\n\t\t\"/?time_foo=2017-11-15\", \"/?bar2=foo\",\n\t\t\"\", \"\")\n\ttestFormBindingForTimeFailFormat(t, http.MethodGet,\n\t\t\"/?time_foo=2017-11-15\", \"/?bar2=foo\",\n\t\t\"\", \"\")\n\ttestFormBindingForTimeFailLocation(t, http.MethodGet,\n\t\t\"/?time_foo=2017-11-15\", \"/?bar2=foo\",\n\t\t\"\", \"\")\n}\n\nfunc TestFormBindingIgnoreField(t *testing.T) {\n\ttestFormBindingIgnoreField(t, http.MethodPost,\n\t\t\"/\", \"/\",\n\t\t\"-=bar\", \"\")\n}\n\nfunc TestBindingFormInvalidName(t *testing.T) {\n\ttestFormBindingInvalidName(t, http.MethodPost,\n\t\t\"/\", \"/\",\n\t\t\"test_name=bar\", \"bar2=foo\")\n}\n\nfunc TestBindingFormInvalidName2(t *testing.T) {\n\ttestFormBindingInvalidName2(t, http.MethodPost,\n\t\t\"/\", \"/\",\n\t\t\"map_foo=bar\", \"bar2=foo\")\n}\n\nfunc TestBindingFormForType(t *testing.T) {\n\ttestFormBindingForType(t, http.MethodPost,\n\t\t\"/\", \"/\",\n\t\t\"map_foo={\\\"bar\\\":123}\", \"map_foo=1\", \"Map\")\n\n\ttestFormBindingForType(t, http.MethodPost,\n\t\t\"/\", \"/\",\n\t\t\"slice_foo=1&slice_foo=2\", \"bar2=1&bar2=2\", \"Slice\")\n\n\ttestFormBindingForType(t, http.MethodGet,\n\t\t\"/?slice_foo=1&slice_foo=2\", \"/?bar2=1&bar2=2\",\n\t\t\"\", \"\", \"Slice\")\n\n\ttestFormBindingForType(t, http.MethodPost,\n\t\t\"/\", \"/\",\n\t\t\"slice_map_foo=1&slice_map_foo=2\", \"bar2=1&bar2=2\", \"SliceMap\")\n\n\ttestFormBindingForType(t, http.MethodGet,\n\t\t\"/?slice_map_foo=1&slice_map_foo=2\", \"/?bar2=1&bar2=2\",\n\t\t\"\", \"\", \"SliceMap\")\n\n\ttestFormBindingForType(t, http.MethodPost,\n\t\t\"/\", \"/\",\n\t\t\"ptr_bar=test\", \"bar2=test\", \"Ptr\")\n\n\ttestFormBindingForType(t, http.MethodGet,\n\t\t\"/?ptr_bar=test\", \"/?bar2=test\",\n\t\t\"\", \"\", \"Ptr\")\n\n\ttestFormBindingForType(t, http.MethodPost,\n\t\t\"/\", \"/\",\n\t\t\"idx=123\", \"id1=1\", \"Struct\")\n\n\ttestFormBindingForType(t, http.MethodGet,\n\t\t\"/?idx=123\", \"/?id1=1\",\n\t\t\"\", \"\", \"Struct\")\n\n\ttestFormBindingForType(t, http.MethodPost,\n\t\t\"/\", \"/\",\n\t\t\"name=thinkerou\", \"name1=ou\", \"StructPointer\")\n\n\ttestFormBindingForType(t, http.MethodGet,\n\t\t\"/?name=thinkerou\", \"/?name1=ou\",\n\t\t\"\", \"\", \"StructPointer\")\n}\n\nfunc TestBindingFormStringMap(t *testing.T) {\n\ttestBodyBindingStringMap(t, Form,\n\t\t\"/\", \"\",\n\t\t`foo=bar&hello=world`, \"\")\n\t// Should pick the last value\n\ttestBodyBindingStringMap(t, Form,\n\t\t\"/\", \"\",\n\t\t`foo=something&foo=bar&hello=world`, \"\")\n}\n\nfunc TestBindingFormStringSliceMap(t *testing.T) {\n\tobj := make(map[string][]string)\n\treq := requestWithBody(http.MethodPost, \"/\", \"foo=something&foo=bar&hello=world\")\n\treq.Header.Add(\"Content-Type\", MIMEPOSTForm)\n\terr := Form.Bind(req, &obj)\n\trequire.NoError(t, err)\n\tassert.NotNil(t, obj)\n\tassert.Len(t, obj, 2)\n\ttarget := map[string][]string{\n\t\t\"foo\":   {\"something\", \"bar\"},\n\t\t\"hello\": {\"world\"},\n\t}\n\tassert.True(t, reflect.DeepEqual(obj, target))\n\n\tobjInvalid := make(map[string][]int)\n\treq = requestWithBody(http.MethodPost, \"/\", \"foo=something&foo=bar&hello=world\")\n\treq.Header.Add(\"Content-Type\", MIMEPOSTForm)\n\terr = Form.Bind(req, &objInvalid)\n\trequire.Error(t, err)\n}\n\nfunc TestBindingQuery(t *testing.T) {\n\ttestQueryBinding(t, http.MethodPost,\n\t\t\"/?foo=bar&bar=foo\", \"/\",\n\t\t\"foo=unused\", \"bar2=foo\")\n}\n\nfunc TestBindingQuery2(t *testing.T) {\n\ttestQueryBinding(t, http.MethodGet,\n\t\t\"/?foo=bar&bar=foo\", \"/?bar2=foo\",\n\t\t\"foo=unused\", \"\")\n}\n\nfunc TestBindingQueryFail(t *testing.T) {\n\ttestQueryBindingFail(t, http.MethodPost,\n\t\t\"/?map_foo=\", \"/\",\n\t\t\"map_foo=unused\", \"bar2=foo\")\n}\n\nfunc TestBindingQueryFail2(t *testing.T) {\n\ttestQueryBindingFail(t, http.MethodGet,\n\t\t\"/?map_foo=\", \"/?bar2=foo\",\n\t\t\"map_foo=unused\", \"\")\n}\n\nfunc TestBindingQueryBoolFail(t *testing.T) {\n\ttestQueryBindingBoolFail(t, http.MethodGet,\n\t\t\"/?bool_foo=fasl\", \"/?bar2=foo\",\n\t\t\"bool_foo=unused\", \"\")\n}\n\nfunc TestBindingQueryStringMap(t *testing.T) {\n\tb := Query\n\n\tobj := make(map[string]string)\n\treq := requestWithBody(http.MethodGet, \"/?foo=bar&hello=world\", \"\")\n\terr := b.Bind(req, &obj)\n\trequire.NoError(t, err)\n\tassert.NotNil(t, obj)\n\tassert.Len(t, obj, 2)\n\tassert.Equal(t, \"bar\", obj[\"foo\"])\n\tassert.Equal(t, \"world\", obj[\"hello\"])\n\n\tobj = make(map[string]string)\n\treq = requestWithBody(http.MethodGet, \"/?foo=bar&foo=2&hello=world\", \"\") // should pick last\n\terr = b.Bind(req, &obj)\n\trequire.NoError(t, err)\n\tassert.NotNil(t, obj)\n\tassert.Len(t, obj, 2)\n\tassert.Equal(t, \"2\", obj[\"foo\"])\n\tassert.Equal(t, \"world\", obj[\"hello\"])\n}\n\nfunc TestBindingXML(t *testing.T) {\n\ttestBodyBinding(t,\n\t\tXML, \"xml\",\n\t\t\"/\", \"/\",\n\t\t\"<map><foo>bar</foo></map>\", \"<map><bar>foo</bar></map>\")\n}\n\nfunc TestBindingXMLFail(t *testing.T) {\n\ttestBodyBindingFail(t,\n\t\tXML, \"xml\",\n\t\t\"/\", \"/\",\n\t\t\"<map><foo>bar<foo></map>\", \"<map><bar>foo</bar></map>\")\n}\n\nfunc TestBindingTOML(t *testing.T) {\n\ttestBodyBinding(t,\n\t\tTOML, \"toml\",\n\t\t\"/\", \"/\",\n\t\t`foo=\"bar\"`, `bar=\"foo\"`)\n}\n\nfunc TestBindingTOMLFail(t *testing.T) {\n\ttestBodyBindingFail(t,\n\t\tTOML, \"toml\",\n\t\t\"/\", \"/\",\n\t\t`foo=\\n\"bar\"`, `bar=\"foo\"`)\n}\n\nfunc TestBindingYAML(t *testing.T) {\n\ttestBodyBinding(t,\n\t\tYAML, \"yaml\",\n\t\t\"/\", \"/\",\n\t\t`foo: bar`, `bar: foo`)\n}\n\nfunc TestBindingYAMLStringMap(t *testing.T) {\n\t// YAML is a superset of JSON, so the test below is JSON (to avoid newlines)\n\ttestBodyBindingStringMap(t, YAML,\n\t\t\"/\", \"/\",\n\t\t`{\"foo\": \"bar\", \"hello\": \"world\"}`, `{\"nested\": {\"foo\": \"bar\"}}`)\n}\n\nfunc TestBindingYAMLFail(t *testing.T) {\n\ttestBodyBindingFail(t,\n\t\tYAML, \"yaml\",\n\t\t\"/\", \"/\",\n\t\t`foo:\\nbar`, `bar: foo`)\n}\n\nfunc createFormPostRequest(t *testing.T) *http.Request {\n\treq, err := http.NewRequest(http.MethodPost, \"/?foo=getfoo&bar=getbar\", bytes.NewBufferString(\"foo=bar&bar=foo\"))\n\trequire.NoError(t, err)\n\treq.Header.Set(\"Content-Type\", MIMEPOSTForm)\n\treturn req\n}\n\nfunc createDefaultFormPostRequest(t *testing.T) *http.Request {\n\treq, err := http.NewRequest(http.MethodPost, \"/?foo=getfoo&bar=getbar\", bytes.NewBufferString(\"foo=bar\"))\n\trequire.NoError(t, err)\n\treq.Header.Set(\"Content-Type\", MIMEPOSTForm)\n\treturn req\n}\n\nfunc createFormPostRequestForMap(t *testing.T) *http.Request {\n\treq, err := http.NewRequest(http.MethodPost, \"/?map_foo=getfoo\", bytes.NewBufferString(\"map_foo={\\\"bar\\\":123}\"))\n\trequire.NoError(t, err)\n\treq.Header.Set(\"Content-Type\", MIMEPOSTForm)\n\treturn req\n}\n\nfunc createFormPostRequestForMapFail(t *testing.T) *http.Request {\n\treq, err := http.NewRequest(http.MethodPost, \"/?map_foo=getfoo\", bytes.NewBufferString(\"map_foo=hello\"))\n\trequire.NoError(t, err)\n\treq.Header.Set(\"Content-Type\", MIMEPOSTForm)\n\treturn req\n}\n\nfunc createFormFilesMultipartRequest(t *testing.T) *http.Request {\n\tboundary := \"--testboundary\"\n\tbody := new(bytes.Buffer)\n\tmw := multipart.NewWriter(body)\n\tdefer mw.Close()\n\n\trequire.NoError(t, mw.SetBoundary(boundary))\n\trequire.NoError(t, mw.WriteField(\"foo\", \"bar\"))\n\trequire.NoError(t, mw.WriteField(\"bar\", \"foo\"))\n\n\tf, err := os.Open(\"form.go\")\n\trequire.NoError(t, err)\n\tdefer f.Close()\n\tfw, err1 := mw.CreateFormFile(\"file\", \"form.go\")\n\trequire.NoError(t, err1)\n\t_, err = io.Copy(fw, f)\n\trequire.NoError(t, err)\n\n\treq, err2 := http.NewRequest(http.MethodPost, \"/?foo=getfoo&bar=getbar\", body)\n\trequire.NoError(t, err2)\n\treq.Header.Set(\"Content-Type\", MIMEMultipartPOSTForm+\"; boundary=\"+boundary)\n\n\treturn req\n}\n\nfunc createFormFilesMultipartRequestFail(t *testing.T) *http.Request {\n\tboundary := \"--testboundary\"\n\tbody := new(bytes.Buffer)\n\tmw := multipart.NewWriter(body)\n\tdefer mw.Close()\n\n\trequire.NoError(t, mw.SetBoundary(boundary))\n\trequire.NoError(t, mw.WriteField(\"foo\", \"bar\"))\n\trequire.NoError(t, mw.WriteField(\"bar\", \"foo\"))\n\n\tf, err := os.Open(\"form.go\")\n\trequire.NoError(t, err)\n\tdefer f.Close()\n\tfw, err1 := mw.CreateFormFile(\"file_foo\", \"form_foo.go\")\n\trequire.NoError(t, err1)\n\t_, err = io.Copy(fw, f)\n\trequire.NoError(t, err)\n\n\treq, err2 := http.NewRequest(http.MethodPost, \"/?foo=getfoo&bar=getbar\", body)\n\trequire.NoError(t, err2)\n\treq.Header.Set(\"Content-Type\", MIMEMultipartPOSTForm+\"; boundary=\"+boundary)\n\n\treturn req\n}\n\nfunc createFormMultipartRequest(t *testing.T) *http.Request {\n\tboundary := \"--testboundary\"\n\tbody := new(bytes.Buffer)\n\tmw := multipart.NewWriter(body)\n\tdefer mw.Close()\n\n\trequire.NoError(t, mw.SetBoundary(boundary))\n\trequire.NoError(t, mw.WriteField(\"foo\", \"bar\"))\n\trequire.NoError(t, mw.WriteField(\"bar\", \"foo\"))\n\treq, err := http.NewRequest(http.MethodPost, \"/?foo=getfoo&bar=getbar\", body)\n\trequire.NoError(t, err)\n\treq.Header.Set(\"Content-Type\", MIMEMultipartPOSTForm+\"; boundary=\"+boundary)\n\treturn req\n}\n\nfunc createFormMultipartRequestForMap(t *testing.T) *http.Request {\n\tboundary := \"--testboundary\"\n\tbody := new(bytes.Buffer)\n\tmw := multipart.NewWriter(body)\n\tdefer mw.Close()\n\n\trequire.NoError(t, mw.SetBoundary(boundary))\n\trequire.NoError(t, mw.WriteField(\"map_foo\", \"{\\\"bar\\\":123, \\\"name\\\":\\\"thinkerou\\\", \\\"pai\\\": 3.14}\"))\n\treq, err := http.NewRequest(http.MethodPost, \"/?map_foo=getfoo\", body)\n\trequire.NoError(t, err)\n\treq.Header.Set(\"Content-Type\", MIMEMultipartPOSTForm+\"; boundary=\"+boundary)\n\treturn req\n}\n\nfunc createFormMultipartRequestForMapFail(t *testing.T) *http.Request {\n\tboundary := \"--testboundary\"\n\tbody := new(bytes.Buffer)\n\tmw := multipart.NewWriter(body)\n\tdefer mw.Close()\n\n\trequire.NoError(t, mw.SetBoundary(boundary))\n\trequire.NoError(t, mw.WriteField(\"map_foo\", \"3.14\"))\n\treq, err := http.NewRequest(http.MethodPost, \"/?map_foo=getfoo\", body)\n\trequire.NoError(t, err)\n\treq.Header.Set(\"Content-Type\", MIMEMultipartPOSTForm+\"; boundary=\"+boundary)\n\treturn req\n}\n\nfunc TestBindingFormPost(t *testing.T) {\n\treq := createFormPostRequest(t)\n\tvar obj FooBarStruct\n\trequire.NoError(t, FormPost.Bind(req, &obj))\n\n\tassert.Equal(t, \"form-urlencoded\", FormPost.Name())\n\tassert.Equal(t, \"bar\", obj.Foo)\n\tassert.Equal(t, \"foo\", obj.Bar)\n}\n\nfunc TestBindingDefaultValueFormPost(t *testing.T) {\n\treq := createDefaultFormPostRequest(t)\n\tvar obj FooDefaultBarStruct\n\trequire.NoError(t, FormPost.Bind(req, &obj))\n\n\tassert.Equal(t, \"bar\", obj.Foo)\n\tassert.Equal(t, \"hello\", obj.Bar)\n}\n\nfunc TestBindingFormPostForMap(t *testing.T) {\n\treq := createFormPostRequestForMap(t)\n\tvar obj FooStructForMapType\n\terr := FormPost.Bind(req, &obj)\n\trequire.NoError(t, err)\n\tassert.InDelta(t, float64(123), obj.MapFoo[\"bar\"].(float64), 0.01)\n}\n\nfunc TestBindingFormPostForMapFail(t *testing.T) {\n\treq := createFormPostRequestForMapFail(t)\n\tvar obj FooStructForMapType\n\terr := FormPost.Bind(req, &obj)\n\trequire.Error(t, err)\n}\n\nfunc TestBindingFormFilesMultipart(t *testing.T) {\n\treq := createFormFilesMultipartRequest(t)\n\tvar obj FooBarFileStruct\n\terr := FormMultipart.Bind(req, &obj)\n\trequire.NoError(t, err)\n\n\t// file from os\n\tf, _ := os.Open(\"form.go\")\n\tdefer f.Close()\n\tfileActual, _ := io.ReadAll(f)\n\n\t// file from multipart\n\tmf, _ := obj.File.Open()\n\tdefer mf.Close()\n\tfileExpect, _ := io.ReadAll(mf)\n\n\tassert.Equal(t, \"multipart/form-data\", FormMultipart.Name())\n\tassert.Equal(t, \"bar\", obj.Foo)\n\tassert.Equal(t, \"foo\", obj.Bar)\n\tassert.Equal(t, fileExpect, fileActual)\n}\n\nfunc TestBindingFormFilesMultipartFail(t *testing.T) {\n\treq := createFormFilesMultipartRequestFail(t)\n\tvar obj FooBarFileFailStruct\n\terr := FormMultipart.Bind(req, &obj)\n\trequire.Error(t, err)\n}\n\nfunc TestBindingFormMultipart(t *testing.T) {\n\treq := createFormMultipartRequest(t)\n\tvar obj FooBarStruct\n\trequire.NoError(t, FormMultipart.Bind(req, &obj))\n\n\tassert.Equal(t, \"multipart/form-data\", FormMultipart.Name())\n\tassert.Equal(t, \"bar\", obj.Foo)\n\tassert.Equal(t, \"foo\", obj.Bar)\n}\n\nfunc TestBindingFormMultipartForMap(t *testing.T) {\n\treq := createFormMultipartRequestForMap(t)\n\tvar obj FooStructForMapType\n\terr := FormMultipart.Bind(req, &obj)\n\trequire.NoError(t, err)\n\tassert.InDelta(t, float64(123), obj.MapFoo[\"bar\"].(float64), 0.01)\n\tassert.Equal(t, \"thinkerou\", obj.MapFoo[\"name\"].(string))\n\tassert.InDelta(t, float64(3.14), obj.MapFoo[\"pai\"].(float64), 0.01)\n}\n\nfunc TestBindingFormMultipartForMapFail(t *testing.T) {\n\treq := createFormMultipartRequestForMapFail(t)\n\tvar obj FooStructForMapType\n\terr := FormMultipart.Bind(req, &obj)\n\trequire.Error(t, err)\n}\n\nfunc TestBindingProtoBuf(t *testing.T) {\n\ttest := &protoexample.Test{\n\t\tLabel: proto.String(\"yes\"),\n\t}\n\tdata, _ := proto.Marshal(test)\n\n\ttestProtoBodyBinding(t,\n\t\tProtoBuf, \"protobuf\",\n\t\t\"/\", \"/\",\n\t\tstring(data), string(data[1:]))\n}\n\nfunc TestBindingProtoBufFail(t *testing.T) {\n\ttest := &protoexample.Test{\n\t\tLabel: proto.String(\"yes\"),\n\t}\n\tdata, _ := proto.Marshal(test)\n\n\ttestProtoBodyBindingFail(t,\n\t\tProtoBuf, \"protobuf\",\n\t\t\"/\", \"/\",\n\t\tstring(data), string(data[1:]))\n}\n\nfunc TestBindingBSON(t *testing.T) {\n\tvar obj FooStruct\n\tobj.Foo = \"bar\"\n\tdata, _ := bson.Marshal(&obj)\n\ttestBodyBinding(t,\n\t\tBSON, \"bson\",\n\t\t\"/\", \"/\",\n\t\tstring(data),\n\t\t// note: for badbody, we remove first byte to make it invalid\n\t\tstring(data[1:]))\n}\n\nfunc TestValidationFails(t *testing.T) {\n\tvar obj FooStruct\n\treq := requestWithBody(http.MethodPost, \"/\", `{\"bar\": \"foo\"}`)\n\terr := JSON.Bind(req, &obj)\n\trequire.Error(t, err)\n}\n\nfunc TestValidationDisabled(t *testing.T) {\n\tbackup := Validator\n\tValidator = nil\n\tdefer func() { Validator = backup }()\n\n\tvar obj FooStruct\n\treq := requestWithBody(http.MethodPost, \"/\", `{\"bar\": \"foo\"}`)\n\terr := JSON.Bind(req, &obj)\n\trequire.NoError(t, err)\n}\n\nfunc TestRequiredSucceeds(t *testing.T) {\n\ttype HogeStruct struct {\n\t\tHoge *int `json:\"hoge\" binding:\"required\"`\n\t}\n\n\tvar obj HogeStruct\n\treq := requestWithBody(http.MethodPost, \"/\", `{\"hoge\": 0}`)\n\terr := JSON.Bind(req, &obj)\n\trequire.NoError(t, err)\n}\n\nfunc TestRequiredFails(t *testing.T) {\n\ttype HogeStruct struct {\n\t\tHoge *int `json:\"foo\" binding:\"required\"`\n\t}\n\n\tvar obj HogeStruct\n\treq := requestWithBody(http.MethodPost, \"/\", `{\"boen\": 0}`)\n\terr := JSON.Bind(req, &obj)\n\trequire.Error(t, err)\n}\n\nfunc TestHeaderBinding(t *testing.T) {\n\th := Header\n\tassert.Equal(t, \"header\", h.Name())\n\n\ttype tHeader struct {\n\t\tLimit int `header:\"limit\"`\n\t}\n\n\tvar theader tHeader\n\treq := requestWithBody(http.MethodGet, \"/\", \"\")\n\treq.Header.Add(\"limit\", \"1000\")\n\trequire.NoError(t, h.Bind(req, &theader))\n\tassert.Equal(t, 1000, theader.Limit)\n\n\treq = requestWithBody(http.MethodGet, \"/\", \"\")\n\treq.Header.Add(\"fail\", `{fail:fail}`)\n\n\ttype failStruct struct {\n\t\tFail map[string]any `header:\"fail\"`\n\t}\n\n\terr := h.Bind(req, &failStruct{})\n\trequire.Error(t, err)\n}\n\nfunc TestUriBinding(t *testing.T) {\n\tb := Uri\n\tassert.Equal(t, \"uri\", b.Name())\n\n\ttype Tag struct {\n\t\tName string `uri:\"name\"`\n\t}\n\tvar tag Tag\n\tm := make(map[string][]string)\n\tm[\"name\"] = []string{\"thinkerou\"}\n\trequire.NoError(t, b.BindUri(m, &tag))\n\tassert.Equal(t, \"thinkerou\", tag.Name)\n\n\ttype NotSupportStruct struct {\n\t\tName map[string]any `uri:\"name\"`\n\t}\n\tvar not NotSupportStruct\n\trequire.Error(t, b.BindUri(m, &not))\n\tassert.Equal(t, map[string]any(nil), not.Name)\n}\n\nfunc TestUriInnerBinding(t *testing.T) {\n\ttype Tag struct {\n\t\tName string `uri:\"name\"`\n\t\tS    struct {\n\t\t\tAge int `uri:\"age\"`\n\t\t}\n\t}\n\n\texpectedName := \"mike\"\n\texpectedAge := 25\n\n\tm := map[string][]string{\n\t\t\"name\": {expectedName},\n\t\t\"age\":  {strconv.Itoa(expectedAge)},\n\t}\n\n\tvar tag Tag\n\trequire.NoError(t, Uri.BindUri(m, &tag))\n\tassert.Equal(t, expectedName, tag.Name)\n\tassert.Equal(t, expectedAge, tag.S.Age)\n}\n\nfunc testFormBindingEmbeddedStruct(t *testing.T, method, path, badPath, body, badBody string) {\n\tb := Form\n\tassert.Equal(t, \"form\", b.Name())\n\n\tobj := QueryTest{}\n\treq := requestWithBody(method, path, body)\n\tif method == http.MethodPost {\n\t\treq.Header.Add(\"Content-Type\", MIMEPOSTForm)\n\t}\n\terr := b.Bind(req, &obj)\n\trequire.NoError(t, err)\n\tassert.Equal(t, 1, obj.Page)\n\tassert.Equal(t, 2, obj.Size)\n\tassert.Equal(t, \"test-appkey\", obj.Appkey)\n}\n\nfunc testFormBinding(t *testing.T, method, path, badPath, body, badBody string) {\n\tb := Form\n\tassert.Equal(t, \"form\", b.Name())\n\n\tobj := FooBarStruct{}\n\treq := requestWithBody(method, path, body)\n\tif method == http.MethodPost {\n\t\treq.Header.Add(\"Content-Type\", MIMEPOSTForm)\n\t}\n\terr := b.Bind(req, &obj)\n\trequire.NoError(t, err)\n\tassert.Equal(t, \"bar\", obj.Foo)\n\tassert.Equal(t, \"foo\", obj.Bar)\n\n\tobj = FooBarStruct{}\n\treq = requestWithBody(method, badPath, badBody)\n\terr = JSON.Bind(req, &obj)\n\trequire.Error(t, err)\n}\n\nfunc testFormBindingDefaultValue(t *testing.T, method, path, badPath, body, badBody string) {\n\tb := Form\n\tassert.Equal(t, \"form\", b.Name())\n\n\tobj := FooDefaultBarStruct{}\n\treq := requestWithBody(method, path, body)\n\tif method == http.MethodPost {\n\t\treq.Header.Add(\"Content-Type\", MIMEPOSTForm)\n\t}\n\terr := b.Bind(req, &obj)\n\trequire.NoError(t, err)\n\tassert.Equal(t, \"bar\", obj.Foo)\n\tassert.Equal(t, \"hello\", obj.Bar)\n\n\tobj = FooDefaultBarStruct{}\n\treq = requestWithBody(method, badPath, badBody)\n\terr = JSON.Bind(req, &obj)\n\trequire.Error(t, err)\n}\n\nfunc TestFormBindingFail(t *testing.T) {\n\tb := Form\n\tassert.Equal(t, \"form\", b.Name())\n\n\tobj := FooBarStruct{}\n\treq, _ := http.NewRequest(http.MethodPost, \"/\", nil)\n\terr := b.Bind(req, &obj)\n\trequire.Error(t, err)\n}\n\nfunc TestFormBindingMultipartFail(t *testing.T) {\n\tobj := FooBarStruct{}\n\treq, err := http.NewRequest(http.MethodPost, \"/\", strings.NewReader(\"foo=bar\"))\n\trequire.NoError(t, err)\n\treq.Header.Set(\"Content-Type\", MIMEMultipartPOSTForm+\";boundary=testboundary\")\n\t_, err = req.MultipartReader()\n\trequire.NoError(t, err)\n\terr = Form.Bind(req, &obj)\n\trequire.Error(t, err)\n}\n\nfunc TestFormPostBindingFail(t *testing.T) {\n\tb := FormPost\n\tassert.Equal(t, \"form-urlencoded\", b.Name())\n\n\tobj := FooBarStruct{}\n\treq, _ := http.NewRequest(http.MethodPost, \"/\", nil)\n\terr := b.Bind(req, &obj)\n\trequire.Error(t, err)\n}\n\nfunc TestFormMultipartBindingFail(t *testing.T) {\n\tb := FormMultipart\n\tassert.Equal(t, \"multipart/form-data\", b.Name())\n\n\tobj := FooBarStruct{}\n\treq, _ := http.NewRequest(http.MethodPost, \"/\", nil)\n\terr := b.Bind(req, &obj)\n\trequire.Error(t, err)\n}\n\nfunc testFormBindingForTime(t *testing.T, method, path, badPath, body, badBody string) {\n\tb := Form\n\tassert.Equal(t, \"form\", b.Name())\n\n\tobj := FooBarStructForTimeType{}\n\treq := requestWithBody(method, path, body)\n\tif method == http.MethodPost {\n\t\treq.Header.Add(\"Content-Type\", MIMEPOSTForm)\n\t}\n\terr := b.Bind(req, &obj)\n\n\trequire.NoError(t, err)\n\tassert.Equal(t, int64(1510675200), obj.TimeFoo.Unix())\n\tassert.Equal(t, \"Asia/Chongqing\", obj.TimeFoo.Location().String())\n\tassert.Equal(t, int64(-62135596800), obj.TimeBar.Unix())\n\tassert.Equal(t, \"UTC\", obj.TimeBar.Location().String())\n\tassert.Equal(t, int64(1562400033000000123), obj.CreateTime.UnixNano())\n\tassert.Equal(t, int64(1562400033), obj.UnixTime.Unix())\n\tassert.Equal(t, int64(1562400033001), obj.UnixMilliTime.UnixMilli())\n\tassert.Equal(t, int64(1562400033000012), obj.UnixMicroTime.UnixMicro())\n\n\tobj = FooBarStructForTimeType{}\n\treq = requestWithBody(method, badPath, badBody)\n\terr = JSON.Bind(req, &obj)\n\trequire.Error(t, err)\n}\n\nfunc testFormBindingForTimeNotUnixFormat(t *testing.T, method, path, badPath, body, badBody string) {\n\tb := Form\n\tassert.Equal(t, \"form\", b.Name())\n\n\tobj := FooStructForTimeTypeNotUnixFormat{}\n\treq := requestWithBody(method, path, body)\n\tif method == http.MethodPost {\n\t\treq.Header.Add(\"Content-Type\", MIMEPOSTForm)\n\t}\n\terr := b.Bind(req, &obj)\n\trequire.Error(t, err)\n\n\tobj = FooStructForTimeTypeNotUnixFormat{}\n\treq = requestWithBody(method, badPath, badBody)\n\terr = JSON.Bind(req, &obj)\n\trequire.Error(t, err)\n}\n\nfunc testFormBindingForTimeNotFormat(t *testing.T, method, path, badPath, body, badBody string) {\n\tb := Form\n\tassert.Equal(t, \"form\", b.Name())\n\n\tobj := FooStructForTimeTypeNotFormat{}\n\treq := requestWithBody(method, path, body)\n\tif method == http.MethodPost {\n\t\treq.Header.Add(\"Content-Type\", MIMEPOSTForm)\n\t}\n\terr := b.Bind(req, &obj)\n\trequire.Error(t, err)\n\n\tobj = FooStructForTimeTypeNotFormat{}\n\treq = requestWithBody(method, badPath, badBody)\n\terr = JSON.Bind(req, &obj)\n\trequire.Error(t, err)\n}\n\nfunc testFormBindingForTimeFailFormat(t *testing.T, method, path, badPath, body, badBody string) {\n\tb := Form\n\tassert.Equal(t, \"form\", b.Name())\n\n\tobj := FooStructForTimeTypeFailFormat{}\n\treq := requestWithBody(method, path, body)\n\tif method == http.MethodPost {\n\t\treq.Header.Add(\"Content-Type\", MIMEPOSTForm)\n\t}\n\terr := b.Bind(req, &obj)\n\trequire.Error(t, err)\n\n\tobj = FooStructForTimeTypeFailFormat{}\n\treq = requestWithBody(method, badPath, badBody)\n\terr = JSON.Bind(req, &obj)\n\trequire.Error(t, err)\n}\n\nfunc testFormBindingForTimeFailLocation(t *testing.T, method, path, badPath, body, badBody string) {\n\tb := Form\n\tassert.Equal(t, \"form\", b.Name())\n\n\tobj := FooStructForTimeTypeFailLocation{}\n\treq := requestWithBody(method, path, body)\n\tif method == http.MethodPost {\n\t\treq.Header.Add(\"Content-Type\", MIMEPOSTForm)\n\t}\n\terr := b.Bind(req, &obj)\n\trequire.Error(t, err)\n\n\tobj = FooStructForTimeTypeFailLocation{}\n\treq = requestWithBody(method, badPath, badBody)\n\terr = JSON.Bind(req, &obj)\n\trequire.Error(t, err)\n}\n\nfunc testFormBindingIgnoreField(t *testing.T, method, path, badPath, body, badBody string) {\n\tb := Form\n\tassert.Equal(t, \"form\", b.Name())\n\n\tobj := FooStructForIgnoreFormTag{}\n\treq := requestWithBody(method, path, body)\n\tif method == http.MethodPost {\n\t\treq.Header.Add(\"Content-Type\", MIMEPOSTForm)\n\t}\n\terr := b.Bind(req, &obj)\n\trequire.NoError(t, err)\n\n\tassert.Nil(t, obj.Foo)\n}\n\nfunc testFormBindingInvalidName(t *testing.T, method, path, badPath, body, badBody string) {\n\tb := Form\n\tassert.Equal(t, \"form\", b.Name())\n\n\tobj := InvalidNameType{}\n\treq := requestWithBody(method, path, body)\n\tif method == http.MethodPost {\n\t\treq.Header.Add(\"Content-Type\", MIMEPOSTForm)\n\t}\n\terr := b.Bind(req, &obj)\n\trequire.NoError(t, err)\n\tassert.Empty(t, obj.TestName)\n\n\tobj = InvalidNameType{}\n\treq = requestWithBody(method, badPath, badBody)\n\terr = JSON.Bind(req, &obj)\n\trequire.Error(t, err)\n}\n\nfunc testFormBindingInvalidName2(t *testing.T, method, path, badPath, body, badBody string) {\n\tb := Form\n\tassert.Equal(t, \"form\", b.Name())\n\n\tobj := InvalidNameMapType{}\n\treq := requestWithBody(method, path, body)\n\tif method == http.MethodPost {\n\t\treq.Header.Add(\"Content-Type\", MIMEPOSTForm)\n\t}\n\terr := b.Bind(req, &obj)\n\trequire.Error(t, err)\n\n\tobj = InvalidNameMapType{}\n\treq = requestWithBody(method, badPath, badBody)\n\terr = JSON.Bind(req, &obj)\n\trequire.Error(t, err)\n}\n\nfunc testFormBindingForType(t *testing.T, method, path, badPath, body, badBody string, typ string) {\n\tb := Form\n\tassert.Equal(t, \"form\", b.Name())\n\n\treq := requestWithBody(method, path, body)\n\tif method == http.MethodPost {\n\t\treq.Header.Add(\"Content-Type\", MIMEPOSTForm)\n\t}\n\tswitch typ {\n\tcase \"Slice\":\n\t\tobj := FooStructForSliceType{}\n\t\terr := b.Bind(req, &obj)\n\t\trequire.NoError(t, err)\n\t\tassert.Equal(t, []int{1, 2}, obj.SliceFoo)\n\n\t\tobj = FooStructForSliceType{}\n\t\treq = requestWithBody(method, badPath, badBody)\n\t\terr = JSON.Bind(req, &obj)\n\t\trequire.Error(t, err)\n\tcase \"Struct\":\n\t\tobj := FooStructForStructType{}\n\t\terr := b.Bind(req, &obj)\n\t\trequire.NoError(t, err)\n\t\tassert.Equal(t,\n\t\t\tstruct {\n\t\t\t\tIdx int \"form:\\\"idx\\\"\"\n\t\t\t}{Idx: 123},\n\t\t\tobj.StructFoo)\n\tcase \"StructPointer\":\n\t\tobj := FooStructForStructPointerType{}\n\t\terr := b.Bind(req, &obj)\n\t\trequire.NoError(t, err)\n\t\tassert.Equal(t,\n\t\t\tstruct {\n\t\t\t\tName string \"form:\\\"name\\\"\"\n\t\t\t}{Name: \"thinkerou\"},\n\t\t\t*obj.StructPointerFoo)\n\tcase \"Map\":\n\t\tobj := FooStructForMapType{}\n\t\terr := b.Bind(req, &obj)\n\t\trequire.NoError(t, err)\n\t\tassert.InDelta(t, float64(123), obj.MapFoo[\"bar\"].(float64), 0.01)\n\tcase \"SliceMap\":\n\t\tobj := FooStructForSliceMapType{}\n\t\terr := b.Bind(req, &obj)\n\t\trequire.Error(t, err)\n\tcase \"Ptr\":\n\t\tobj := FooStructForStringPtrType{}\n\t\terr := b.Bind(req, &obj)\n\t\trequire.NoError(t, err)\n\t\tassert.Nil(t, obj.PtrFoo)\n\t\tassert.Equal(t, \"test\", *obj.PtrBar)\n\n\t\tobj = FooStructForStringPtrType{}\n\t\tobj.PtrBar = new(string)\n\t\terr = b.Bind(req, &obj)\n\t\trequire.NoError(t, err)\n\t\tassert.Equal(t, \"test\", *obj.PtrBar)\n\n\t\tobjErr := FooStructForMapPtrType{}\n\t\terr = b.Bind(req, &objErr)\n\t\trequire.Error(t, err)\n\n\t\tobj = FooStructForStringPtrType{}\n\t\treq = requestWithBody(method, badPath, badBody)\n\t\terr = b.Bind(req, &obj)\n\t\trequire.Error(t, err)\n\t}\n}\n\nfunc testQueryBinding(t *testing.T, method, path, badPath, body, badBody string) {\n\tb := Query\n\tassert.Equal(t, \"query\", b.Name())\n\n\tobj := FooBarStruct{}\n\treq := requestWithBody(method, path, body)\n\tif method == http.MethodPost {\n\t\treq.Header.Add(\"Content-Type\", MIMEPOSTForm)\n\t}\n\terr := b.Bind(req, &obj)\n\trequire.NoError(t, err)\n\tassert.Equal(t, \"bar\", obj.Foo)\n\tassert.Equal(t, \"foo\", obj.Bar)\n}\n\nfunc testQueryBindingFail(t *testing.T, method, path, badPath, body, badBody string) {\n\tb := Query\n\tassert.Equal(t, \"query\", b.Name())\n\n\tobj := FooStructForMapType{}\n\treq := requestWithBody(method, path, body)\n\tif method == http.MethodPost {\n\t\treq.Header.Add(\"Content-Type\", MIMEPOSTForm)\n\t}\n\terr := b.Bind(req, &obj)\n\trequire.Error(t, err)\n}\n\nfunc testQueryBindingBoolFail(t *testing.T, method, path, badPath, body, badBody string) {\n\tb := Query\n\tassert.Equal(t, \"query\", b.Name())\n\n\tobj := FooStructForBoolType{}\n\treq := requestWithBody(method, path, body)\n\tif method == http.MethodPost {\n\t\treq.Header.Add(\"Content-Type\", MIMEPOSTForm)\n\t}\n\terr := b.Bind(req, &obj)\n\trequire.Error(t, err)\n}\n\nfunc testBodyBinding(t *testing.T, b Binding, name, path, badPath, body, badBody string) {\n\tassert.Equal(t, name, b.Name())\n\n\tobj := FooStruct{}\n\treq := requestWithBody(http.MethodPost, path, body)\n\terr := b.Bind(req, &obj)\n\trequire.NoError(t, err)\n\tassert.Equal(t, \"bar\", obj.Foo)\n\n\tobj = FooStruct{}\n\treq = requestWithBody(http.MethodPost, badPath, badBody)\n\terr = JSON.Bind(req, &obj)\n\trequire.Error(t, err)\n}\n\nfunc testBodyBindingSlice(t *testing.T, b Binding, name, path, badPath, body, badBody string) {\n\tassert.Equal(t, name, b.Name())\n\n\tvar obj1 []FooStruct\n\treq := requestWithBody(http.MethodPost, path, body)\n\terr := b.Bind(req, &obj1)\n\trequire.NoError(t, err)\n\n\tvar obj2 []FooStruct\n\treq = requestWithBody(http.MethodPost, badPath, badBody)\n\terr = JSON.Bind(req, &obj2)\n\trequire.Error(t, err)\n}\n\nfunc testBodyBindingStringMap(t *testing.T, b Binding, path, badPath, body, badBody string) {\n\tobj := make(map[string]string)\n\treq := requestWithBody(http.MethodPost, path, body)\n\tif b.Name() == \"form\" {\n\t\treq.Header.Add(\"Content-Type\", MIMEPOSTForm)\n\t}\n\terr := b.Bind(req, &obj)\n\trequire.NoError(t, err)\n\tassert.NotNil(t, obj)\n\tassert.Len(t, obj, 2)\n\tassert.Equal(t, \"bar\", obj[\"foo\"])\n\tassert.Equal(t, \"world\", obj[\"hello\"])\n\n\tif badPath != \"\" && badBody != \"\" {\n\t\tobj = make(map[string]string)\n\t\treq = requestWithBody(http.MethodPost, badPath, badBody)\n\t\terr = b.Bind(req, &obj)\n\t\trequire.Error(t, err)\n\t}\n\n\tobjInt := make(map[string]int)\n\treq = requestWithBody(http.MethodPost, path, body)\n\terr = b.Bind(req, &objInt)\n\trequire.Error(t, err)\n}\n\nfunc testBodyBindingUseNumber(t *testing.T, b Binding, name, path, badPath, body, badBody string) {\n\tassert.Equal(t, name, b.Name())\n\n\tobj := FooStructUseNumber{}\n\treq := requestWithBody(http.MethodPost, path, body)\n\tEnableDecoderUseNumber = true\n\terr := b.Bind(req, &obj)\n\trequire.NoError(t, err)\n\t// we hope it is int64(123)\n\tv, e := obj.Foo.(json.Number).Int64()\n\trequire.NoError(t, e)\n\tassert.Equal(t, int64(123), v)\n\n\tobj = FooStructUseNumber{}\n\treq = requestWithBody(http.MethodPost, badPath, badBody)\n\terr = JSON.Bind(req, &obj)\n\trequire.Error(t, err)\n}\n\nfunc testBodyBindingUseNumber2(t *testing.T, b Binding, name, path, badPath, body, badBody string) {\n\tassert.Equal(t, name, b.Name())\n\n\tobj := FooStructUseNumber{}\n\treq := requestWithBody(http.MethodPost, path, body)\n\tEnableDecoderUseNumber = false\n\terr := b.Bind(req, &obj)\n\trequire.NoError(t, err)\n\t// it will return float64(123) if not use EnableDecoderUseNumber\n\t// maybe it is not hoped\n\tassert.InDelta(t, float64(123), obj.Foo, 0.01)\n\n\tobj = FooStructUseNumber{}\n\treq = requestWithBody(http.MethodPost, badPath, badBody)\n\terr = JSON.Bind(req, &obj)\n\trequire.Error(t, err)\n}\n\nfunc testBodyBindingDisallowUnknownFields(t *testing.T, b Binding, path, badPath, body, badBody string) {\n\tEnableDecoderDisallowUnknownFields = true\n\tdefer func() {\n\t\tEnableDecoderDisallowUnknownFields = false\n\t}()\n\n\tobj := FooStructDisallowUnknownFields{}\n\treq := requestWithBody(http.MethodPost, path, body)\n\terr := b.Bind(req, &obj)\n\trequire.NoError(t, err)\n\tassert.Equal(t, \"bar\", obj.Foo)\n\n\tobj = FooStructDisallowUnknownFields{}\n\treq = requestWithBody(http.MethodPost, badPath, badBody)\n\terr = JSON.Bind(req, &obj)\n\trequire.Error(t, err)\n\tassert.Contains(t, err.Error(), \"what\")\n}\n\nfunc testBodyBindingFail(t *testing.T, b Binding, name, path, badPath, body, badBody string) {\n\tassert.Equal(t, name, b.Name())\n\n\tobj := FooStruct{}\n\treq := requestWithBody(http.MethodPost, path, body)\n\terr := b.Bind(req, &obj)\n\trequire.Error(t, err)\n\tassert.Empty(t, obj.Foo)\n\n\tobj = FooStruct{}\n\treq = requestWithBody(http.MethodPost, badPath, badBody)\n\terr = JSON.Bind(req, &obj)\n\trequire.Error(t, err)\n}\n\nfunc testProtoBodyBinding(t *testing.T, b Binding, name, path, badPath, body, badBody string) {\n\tassert.Equal(t, name, b.Name())\n\n\tobj := protoexample.Test{}\n\treq := requestWithBody(http.MethodPost, path, body)\n\treq.Header.Add(\"Content-Type\", MIMEPROTOBUF)\n\terr := b.Bind(req, &obj)\n\trequire.NoError(t, err)\n\tassert.Equal(t, \"yes\", *obj.Label)\n\n\tobj = protoexample.Test{}\n\treq = requestWithBody(http.MethodPost, badPath, badBody)\n\treq.Header.Add(\"Content-Type\", MIMEPROTOBUF)\n\terr = ProtoBuf.Bind(req, &obj)\n\trequire.Error(t, err)\n}\n\ntype hook struct{}\n\nfunc (h hook) Read([]byte) (int, error) {\n\treturn 0, errors.New(\"error\")\n}\n\ntype failRead struct{}\n\nfunc (f *failRead) Read(b []byte) (n int, err error) {\n\treturn 0, errors.New(\"my fail\")\n}\n\nfunc (f *failRead) Close() error {\n\treturn nil\n}\n\nfunc TestPlainBinding(t *testing.T) {\n\tp := Plain\n\tassert.Equal(t, \"plain\", p.Name())\n\n\tvar s string\n\treq := requestWithBody(http.MethodPost, \"/\", \"test string\")\n\trequire.NoError(t, p.Bind(req, &s))\n\tassert.Equal(t, \"test string\", s)\n\n\tvar bs []byte\n\treq = requestWithBody(http.MethodPost, \"/\", \"test []byte\")\n\trequire.NoError(t, p.Bind(req, &bs))\n\tassert.Equal(t, bs, []byte(\"test []byte\"))\n\n\tvar i int\n\treq = requestWithBody(http.MethodPost, \"/\", \"test fail\")\n\trequire.Error(t, p.Bind(req, &i))\n\n\treq = requestWithBody(http.MethodPost, \"/\", \"\")\n\treq.Body = &failRead{}\n\trequire.Error(t, p.Bind(req, &s))\n\n\treq = requestWithBody(http.MethodPost, \"/\", \"\")\n\trequire.NoError(t, p.Bind(req, nil))\n\n\tvar ptr *string\n\treq = requestWithBody(http.MethodPost, \"/\", \"\")\n\trequire.NoError(t, p.Bind(req, ptr))\n}\n\nfunc testProtoBodyBindingFail(t *testing.T, b Binding, name, path, badPath, body, badBody string) {\n\tassert.Equal(t, name, b.Name())\n\n\tobj := protoexample.Test{}\n\treq := requestWithBody(http.MethodPost, path, body)\n\n\treq.Body = io.NopCloser(&hook{})\n\treq.Header.Add(\"Content-Type\", MIMEPROTOBUF)\n\terr := b.Bind(req, &obj)\n\trequire.Error(t, err)\n\n\tinvalidobj := FooStruct{}\n\treq.Body = io.NopCloser(strings.NewReader(`{\"msg\":\"hello\"}`))\n\treq.Header.Add(\"Content-Type\", MIMEPROTOBUF)\n\terr = b.Bind(req, &invalidobj)\n\trequire.Error(t, err)\n\tassert.Equal(t, \"obj is not ProtoMessage\", err.Error())\n\n\tobj = protoexample.Test{}\n\treq = requestWithBody(http.MethodPost, badPath, badBody)\n\treq.Header.Add(\"Content-Type\", MIMEPROTOBUF)\n\terr = ProtoBuf.Bind(req, &obj)\n\trequire.Error(t, err)\n}\n\nfunc requestWithBody(method, path, body string) (req *http.Request) {\n\treq, _ = http.NewRequest(method, path, bytes.NewBufferString(body))\n\treturn\n}\n"
  },
  {
    "path": "binding/bson.go",
    "content": "// Copyright 2025 Gin Core Team. All rights reserved.\n// Use of this source code is governed by a MIT style\n// license that can be found in the LICENSE file.\n\npackage binding\n\nimport (\n\t\"io\"\n\t\"net/http\"\n\n\t\"go.mongodb.org/mongo-driver/v2/bson\"\n)\n\ntype bsonBinding struct{}\n\nfunc (bsonBinding) Name() string {\n\treturn \"bson\"\n}\n\nfunc (b bsonBinding) Bind(req *http.Request, obj any) error {\n\tbuf, err := io.ReadAll(req.Body)\n\tif err == nil {\n\t\terr = b.BindBody(buf, obj)\n\t}\n\treturn err\n}\n\nfunc (bsonBinding) BindBody(body []byte, obj any) error {\n\treturn bson.Unmarshal(body, obj)\n}\n"
  },
  {
    "path": "binding/default_validator.go",
    "content": "// Copyright 2017 Manu Martinez-Almeida. All rights reserved.\n// Use of this source code is governed by a MIT style\n// license that can be found in the LICENSE file.\n\npackage binding\n\nimport (\n\t\"reflect\"\n\t\"strconv\"\n\t\"strings\"\n\t\"sync\"\n\n\t\"github.com/go-playground/validator/v10\"\n)\n\ntype defaultValidator struct {\n\tonce     sync.Once\n\tvalidate *validator.Validate\n}\n\ntype SliceValidationError []error\n\n// Error concatenates all error elements in SliceValidationError into a single string separated by \\n.\nfunc (err SliceValidationError) Error() string {\n\tif len(err) == 0 {\n\t\treturn \"\"\n\t}\n\n\tvar b strings.Builder\n\tfor i := range len(err) {\n\t\tif err[i] != nil {\n\t\t\tif b.Len() > 0 {\n\t\t\t\tb.WriteString(\"\\n\")\n\t\t\t}\n\t\t\tb.WriteString(\"[\" + strconv.Itoa(i) + \"]: \" + err[i].Error())\n\t\t}\n\t}\n\treturn b.String()\n}\n\nvar _ StructValidator = (*defaultValidator)(nil)\n\n// ValidateStruct receives any kind of type, but only performed struct or pointer to struct type.\nfunc (v *defaultValidator) ValidateStruct(obj any) error {\n\tif obj == nil {\n\t\treturn nil\n\t}\n\n\tvalue := reflect.ValueOf(obj)\n\tswitch value.Kind() {\n\tcase reflect.Ptr:\n\t\tif value.Elem().Kind() != reflect.Struct {\n\t\t\treturn v.ValidateStruct(value.Elem().Interface())\n\t\t}\n\t\treturn v.validateStruct(obj)\n\tcase reflect.Struct:\n\t\treturn v.validateStruct(obj)\n\tcase reflect.Slice, reflect.Array:\n\t\tcount := value.Len()\n\t\tvalidateRet := make(SliceValidationError, 0)\n\t\tfor i := range count {\n\t\t\tif err := v.ValidateStruct(value.Index(i).Interface()); err != nil {\n\t\t\t\tvalidateRet = append(validateRet, err)\n\t\t\t}\n\t\t}\n\t\tif len(validateRet) == 0 {\n\t\t\treturn nil\n\t\t}\n\t\treturn validateRet\n\tdefault:\n\t\treturn nil\n\t}\n}\n\n// validateStruct receives struct type\nfunc (v *defaultValidator) validateStruct(obj any) error {\n\tv.lazyinit()\n\treturn v.validate.Struct(obj)\n}\n\n// Engine returns the underlying validator engine which powers the default\n// Validator instance. This is useful if you want to register custom validations\n// or struct level validations. See validator GoDoc for more info -\n// https://pkg.go.dev/github.com/go-playground/validator/v10\nfunc (v *defaultValidator) Engine() any {\n\tv.lazyinit()\n\treturn v.validate\n}\n\nfunc (v *defaultValidator) lazyinit() {\n\tv.once.Do(func() {\n\t\tv.validate = validator.New()\n\t\tv.validate.SetTagName(\"binding\")\n\t})\n}\n"
  },
  {
    "path": "binding/default_validator_benchmark_test.go",
    "content": "// Copyright 2022 Gin Core Team. All rights reserved.\n// Use of this source code is governed by a MIT style\n// license that can be found in the LICENSE file.\n\npackage binding\n\nimport (\n\t\"errors\"\n\t\"strconv\"\n\t\"testing\"\n)\n\nfunc BenchmarkSliceValidationError(b *testing.B) {\n\tconst size int = 100\n\te := make(SliceValidationError, size)\n\tfor j := 0; j < size; j++ {\n\t\te[j] = errors.New(strconv.Itoa(j))\n\t}\n\n\tb.ReportAllocs()\n\n\tfor b.Loop() {\n\t\tif len(e.Error()) == 0 {\n\t\t\tb.Errorf(\"error\")\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "binding/default_validator_test.go",
    "content": "// Copyright 2020 Gin Core Team. All rights reserved.\n// Use of this source code is governed by a MIT style\n// license that can be found in the LICENSE file.\n\npackage binding\n\nimport (\n\t\"errors\"\n\t\"testing\"\n)\n\nfunc TestSliceValidationError(t *testing.T) {\n\ttests := []struct {\n\t\tname string\n\t\terr  SliceValidationError\n\t\twant string\n\t}{\n\t\t{\"has nil elements\", SliceValidationError{errors.New(\"test error\"), nil}, \"[0]: test error\"},\n\t\t{\"has zero elements\", SliceValidationError{}, \"\"},\n\t\t{\"has one element\", SliceValidationError{errors.New(\"test one error\")}, \"[0]: test one error\"},\n\t\t{\n\t\t\t\"has two elements\",\n\t\t\tSliceValidationError{\n\t\t\t\terrors.New(\"first error\"),\n\t\t\t\terrors.New(\"second error\"),\n\t\t\t},\n\t\t\t\"[0]: first error\\n[1]: second error\",\n\t\t},\n\t\t{\n\t\t\t\"has many elements\",\n\t\t\tSliceValidationError{\n\t\t\t\terrors.New(\"first error\"),\n\t\t\t\terrors.New(\"second error\"),\n\t\t\t\tnil,\n\t\t\t\tnil,\n\t\t\t\tnil,\n\t\t\t\terrors.New(\"last error\"),\n\t\t\t},\n\t\t\t\"[0]: first error\\n[1]: second error\\n[5]: last error\",\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tif got := tt.err.Error(); got != tt.want {\n\t\t\t\tt.Errorf(\"SliceValidationError.Error() = %v, want %v\", got, tt.want)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestDefaultValidator(t *testing.T) {\n\ttype exampleStruct struct {\n\t\tA string `binding:\"max=8\"`\n\t\tB int    `binding:\"gt=0\"`\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\tv       *defaultValidator\n\t\tobj     any\n\t\twantErr bool\n\t}{\n\t\t{\"validate nil obj\", &defaultValidator{}, nil, false},\n\t\t{\"validate int obj\", &defaultValidator{}, 3, false},\n\t\t{\"validate struct failed-1\", &defaultValidator{}, exampleStruct{A: \"123456789\", B: 1}, true},\n\t\t{\"validate struct failed-2\", &defaultValidator{}, exampleStruct{A: \"12345678\", B: 0}, true},\n\t\t{\"validate struct passed\", &defaultValidator{}, exampleStruct{A: \"12345678\", B: 1}, false},\n\t\t{\"validate *struct failed-1\", &defaultValidator{}, &exampleStruct{A: \"123456789\", B: 1}, true},\n\t\t{\"validate *struct failed-2\", &defaultValidator{}, &exampleStruct{A: \"12345678\", B: 0}, true},\n\t\t{\"validate *struct passed\", &defaultValidator{}, &exampleStruct{A: \"12345678\", B: 1}, false},\n\t\t{\"validate []struct failed-1\", &defaultValidator{}, []exampleStruct{{A: \"123456789\", B: 1}}, true},\n\t\t{\"validate []struct failed-2\", &defaultValidator{}, []exampleStruct{{A: \"12345678\", B: 0}}, true},\n\t\t{\"validate []struct passed\", &defaultValidator{}, []exampleStruct{{A: \"12345678\", B: 1}}, false},\n\t\t{\"validate []*struct failed-1\", &defaultValidator{}, []*exampleStruct{{A: \"123456789\", B: 1}}, true},\n\t\t{\"validate []*struct failed-2\", &defaultValidator{}, []*exampleStruct{{A: \"12345678\", B: 0}}, true},\n\t\t{\"validate []*struct passed\", &defaultValidator{}, []*exampleStruct{{A: \"12345678\", B: 1}}, false},\n\t\t{\"validate *[]struct failed-1\", &defaultValidator{}, &[]exampleStruct{{A: \"123456789\", B: 1}}, true},\n\t\t{\"validate *[]struct failed-2\", &defaultValidator{}, &[]exampleStruct{{A: \"12345678\", B: 0}}, true},\n\t\t{\"validate *[]struct passed\", &defaultValidator{}, &[]exampleStruct{{A: \"12345678\", B: 1}}, false},\n\t\t{\"validate *[]*struct failed-1\", &defaultValidator{}, &[]*exampleStruct{{A: \"123456789\", B: 1}}, true},\n\t\t{\"validate *[]*struct failed-2\", &defaultValidator{}, &[]*exampleStruct{{A: \"12345678\", B: 0}}, true},\n\t\t{\"validate *[]*struct passed\", &defaultValidator{}, &[]*exampleStruct{{A: \"12345678\", B: 1}}, false},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tif err := tt.v.ValidateStruct(tt.obj); (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"defaultValidator.Validate() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "binding/form.go",
    "content": "// Copyright 2014 Manu Martinez-Almeida. All rights reserved.\n// Use of this source code is governed by a MIT style\n// license that can be found in the LICENSE file.\n\npackage binding\n\nimport (\n\t\"errors\"\n\t\"net/http\"\n)\n\nconst defaultMemory = 32 << 20\n\ntype (\n\tformBinding          struct{}\n\tformPostBinding      struct{}\n\tformMultipartBinding struct{}\n)\n\nfunc (formBinding) Name() string {\n\treturn \"form\"\n}\n\nfunc (formBinding) Bind(req *http.Request, obj any) error {\n\tif err := req.ParseForm(); err != nil {\n\t\treturn err\n\t}\n\tif err := req.ParseMultipartForm(defaultMemory); err != nil && !errors.Is(err, http.ErrNotMultipart) {\n\t\treturn err\n\t}\n\tif err := mapForm(obj, req.Form); err != nil {\n\t\treturn err\n\t}\n\treturn validate(obj)\n}\n\nfunc (formPostBinding) Name() string {\n\treturn \"form-urlencoded\"\n}\n\nfunc (formPostBinding) Bind(req *http.Request, obj any) error {\n\tif err := req.ParseForm(); err != nil {\n\t\treturn err\n\t}\n\tif err := mapForm(obj, req.PostForm); err != nil {\n\t\treturn err\n\t}\n\treturn validate(obj)\n}\n\nfunc (formMultipartBinding) Name() string {\n\treturn \"multipart/form-data\"\n}\n\nfunc (formMultipartBinding) Bind(req *http.Request, obj any) error {\n\tif err := req.ParseMultipartForm(defaultMemory); err != nil {\n\t\treturn err\n\t}\n\tif err := mappingByPtr(obj, (*multipartRequest)(req), \"form\"); err != nil {\n\t\treturn err\n\t}\n\n\treturn validate(obj)\n}\n"
  },
  {
    "path": "binding/form_mapping.go",
    "content": "// Copyright 2014 Manu Martinez-Almeida. All rights reserved.\n// Use of this source code is governed by a MIT style\n// license that can be found in the LICENSE file.\n\npackage binding\n\nimport (\n\t\"encoding\"\n\t\"errors\"\n\t\"fmt\"\n\t\"maps\"\n\t\"mime/multipart\"\n\t\"reflect\"\n\t\"strconv\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/gin-gonic/gin/codec/json\"\n\t\"github.com/gin-gonic/gin/internal/bytesconv\"\n)\n\nvar (\n\terrUnknownType = errors.New(\"unknown type\")\n\n\t// ErrConvertMapStringSlice can not convert to map[string][]string\n\tErrConvertMapStringSlice = errors.New(\"can not convert to map slices of strings\")\n\n\t// ErrConvertToMapString can not convert to map[string]string\n\tErrConvertToMapString = errors.New(\"can not convert to map of strings\")\n)\n\nfunc mapURI(ptr any, m map[string][]string) error {\n\treturn mapFormByTag(ptr, m, \"uri\")\n}\n\nfunc mapForm(ptr any, form map[string][]string) error {\n\treturn mapFormByTag(ptr, form, \"form\")\n}\n\nfunc MapFormWithTag(ptr any, form map[string][]string, tag string) error {\n\treturn mapFormByTag(ptr, form, tag)\n}\n\nvar emptyField = reflect.StructField{}\n\nfunc mapFormByTag(ptr any, form map[string][]string, tag string) error {\n\t// Check if ptr is a map\n\tptrVal := reflect.ValueOf(ptr)\n\tvar pointed any\n\tif ptrVal.Kind() == reflect.Ptr {\n\t\tptrVal = ptrVal.Elem()\n\t\tpointed = ptrVal.Interface()\n\t}\n\tif ptrVal.Kind() == reflect.Map &&\n\t\tptrVal.Type().Key().Kind() == reflect.String {\n\t\tif pointed != nil {\n\t\t\tptr = pointed\n\t\t}\n\t\treturn setFormMap(ptr, form)\n\t}\n\n\treturn mappingByPtr(ptr, formSource(form), tag)\n}\n\n// setter tries to set value on a walking by fields of a struct\ntype setter interface {\n\tTrySet(value reflect.Value, field reflect.StructField, key string, opt setOptions) (isSet bool, err error)\n}\n\ntype formSource map[string][]string\n\nvar _ setter = formSource(nil)\n\n// TrySet tries to set a value by request's form source (like map[string][]string)\nfunc (form formSource) TrySet(value reflect.Value, field reflect.StructField, tagValue string, opt setOptions) (isSet bool, err error) {\n\treturn setByForm(value, field, form, tagValue, opt)\n}\n\nfunc mappingByPtr(ptr any, setter setter, tag string) error {\n\t_, err := mapping(reflect.ValueOf(ptr), emptyField, setter, tag)\n\treturn err\n}\n\nfunc mapping(value reflect.Value, field reflect.StructField, setter setter, tag string) (bool, error) {\n\tif field.Tag.Get(tag) == \"-\" { // just ignoring this field\n\t\treturn false, nil\n\t}\n\n\tvKind := value.Kind()\n\n\tif vKind == reflect.Ptr {\n\t\tvar isNew bool\n\t\tvPtr := value\n\t\tif value.IsNil() {\n\t\t\tisNew = true\n\t\t\tvPtr = reflect.New(value.Type().Elem())\n\t\t}\n\t\tisSet, err := mapping(vPtr.Elem(), field, setter, tag)\n\t\tif err != nil {\n\t\t\treturn false, err\n\t\t}\n\t\tif isNew && isSet {\n\t\t\tvalue.Set(vPtr)\n\t\t}\n\t\treturn isSet, nil\n\t}\n\n\tif vKind != reflect.Struct || !field.Anonymous {\n\t\tok, err := tryToSetValue(value, field, setter, tag)\n\t\tif err != nil {\n\t\t\treturn false, err\n\t\t}\n\t\tif ok {\n\t\t\treturn true, nil\n\t\t}\n\t}\n\n\tif vKind == reflect.Struct {\n\t\ttValue := value.Type()\n\n\t\tvar isSet bool\n\t\tfor i := range value.NumField() {\n\t\t\tsf := tValue.Field(i)\n\t\t\tif sf.PkgPath != \"\" && !sf.Anonymous { // unexported\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tok, err := mapping(value.Field(i), sf, setter, tag)\n\t\t\tif err != nil {\n\t\t\t\treturn false, err\n\t\t\t}\n\t\t\tisSet = isSet || ok\n\t\t}\n\t\treturn isSet, nil\n\t}\n\treturn false, nil\n}\n\ntype setOptions struct {\n\tisDefaultExists bool\n\tdefaultValue    string\n\t// parser specifies what interface to use for reading the request & default values (e.g. `encoding.TextUnmarshaler`)\n\tparser string\n}\n\nfunc tryToSetValue(value reflect.Value, field reflect.StructField, setter setter, tag string) (bool, error) {\n\tvar tagValue string\n\tvar setOpt setOptions\n\n\ttagValue = field.Tag.Get(tag)\n\ttagValue, opts := head(tagValue, \",\")\n\n\tif tagValue == \"\" { // default value is FieldName\n\t\ttagValue = field.Name\n\t}\n\tif tagValue == \"\" { // when field is \"emptyField\" variable\n\t\treturn false, nil\n\t}\n\n\tvar opt string\n\tfor len(opts) > 0 {\n\t\topt, opts = head(opts, \",\")\n\n\t\tif k, v := head(opt, \"=\"); k == \"default\" {\n\t\t\tsetOpt.isDefaultExists = true\n\t\t\tsetOpt.defaultValue = v\n\n\t\t\t// convert semicolon-separated default values to csv-separated values for processing in setByForm\n\t\t\tif field.Type.Kind() == reflect.Slice || field.Type.Kind() == reflect.Array {\n\t\t\t\tcfTag := field.Tag.Get(\"collection_format\")\n\t\t\t\tif cfTag == \"\" || cfTag == \"multi\" || cfTag == \"csv\" {\n\t\t\t\t\tsetOpt.defaultValue = strings.ReplaceAll(v, \";\", \",\")\n\t\t\t\t}\n\t\t\t}\n\t\t} else if k, v = head(opt, \"=\"); k == \"parser\" {\n\t\t\tsetOpt.parser = v\n\t\t}\n\t}\n\n\treturn setter.TrySet(value, field, tagValue, setOpt)\n}\n\n// BindUnmarshaler is the interface used to wrap the UnmarshalParam method.\ntype BindUnmarshaler interface {\n\t// UnmarshalParam decodes and assigns a value from a form or query param.\n\tUnmarshalParam(param string) error\n}\n\n// trySetCustom tries to set a custom type value\n// If the value implements the BindUnmarshaler interface, it will be used to set the value, we will return `true`\n// to skip the default value setting.\nfunc trySetCustom(val string, value reflect.Value) (isSet bool, err error) {\n\tswitch v := value.Addr().Interface().(type) {\n\tcase BindUnmarshaler:\n\t\treturn true, v.UnmarshalParam(val)\n\t}\n\treturn false, nil\n}\n\n// trySetUsingParser tries to set a custom type value based on the presence of the \"parser\" tag on the field.\n// If the parser tag does not exist or does not match any of the supported parsers, gin will skip over this.\nfunc trySetUsingParser(val string, value reflect.Value, parser string) (isSet bool, err error) {\n\tswitch parser {\n\tcase \"encoding.TextUnmarshaler\":\n\t\tv, ok := value.Addr().Interface().(encoding.TextUnmarshaler)\n\t\tif !ok {\n\t\t\treturn false, nil\n\t\t}\n\t\treturn true, v.UnmarshalText([]byte(val))\n\t}\n\treturn false, nil\n}\n\nfunc trySplit(vs []string, field reflect.StructField) (newVs []string, err error) {\n\tcfTag := field.Tag.Get(\"collection_format\")\n\tif cfTag == \"\" || cfTag == \"multi\" {\n\t\treturn vs, nil\n\t}\n\n\tvar sep string\n\tswitch cfTag {\n\tcase \"csv\":\n\t\tsep = \",\"\n\tcase \"ssv\":\n\t\tsep = \" \"\n\tcase \"tsv\":\n\t\tsep = \"\\t\"\n\tcase \"pipes\":\n\t\tsep = \"|\"\n\tdefault:\n\t\treturn vs, fmt.Errorf(\"%s is not supported in the collection_format. (multi, csv, ssv, tsv, pipes)\", cfTag)\n\t}\n\n\ttotalLength := 0\n\tfor _, v := range vs {\n\t\ttotalLength += strings.Count(v, sep) + 1\n\t}\n\tnewVs = make([]string, 0, totalLength)\n\tfor _, v := range vs {\n\t\tnewVs = append(newVs, strings.Split(v, sep)...)\n\t}\n\n\treturn newVs, nil\n}\n\nfunc setByForm(value reflect.Value, field reflect.StructField, form map[string][]string, tagValue string, opt setOptions) (isSet bool, err error) {\n\tvs, ok := form[tagValue]\n\tif !ok && !opt.isDefaultExists {\n\t\treturn false, nil\n\t}\n\n\tswitch value.Kind() {\n\tcase reflect.Slice:\n\t\tif len(vs) == 0 {\n\t\t\tif !opt.isDefaultExists {\n\t\t\t\treturn false, nil\n\t\t\t}\n\n\t\t\tvs = []string{opt.defaultValue}\n\t\t\t// pre-process the default value for multi if present\n\t\t\tcfTag := field.Tag.Get(\"collection_format\")\n\t\t\tif cfTag == \"\" || cfTag == \"multi\" {\n\t\t\t\tvs = strings.Split(opt.defaultValue, \",\")\n\t\t\t}\n\t\t}\n\n\t\tif ok, err = trySetUsingParser(vs[0], value, opt.parser); ok {\n\t\t\treturn ok, err\n\t\t} else if ok, err = trySetCustom(vs[0], value); ok {\n\t\t\treturn ok, err\n\t\t}\n\n\t\tif vs, err = trySplit(vs, field); err != nil {\n\t\t\treturn false, err\n\t\t}\n\n\t\treturn true, setSlice(vs, value, field, opt)\n\tcase reflect.Array:\n\t\tif len(vs) == 0 {\n\t\t\tif !opt.isDefaultExists {\n\t\t\t\treturn false, nil\n\t\t\t}\n\n\t\t\tvs = []string{opt.defaultValue}\n\t\t\t// pre-process the default value for multi if present\n\t\t\tcfTag := field.Tag.Get(\"collection_format\")\n\t\t\tif cfTag == \"\" || cfTag == \"multi\" {\n\t\t\t\tvs = strings.Split(opt.defaultValue, \",\")\n\t\t\t}\n\t\t}\n\n\t\tif ok, err = trySetUsingParser(vs[0], value, opt.parser); ok {\n\t\t\treturn ok, err\n\t\t} else if ok, err = trySetCustom(vs[0], value); ok {\n\t\t\treturn ok, err\n\t\t}\n\n\t\tif vs, err = trySplit(vs, field); err != nil {\n\t\t\treturn false, err\n\t\t}\n\n\t\tif len(vs) != value.Len() {\n\t\t\treturn false, fmt.Errorf(\"%q is not valid value for %s\", vs, value.Type().String())\n\t\t}\n\n\t\treturn true, setArray(vs, value, field, opt)\n\tdefault:\n\t\tvar val string\n\t\tif !ok || len(vs) == 0 || (len(vs) > 0 && vs[0] == \"\") {\n\t\t\tval = opt.defaultValue\n\t\t} else if len(vs) > 0 {\n\t\t\tval = vs[0]\n\t\t}\n\n\t\tif ok, err = trySetUsingParser(val, value, opt.parser); ok {\n\t\t\treturn ok, err\n\t\t} else if ok, err = trySetCustom(val, value); ok {\n\t\t\treturn ok, err\n\t\t}\n\t\treturn true, setWithProperType(val, value, field, opt)\n\t}\n}\n\nfunc setWithProperType(val string, value reflect.Value, field reflect.StructField, opt setOptions) error {\n\t// this if-check is required for parsing nested types like []MyId, where MyId is [12]byte\n\tif ok, err := trySetUsingParser(val, value, opt.parser); ok {\n\t\treturn err\n\t} else if ok, err = trySetCustom(val, value); ok {\n\t\treturn err\n\t}\n\n\t// If it is a string type, no spaces are removed, and the user data is not modified here\n\tif value.Kind() != reflect.String {\n\t\tval = strings.TrimSpace(val)\n\t}\n\n\tswitch value.Kind() {\n\tcase reflect.Int:\n\t\treturn setIntField(val, 0, value)\n\tcase reflect.Int8:\n\t\treturn setIntField(val, 8, value)\n\tcase reflect.Int16:\n\t\treturn setIntField(val, 16, value)\n\tcase reflect.Int32:\n\t\treturn setIntField(val, 32, value)\n\tcase reflect.Int64:\n\t\tswitch value.Interface().(type) {\n\t\tcase time.Duration:\n\t\t\treturn setTimeDuration(val, value)\n\t\t}\n\t\treturn setIntField(val, 64, value)\n\tcase reflect.Uint:\n\t\treturn setUintField(val, 0, value)\n\tcase reflect.Uint8:\n\t\treturn setUintField(val, 8, value)\n\tcase reflect.Uint16:\n\t\treturn setUintField(val, 16, value)\n\tcase reflect.Uint32:\n\t\treturn setUintField(val, 32, value)\n\tcase reflect.Uint64:\n\t\treturn setUintField(val, 64, value)\n\tcase reflect.Bool:\n\t\treturn setBoolField(val, value)\n\tcase reflect.Float32:\n\t\treturn setFloatField(val, 32, value)\n\tcase reflect.Float64:\n\t\treturn setFloatField(val, 64, value)\n\tcase reflect.String:\n\t\tvalue.SetString(val)\n\tcase reflect.Struct:\n\t\tswitch value.Interface().(type) {\n\t\tcase time.Time:\n\t\t\treturn setTimeField(val, field, value)\n\t\tcase multipart.FileHeader:\n\t\t\treturn nil\n\t\t}\n\t\treturn json.API.Unmarshal(bytesconv.StringToBytes(val), value.Addr().Interface())\n\tcase reflect.Map:\n\t\treturn json.API.Unmarshal(bytesconv.StringToBytes(val), value.Addr().Interface())\n\tcase reflect.Ptr:\n\t\tif !value.Elem().IsValid() {\n\t\t\tvalue.Set(reflect.New(value.Type().Elem()))\n\t\t}\n\t\treturn setWithProperType(val, value.Elem(), field, opt)\n\tdefault:\n\t\treturn errUnknownType\n\t}\n\treturn nil\n}\n\nfunc setIntField(val string, bitSize int, field reflect.Value) error {\n\tif val == \"\" {\n\t\tval = \"0\"\n\t}\n\tintVal, err := strconv.ParseInt(val, 10, bitSize)\n\tif err == nil {\n\t\tfield.SetInt(intVal)\n\t}\n\treturn err\n}\n\nfunc setUintField(val string, bitSize int, field reflect.Value) error {\n\tif val == \"\" {\n\t\tval = \"0\"\n\t}\n\tuintVal, err := strconv.ParseUint(val, 10, bitSize)\n\tif err == nil {\n\t\tfield.SetUint(uintVal)\n\t}\n\treturn err\n}\n\nfunc setBoolField(val string, field reflect.Value) error {\n\tif val == \"\" {\n\t\tval = \"false\"\n\t}\n\tboolVal, err := strconv.ParseBool(val)\n\tif err == nil {\n\t\tfield.SetBool(boolVal)\n\t}\n\treturn err\n}\n\nfunc setFloatField(val string, bitSize int, field reflect.Value) error {\n\tif val == \"\" {\n\t\tval = \"0.0\"\n\t}\n\tfloatVal, err := strconv.ParseFloat(val, bitSize)\n\tif err == nil {\n\t\tfield.SetFloat(floatVal)\n\t}\n\treturn err\n}\n\nfunc setTimeField(val string, structField reflect.StructField, value reflect.Value) error {\n\ttimeFormat := structField.Tag.Get(\"time_format\")\n\tif timeFormat == \"\" {\n\t\ttimeFormat = time.RFC3339\n\t}\n\n\tif val == \"\" {\n\t\tvalue.Set(reflect.ValueOf(time.Time{}))\n\t\treturn nil\n\t}\n\n\tswitch tf := strings.ToLower(timeFormat); tf {\n\tcase \"unix\", \"unixmilli\", \"unixmicro\", \"unixnano\":\n\t\ttv, err := strconv.ParseInt(val, 10, 64)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tvar t time.Time\n\t\tswitch tf {\n\t\tcase \"unix\":\n\t\t\tt = time.Unix(tv, 0)\n\t\tcase \"unixmilli\":\n\t\t\tt = time.UnixMilli(tv)\n\t\tcase \"unixmicro\":\n\t\t\tt = time.UnixMicro(tv)\n\t\tdefault:\n\t\t\tt = time.Unix(0, tv)\n\t\t}\n\n\t\tvalue.Set(reflect.ValueOf(t))\n\t\treturn nil\n\t}\n\n\tl := time.Local\n\tif isUTC, _ := strconv.ParseBool(structField.Tag.Get(\"time_utc\")); isUTC {\n\t\tl = time.UTC\n\t}\n\n\tif locTag := structField.Tag.Get(\"time_location\"); locTag != \"\" {\n\t\tloc, err := time.LoadLocation(locTag)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tl = loc\n\t}\n\n\tt, err := time.ParseInLocation(timeFormat, val, l)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tvalue.Set(reflect.ValueOf(t))\n\treturn nil\n}\n\nfunc setArray(vals []string, value reflect.Value, field reflect.StructField, opt setOptions) error {\n\tfor i, s := range vals {\n\t\terr := setWithProperType(s, value.Index(i), field, opt)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc setSlice(vals []string, value reflect.Value, field reflect.StructField, opt setOptions) error {\n\tslice := reflect.MakeSlice(value.Type(), len(vals), len(vals))\n\terr := setArray(vals, slice, field, opt)\n\tif err != nil {\n\t\treturn err\n\t}\n\tvalue.Set(slice)\n\treturn nil\n}\n\nfunc setTimeDuration(val string, value reflect.Value) error {\n\tif val == \"\" {\n\t\tval = \"0\"\n\t}\n\n\td, err := time.ParseDuration(val)\n\tif err != nil {\n\t\treturn err\n\t}\n\tvalue.Set(reflect.ValueOf(d))\n\treturn nil\n}\n\nfunc head(str, sep string) (head string, tail string) {\n\thead, tail, _ = strings.Cut(str, sep)\n\treturn head, tail\n}\n\nfunc setFormMap(ptr any, form map[string][]string) error {\n\tel := reflect.TypeOf(ptr).Elem()\n\n\tif el.Kind() == reflect.Slice {\n\t\tptrMap, ok := ptr.(map[string][]string)\n\t\tif !ok {\n\t\t\treturn ErrConvertMapStringSlice\n\t\t}\n\t\tmaps.Copy(ptrMap, form)\n\n\t\treturn nil\n\t}\n\n\tptrMap, ok := ptr.(map[string]string)\n\tif !ok {\n\t\treturn ErrConvertToMapString\n\t}\n\tfor k, v := range form {\n\t\tptrMap[k] = v[len(v)-1] // pick last\n\t}\n\n\treturn nil\n}\n"
  },
  {
    "path": "binding/form_mapping_benchmark_test.go",
    "content": "// Copyright 2019 Gin Core Team. All rights reserved.\n// Use of this source code is governed by a MIT style\n// license that can be found in the LICENSE file.\n\npackage binding\n\nimport (\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/stretchr/testify/assert\"\n)\n\nvar form = map[string][]string{\n\t\"name\":      {\"mike\"},\n\t\"friends\":   {\"anna\", \"nicole\"},\n\t\"id_number\": {\"12345678\"},\n\t\"id_date\":   {\"2018-01-20\"},\n}\n\ntype structFull struct {\n\tName    string   `form:\"name\"`\n\tAge     int      `form:\"age,default=25\"`\n\tFriends []string `form:\"friends\"`\n\tID      *struct {\n\t\tNumber      string    `form:\"id_number\"`\n\t\tDateOfIssue time.Time `form:\"id_date\" time_format:\"2006-01-02\" time_utc:\"true\"`\n\t}\n\tNationality *string `form:\"nationality\"`\n}\n\nfunc BenchmarkMapFormFull(b *testing.B) {\n\tvar s structFull\n\tfor b.Loop() {\n\t\terr := mapForm(&s, form)\n\t\tif err != nil {\n\t\t\tb.Fatalf(\"Error on a form mapping\")\n\t\t}\n\t}\n\tb.StopTimer()\n\n\tt := b\n\tassert.Equal(t, \"mike\", s.Name)\n\tassert.Equal(t, 25, s.Age)\n\tassert.Equal(t, []string{\"anna\", \"nicole\"}, s.Friends)\n\tassert.Equal(t, \"12345678\", s.ID.Number)\n\tassert.Equal(t, time.Date(2018, 1, 20, 0, 0, 0, 0, time.UTC), s.ID.DateOfIssue)\n\tassert.Nil(t, s.Nationality)\n}\n\ntype structName struct {\n\tName string `form:\"name\"`\n}\n\nfunc BenchmarkMapFormName(b *testing.B) {\n\tvar s structName\n\tfor b.Loop() {\n\t\terr := mapForm(&s, form)\n\t\tif err != nil {\n\t\t\tb.Fatalf(\"Error on a form mapping\")\n\t\t}\n\t}\n\tb.StopTimer()\n\n\tt := b\n\tassert.Equal(t, \"mike\", s.Name)\n}\n"
  },
  {
    "path": "binding/form_mapping_test.go",
    "content": "// Copyright 2019 Gin Core Team. All rights reserved.\n// Use of this source code is governed by a MIT style\n// license that can be found in the LICENSE file.\n\npackage binding\n\nimport (\n\t\"encoding\"\n\t\"encoding/hex\"\n\t\"errors\"\n\t\"mime/multipart\"\n\t\"reflect\"\n\t\"strconv\"\n\t\"strings\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestMappingBaseTypes(t *testing.T) {\n\tintPtr := func(i int) *int {\n\t\treturn &i\n\t}\n\tfor _, tt := range []struct {\n\t\tname   string\n\t\tvalue  any\n\t\tform   string\n\t\texpect any\n\t}{\n\t\t{\"base type\", struct{ F int }{}, \"9\", int(9)},\n\t\t{\"base type\", struct{ F int8 }{}, \"9\", int8(9)},\n\t\t{\"base type\", struct{ F int16 }{}, \"9\", int16(9)},\n\t\t{\"base type\", struct{ F int32 }{}, \"9\", int32(9)},\n\t\t{\"base type\", struct{ F int64 }{}, \"9\", int64(9)},\n\t\t{\"base type\", struct{ F uint }{}, \"9\", uint(9)},\n\t\t{\"base type\", struct{ F uint8 }{}, \"9\", uint8(9)},\n\t\t{\"base type\", struct{ F uint16 }{}, \"9\", uint16(9)},\n\t\t{\"base type\", struct{ F uint32 }{}, \"9\", uint32(9)},\n\t\t{\"base type\", struct{ F uint64 }{}, \"9\", uint64(9)},\n\t\t{\"base type\", struct{ F bool }{}, \"True\", true},\n\t\t{\"base type\", struct{ F float32 }{}, \"9.1\", float32(9.1)},\n\t\t{\"base type\", struct{ F float64 }{}, \"9.1\", float64(9.1)},\n\t\t{\"base type\", struct{ F string }{}, \"test\", string(\"test\")},\n\t\t{\"base type\", struct{ F *int }{}, \"9\", intPtr(9)},\n\n\t\t// zero values\n\t\t{\"zero value\", struct{ F int }{}, \"\", int(0)},\n\t\t{\"zero value\", struct{ F uint }{}, \"\", uint(0)},\n\t\t{\"zero value\", struct{ F bool }{}, \"\", false},\n\t\t{\"zero value\", struct{ F float32 }{}, \"\", float32(0)},\n\t\t{\"file value\", struct{ F *multipart.FileHeader }{}, \"\", &multipart.FileHeader{}},\n\t} {\n\t\ttp := reflect.TypeOf(tt.value)\n\t\ttestName := tt.name + \":\" + tp.Field(0).Type.String()\n\n\t\tval := reflect.New(reflect.TypeOf(tt.value))\n\t\tval.Elem().Set(reflect.ValueOf(tt.value))\n\n\t\tfield := val.Elem().Type().Field(0)\n\n\t\t_, err := mapping(val, emptyField, formSource{field.Name: {tt.form}}, \"form\")\n\t\trequire.NoError(t, err, testName)\n\n\t\tactual := val.Elem().Field(0).Interface()\n\t\tassert.Equal(t, tt.expect, actual, testName)\n\t}\n}\n\nfunc TestMappingDefault(t *testing.T) {\n\tvar s struct {\n\t\tStr   string `form:\",default=defaultVal\"`\n\t\tInt   int    `form:\",default=9\"`\n\t\tSlice []int  `form:\",default=9\"`\n\t\tArray [1]int `form:\",default=9\"`\n\t}\n\terr := mappingByPtr(&s, formSource{}, \"form\")\n\trequire.NoError(t, err)\n\n\tassert.Equal(t, \"defaultVal\", s.Str)\n\tassert.Equal(t, 9, s.Int)\n\tassert.Equal(t, []int{9}, s.Slice)\n\tassert.Equal(t, [1]int{9}, s.Array)\n}\n\nfunc TestMappingSkipField(t *testing.T) {\n\tvar s struct {\n\t\tA int\n\t}\n\terr := mappingByPtr(&s, formSource{}, \"form\")\n\trequire.NoError(t, err)\n\n\tassert.Equal(t, 0, s.A)\n}\n\nfunc TestMappingIgnoreField(t *testing.T) {\n\tvar s struct {\n\t\tA int `form:\"A\"`\n\t\tB int `form:\"-\"`\n\t}\n\terr := mappingByPtr(&s, formSource{\"A\": {\"9\"}, \"B\": {\"9\"}}, \"form\")\n\trequire.NoError(t, err)\n\n\tassert.Equal(t, 9, s.A)\n\tassert.Equal(t, 0, s.B)\n}\n\nfunc TestMappingUnexportedField(t *testing.T) {\n\tvar s struct {\n\t\tA int `form:\"a\"`\n\t\tb int `form:\"b\"`\n\t}\n\terr := mappingByPtr(&s, formSource{\"a\": {\"9\"}, \"b\": {\"9\"}}, \"form\")\n\trequire.NoError(t, err)\n\n\tassert.Equal(t, 9, s.A)\n\tassert.Equal(t, 0, s.b)\n}\n\nfunc TestMappingPrivateField(t *testing.T) {\n\tvar s struct {\n\t\tf int `form:\"field\"`\n\t}\n\terr := mappingByPtr(&s, formSource{\"field\": {\"6\"}}, \"form\")\n\trequire.NoError(t, err)\n\tassert.Equal(t, 0, s.f)\n}\n\nfunc TestMappingUnknownFieldType(t *testing.T) {\n\tvar s struct {\n\t\tU uintptr\n\t}\n\n\terr := mappingByPtr(&s, formSource{\"U\": {\"unknown\"}}, \"form\")\n\trequire.Error(t, err)\n\tassert.Equal(t, errUnknownType, err)\n}\n\nfunc TestMappingURI(t *testing.T) {\n\tvar s struct {\n\t\tF int `uri:\"field\"`\n\t}\n\terr := mapURI(&s, map[string][]string{\"field\": {\"6\"}})\n\trequire.NoError(t, err)\n\tassert.Equal(t, 6, s.F)\n}\n\nfunc TestMappingForm(t *testing.T) {\n\tvar s struct {\n\t\tF int `form:\"field\"`\n\t}\n\terr := mapForm(&s, map[string][]string{\"field\": {\"6\"}})\n\trequire.NoError(t, err)\n\tassert.Equal(t, 6, s.F)\n}\n\nfunc TestMappingFormFieldNotSent(t *testing.T) {\n\tvar s struct {\n\t\tF string `form:\"field,default=defVal\"`\n\t}\n\terr := mapForm(&s, map[string][]string{})\n\trequire.NoError(t, err)\n\tassert.Equal(t, \"defVal\", s.F)\n}\n\nfunc TestMappingFormWithEmptyToDefault(t *testing.T) {\n\tvar s struct {\n\t\tF string `form:\"field,default=DefVal\"`\n\t}\n\terr := mapForm(&s, map[string][]string{\"field\": {\"\"}})\n\trequire.NoError(t, err)\n\tassert.Equal(t, \"DefVal\", s.F)\n}\n\nfunc TestMapFormWithTag(t *testing.T) {\n\tvar s struct {\n\t\tF int `externalTag:\"field\"`\n\t}\n\terr := MapFormWithTag(&s, map[string][]string{\"field\": {\"6\"}}, \"externalTag\")\n\trequire.NoError(t, err)\n\tassert.Equal(t, 6, s.F)\n}\n\nfunc TestMappingTime(t *testing.T) {\n\tvar s struct {\n\t\tTime      time.Time\n\t\tLocalTime time.Time `time_format:\"2006-01-02\"`\n\t\tZeroValue time.Time\n\t\tCSTTime   time.Time `time_format:\"2006-01-02\" time_location:\"Asia/Shanghai\"`\n\t\tUTCTime   time.Time `time_format:\"2006-01-02\" time_utc:\"1\"`\n\t}\n\n\tvar err error\n\ttime.Local, err = time.LoadLocation(\"Europe/Berlin\")\n\trequire.NoError(t, err)\n\n\terr = mapForm(&s, map[string][]string{\n\t\t\"Time\":      {\"2019-01-20T16:02:58Z\"},\n\t\t\"LocalTime\": {\"2019-01-20\"},\n\t\t\"ZeroValue\": {},\n\t\t\"CSTTime\":   {\"2019-01-20\"},\n\t\t\"UTCTime\":   {\"2019-01-20\"},\n\t})\n\trequire.NoError(t, err)\n\n\tassert.Equal(t, \"2019-01-20 16:02:58 +0000 UTC\", s.Time.String())\n\tassert.Equal(t, \"2019-01-20 00:00:00 +0100 CET\", s.LocalTime.String())\n\tassert.Equal(t, \"2019-01-19 23:00:00 +0000 UTC\", s.LocalTime.UTC().String())\n\tassert.Equal(t, \"0001-01-01 00:00:00 +0000 UTC\", s.ZeroValue.String())\n\tassert.Equal(t, \"2019-01-20 00:00:00 +0800 CST\", s.CSTTime.String())\n\tassert.Equal(t, \"2019-01-19 16:00:00 +0000 UTC\", s.CSTTime.UTC().String())\n\tassert.Equal(t, \"2019-01-20 00:00:00 +0000 UTC\", s.UTCTime.String())\n\n\t// wrong location\n\tvar wrongLoc struct {\n\t\tTime time.Time `time_location:\"wrong\"`\n\t}\n\terr = mapForm(&wrongLoc, map[string][]string{\"Time\": {\"2019-01-20T16:02:58Z\"}})\n\trequire.Error(t, err)\n\n\t// wrong time value\n\tvar wrongTime struct {\n\t\tTime time.Time\n\t}\n\terr = mapForm(&wrongTime, map[string][]string{\"Time\": {\"wrong\"}})\n\trequire.Error(t, err)\n}\n\ntype bindTestData struct {\n\tneed any\n\tgot  any\n\tin   map[string][]string\n}\n\nfunc TestMappingTimeUnixNano(t *testing.T) {\n\ttype needFixUnixNanoEmpty struct {\n\t\tCreateTime time.Time `form:\"createTime\" time_format:\"unixNano\"`\n\t}\n\n\t// ok\n\ttests := []bindTestData{\n\t\t{need: &needFixUnixNanoEmpty{}, got: &needFixUnixNanoEmpty{}, in: formSource{\"createTime\": []string{\"   \"}}},\n\t\t{need: &needFixUnixNanoEmpty{}, got: &needFixUnixNanoEmpty{}, in: formSource{\"createTime\": []string{}}},\n\t}\n\n\tfor _, v := range tests {\n\t\terr := mapForm(v.got, v.in)\n\t\trequire.NoError(t, err)\n\t\tassert.Equal(t, v.need, v.got)\n\t}\n}\n\nfunc TestMappingTimeDuration(t *testing.T) {\n\ttype needFixDurationEmpty struct {\n\t\tDuration time.Duration `form:\"duration\"`\n\t}\n\n\tvar s struct {\n\t\tD time.Duration\n\t}\n\n\t// ok\n\terr := mappingByPtr(&s, formSource{\"D\": {\"5s\"}}, \"form\")\n\trequire.NoError(t, err)\n\tassert.Equal(t, 5*time.Second, s.D)\n\n\t// ok\n\ttests := []bindTestData{\n\t\t{need: &needFixDurationEmpty{}, got: &needFixDurationEmpty{}, in: formSource{\"duration\": []string{\"   \"}}},\n\t\t{need: &needFixDurationEmpty{}, got: &needFixDurationEmpty{}, in: formSource{\"duration\": []string{}}},\n\t}\n\n\tfor _, v := range tests {\n\t\terr := mapForm(v.got, v.in)\n\t\trequire.NoError(t, err)\n\t\tassert.Equal(t, v.need, v.got)\n\t}\n\t// error\n\terr = mappingByPtr(&s, formSource{\"D\": {\"wrong\"}}, \"form\")\n\trequire.Error(t, err)\n}\n\nfunc TestMappingSlice(t *testing.T) {\n\tvar s struct {\n\t\tSlice []int `form:\"slice,default=9\"`\n\t}\n\n\t// default value\n\terr := mappingByPtr(&s, formSource{}, \"form\")\n\trequire.NoError(t, err)\n\tassert.Equal(t, []int{9}, s.Slice)\n\n\t// ok\n\terr = mappingByPtr(&s, formSource{\"slice\": {\"3\", \"4\"}}, \"form\")\n\trequire.NoError(t, err)\n\tassert.Equal(t, []int{3, 4}, s.Slice)\n\n\t// error\n\terr = mappingByPtr(&s, formSource{\"slice\": {\"wrong\"}}, \"form\")\n\trequire.Error(t, err)\n}\n\nfunc TestMappingArray(t *testing.T) {\n\tvar s struct {\n\t\tArray [2]int `form:\"array,default=9\"`\n\t}\n\n\t// wrong default\n\terr := mappingByPtr(&s, formSource{}, \"form\")\n\trequire.Error(t, err)\n\n\t// ok\n\terr = mappingByPtr(&s, formSource{\"array\": {\"3\", \"4\"}}, \"form\")\n\trequire.NoError(t, err)\n\tassert.Equal(t, [2]int{3, 4}, s.Array)\n\n\t// error - not enough vals\n\terr = mappingByPtr(&s, formSource{\"array\": {\"3\"}}, \"form\")\n\trequire.Error(t, err)\n\n\t// error - wrong value\n\terr = mappingByPtr(&s, formSource{\"array\": {\"wrong\"}}, \"form\")\n\trequire.Error(t, err)\n}\n\nfunc TestMappingCollectionFormat(t *testing.T) {\n\tvar s struct {\n\t\tSliceMulti []int  `form:\"slice_multi\" collection_format:\"multi\"`\n\t\tSliceCsv   []int  `form:\"slice_csv\" collection_format:\"csv\"`\n\t\tSliceSsv   []int  `form:\"slice_ssv\" collection_format:\"ssv\"`\n\t\tSliceTsv   []int  `form:\"slice_tsv\" collection_format:\"tsv\"`\n\t\tSlicePipes []int  `form:\"slice_pipes\" collection_format:\"pipes\"`\n\t\tArrayMulti [2]int `form:\"array_multi\" collection_format:\"multi\"`\n\t\tArrayCsv   [2]int `form:\"array_csv\" collection_format:\"csv\"`\n\t\tArraySsv   [2]int `form:\"array_ssv\" collection_format:\"ssv\"`\n\t\tArrayTsv   [2]int `form:\"array_tsv\" collection_format:\"tsv\"`\n\t\tArrayPipes [2]int `form:\"array_pipes\" collection_format:\"pipes\"`\n\t}\n\terr := mappingByPtr(&s, formSource{\n\t\t\"slice_multi\": {\"1\", \"2\"},\n\t\t\"slice_csv\":   {\"1,2\"},\n\t\t\"slice_ssv\":   {\"1 2\"},\n\t\t\"slice_tsv\":   {\"1\t2\"},\n\t\t\"slice_pipes\": {\"1|2\"},\n\t\t\"array_multi\": {\"1\", \"2\"},\n\t\t\"array_csv\":   {\"1,2\"},\n\t\t\"array_ssv\":   {\"1 2\"},\n\t\t\"array_tsv\":   {\"1\t2\"},\n\t\t\"array_pipes\": {\"1|2\"},\n\t}, \"form\")\n\trequire.NoError(t, err)\n\n\tassert.Equal(t, []int{1, 2}, s.SliceMulti)\n\tassert.Equal(t, []int{1, 2}, s.SliceCsv)\n\tassert.Equal(t, []int{1, 2}, s.SliceSsv)\n\tassert.Equal(t, []int{1, 2}, s.SliceTsv)\n\tassert.Equal(t, []int{1, 2}, s.SlicePipes)\n\tassert.Equal(t, [2]int{1, 2}, s.ArrayMulti)\n\tassert.Equal(t, [2]int{1, 2}, s.ArrayCsv)\n\tassert.Equal(t, [2]int{1, 2}, s.ArraySsv)\n\tassert.Equal(t, [2]int{1, 2}, s.ArrayTsv)\n\tassert.Equal(t, [2]int{1, 2}, s.ArrayPipes)\n}\n\nfunc TestMappingCollectionFormatInvalid(t *testing.T) {\n\tvar s struct {\n\t\tSliceCsv []int `form:\"slice_csv\" collection_format:\"xxx\"`\n\t}\n\terr := mappingByPtr(&s, formSource{\n\t\t\"slice_csv\": {\"1,2\"},\n\t}, \"form\")\n\trequire.Error(t, err)\n\n\tvar s2 struct {\n\t\tArrayCsv [2]int `form:\"array_csv\" collection_format:\"xxx\"`\n\t}\n\terr = mappingByPtr(&s2, formSource{\n\t\t\"array_csv\": {\"1,2\"},\n\t}, \"form\")\n\trequire.Error(t, err)\n}\n\nfunc TestMappingMultipleDefaultWithCollectionFormat(t *testing.T) {\n\tvar s struct {\n\t\tSliceMulti       []int     `form:\",default=1;2;3\" collection_format:\"multi\"`\n\t\tSliceCsv         []int     `form:\",default=1;2;3\" collection_format:\"csv\"`\n\t\tSliceSsv         []int     `form:\",default=1 2 3\" collection_format:\"ssv\"`\n\t\tSliceTsv         []int     `form:\",default=1\\t2\\t3\" collection_format:\"tsv\"`\n\t\tSlicePipes       []int     `form:\",default=1|2|3\" collection_format:\"pipes\"`\n\t\tArrayMulti       [2]int    `form:\",default=1;2\" collection_format:\"multi\"`\n\t\tArrayCsv         [2]int    `form:\",default=1;2\" collection_format:\"csv\"`\n\t\tArraySsv         [2]int    `form:\",default=1 2\" collection_format:\"ssv\"`\n\t\tArrayTsv         [2]int    `form:\",default=1\\t2\" collection_format:\"tsv\"`\n\t\tArrayPipes       [2]int    `form:\",default=1|2\" collection_format:\"pipes\"`\n\t\tSliceStringMulti []string  `form:\",default=1;2;3\" collection_format:\"multi\"`\n\t\tSliceStringCsv   []string  `form:\",default=1;2;3\" collection_format:\"csv\"`\n\t\tSliceStringSsv   []string  `form:\",default=1 2 3\" collection_format:\"ssv\"`\n\t\tSliceStringTsv   []string  `form:\",default=1\\t2\\t3\" collection_format:\"tsv\"`\n\t\tSliceStringPipes []string  `form:\",default=1|2|3\" collection_format:\"pipes\"`\n\t\tArrayStringMulti [2]string `form:\",default=1;2\" collection_format:\"multi\"`\n\t\tArrayStringCsv   [2]string `form:\",default=1;2\" collection_format:\"csv\"`\n\t\tArrayStringSsv   [2]string `form:\",default=1 2\" collection_format:\"ssv\"`\n\t\tArrayStringTsv   [2]string `form:\",default=1\\t2\" collection_format:\"tsv\"`\n\t\tArrayStringPipes [2]string `form:\",default=1|2\" collection_format:\"pipes\"`\n\t}\n\terr := mappingByPtr(&s, formSource{}, \"form\")\n\trequire.NoError(t, err)\n\n\tassert.Equal(t, []int{1, 2, 3}, s.SliceMulti)\n\tassert.Equal(t, []int{1, 2, 3}, s.SliceCsv)\n\tassert.Equal(t, []int{1, 2, 3}, s.SliceSsv)\n\tassert.Equal(t, []int{1, 2, 3}, s.SliceTsv)\n\tassert.Equal(t, []int{1, 2, 3}, s.SlicePipes)\n\tassert.Equal(t, [2]int{1, 2}, s.ArrayMulti)\n\tassert.Equal(t, [2]int{1, 2}, s.ArrayCsv)\n\tassert.Equal(t, [2]int{1, 2}, s.ArraySsv)\n\tassert.Equal(t, [2]int{1, 2}, s.ArrayTsv)\n\tassert.Equal(t, [2]int{1, 2}, s.ArrayPipes)\n\tassert.Equal(t, []string{\"1\", \"2\", \"3\"}, s.SliceStringMulti)\n\tassert.Equal(t, []string{\"1\", \"2\", \"3\"}, s.SliceStringCsv)\n\tassert.Equal(t, []string{\"1\", \"2\", \"3\"}, s.SliceStringSsv)\n\tassert.Equal(t, []string{\"1\", \"2\", \"3\"}, s.SliceStringTsv)\n\tassert.Equal(t, []string{\"1\", \"2\", \"3\"}, s.SliceStringPipes)\n\tassert.Equal(t, [2]string{\"1\", \"2\"}, s.ArrayStringMulti)\n\tassert.Equal(t, [2]string{\"1\", \"2\"}, s.ArrayStringCsv)\n\tassert.Equal(t, [2]string{\"1\", \"2\"}, s.ArrayStringSsv)\n\tassert.Equal(t, [2]string{\"1\", \"2\"}, s.ArrayStringTsv)\n\tassert.Equal(t, [2]string{\"1\", \"2\"}, s.ArrayStringPipes)\n}\n\nfunc TestMappingStructField(t *testing.T) {\n\tvar s struct {\n\t\tJ struct {\n\t\t\tI int\n\t\t}\n\t}\n\n\terr := mappingByPtr(&s, formSource{\"J\": {`{\"I\": 9}`}}, \"form\")\n\trequire.NoError(t, err)\n\tassert.Equal(t, 9, s.J.I)\n}\n\nfunc TestMappingPtrField(t *testing.T) {\n\ttype ptrStruct struct {\n\t\tKey int64 `json:\"key\"`\n\t}\n\n\ttype ptrRequest struct {\n\t\tItems []*ptrStruct `json:\"items\" form:\"items\"`\n\t}\n\n\tvar err error\n\n\t// With 0 items.\n\tvar req0 ptrRequest\n\terr = mappingByPtr(&req0, formSource{}, \"form\")\n\trequire.NoError(t, err)\n\tassert.Empty(t, req0.Items)\n\n\t// With 1 item.\n\tvar req1 ptrRequest\n\terr = mappingByPtr(&req1, formSource{\"items\": {`{\"key\": 1}`}}, \"form\")\n\trequire.NoError(t, err)\n\tassert.Len(t, req1.Items, 1)\n\tassert.EqualValues(t, 1, req1.Items[0].Key)\n\n\t// With 2 items.\n\tvar req2 ptrRequest\n\terr = mappingByPtr(&req2, formSource{\"items\": {`{\"key\": 1}`, `{\"key\": 2}`}}, \"form\")\n\trequire.NoError(t, err)\n\tassert.Len(t, req2.Items, 2)\n\tassert.EqualValues(t, 1, req2.Items[0].Key)\n\tassert.EqualValues(t, 2, req2.Items[1].Key)\n}\n\nfunc TestMappingMapField(t *testing.T) {\n\tvar s struct {\n\t\tM map[string]int\n\t}\n\n\terr := mappingByPtr(&s, formSource{\"M\": {`{\"one\": 1}`}}, \"form\")\n\trequire.NoError(t, err)\n\tassert.Equal(t, map[string]int{\"one\": 1}, s.M)\n}\n\nfunc TestMappingIgnoredCircularRef(t *testing.T) {\n\ttype S struct {\n\t\tS *S `form:\"-\"`\n\t}\n\tvar s S\n\n\terr := mappingByPtr(&s, formSource{}, \"form\")\n\trequire.NoError(t, err)\n}\n\ntype customUnmarshalParamHex int\n\nfunc (f *customUnmarshalParamHex) UnmarshalParam(param string) error {\n\tv, err := strconv.ParseInt(param, 16, 64)\n\tif err != nil {\n\t\treturn err\n\t}\n\t*f = customUnmarshalParamHex(v)\n\treturn nil\n}\n\nfunc TestMappingCustomUnmarshalParamHexWithFormTag(t *testing.T) {\n\tvar s struct {\n\t\tFoo customUnmarshalParamHex `form:\"foo\"`\n\t}\n\terr := mappingByPtr(&s, formSource{\"foo\": {`f5`}}, \"form\")\n\trequire.NoError(t, err)\n\n\tassert.EqualValues(t, 245, s.Foo)\n}\n\nfunc TestMappingCustomUnmarshalParamHexWithURITag(t *testing.T) {\n\tvar s struct {\n\t\tFoo customUnmarshalParamHex `uri:\"foo\"`\n\t}\n\terr := mappingByPtr(&s, formSource{\"foo\": {`f5`}}, \"uri\")\n\trequire.NoError(t, err)\n\n\tassert.EqualValues(t, 245, s.Foo)\n}\n\nfunc TestMappingCustomUnmarshalParamHexDefault(t *testing.T) {\n\tvar s struct {\n\t\tFoo customUnmarshalParamHex `form:\"foo,default=f5\"`\n\t}\n\terr := mappingByPtr(&s, formSource{\"foo\": {}}, \"form\")\n\trequire.NoError(t, err)\n\n\tassert.EqualValues(t, 0xf5, s.Foo)\n}\n\ntype customUnmarshalParamType struct {\n\tProtocol string\n\tPath     string\n\tName     string\n}\n\nfunc (f *customUnmarshalParamType) UnmarshalParam(param string) error {\n\tparts := strings.Split(param, \":\")\n\tif len(parts) != 3 {\n\t\treturn errors.New(\"invalid format\")\n\t}\n\tf.Protocol = parts[0]\n\tf.Path = parts[1]\n\tf.Name = parts[2]\n\treturn nil\n}\n\nfunc TestMappingCustomStructTypeWithFormTag(t *testing.T) {\n\tvar s struct {\n\t\tFileData customUnmarshalParamType `form:\"data\"`\n\t}\n\terr := mappingByPtr(&s, formSource{\"data\": {`file:/foo:happiness`}}, \"form\")\n\trequire.NoError(t, err)\n\n\tassert.Equal(t, \"file\", s.FileData.Protocol)\n\tassert.Equal(t, \"/foo\", s.FileData.Path)\n\tassert.Equal(t, \"happiness\", s.FileData.Name)\n}\n\nfunc TestMappingCustomStructTypeWithURITag(t *testing.T) {\n\tvar s struct {\n\t\tFileData customUnmarshalParamType `uri:\"data\"`\n\t}\n\terr := mappingByPtr(&s, formSource{\"data\": {`file:/foo:happiness`}}, \"uri\")\n\trequire.NoError(t, err)\n\n\tassert.Equal(t, \"file\", s.FileData.Protocol)\n\tassert.Equal(t, \"/foo\", s.FileData.Path)\n\tassert.Equal(t, \"happiness\", s.FileData.Name)\n}\n\nfunc TestMappingCustomPointerStructTypeWithFormTag(t *testing.T) {\n\tvar s struct {\n\t\tFileData *customUnmarshalParamType `form:\"data\"`\n\t}\n\terr := mappingByPtr(&s, formSource{\"data\": {`file:/foo:happiness`}}, \"form\")\n\trequire.NoError(t, err)\n\n\tassert.Equal(t, \"file\", s.FileData.Protocol)\n\tassert.Equal(t, \"/foo\", s.FileData.Path)\n\tassert.Equal(t, \"happiness\", s.FileData.Name)\n}\n\nfunc TestMappingCustomPointerStructTypeWithURITag(t *testing.T) {\n\tvar s struct {\n\t\tFileData *customUnmarshalParamType `uri:\"data\"`\n\t}\n\terr := mappingByPtr(&s, formSource{\"data\": {`file:/foo:happiness`}}, \"uri\")\n\trequire.NoError(t, err)\n\n\tassert.Equal(t, \"file\", s.FileData.Protocol)\n\tassert.Equal(t, \"/foo\", s.FileData.Path)\n\tassert.Equal(t, \"happiness\", s.FileData.Name)\n}\n\ntype customPath []string\n\nfunc (p *customPath) UnmarshalParam(param string) error {\n\telems := strings.Split(param, \"/\")\n\tn := len(elems)\n\tif n < 2 {\n\t\treturn errors.New(\"invalid format\")\n\t}\n\n\t*p = elems\n\treturn nil\n}\n\nfunc TestMappingCustomSliceUri(t *testing.T) {\n\tvar s struct {\n\t\tFileData customPath `uri:\"path\"`\n\t}\n\terr := mappingByPtr(&s, formSource{\"path\": {`bar/foo`}}, \"uri\")\n\trequire.NoError(t, err)\n\n\tassert.Equal(t, \"bar\", s.FileData[0])\n\tassert.Equal(t, \"foo\", s.FileData[1])\n}\n\nfunc TestMappingCustomSliceForm(t *testing.T) {\n\tvar s struct {\n\t\tFileData customPath `form:\"path\"`\n\t}\n\terr := mappingByPtr(&s, formSource{\"path\": {`bar/foo`}}, \"form\")\n\trequire.NoError(t, err)\n\n\tassert.Equal(t, \"bar\", s.FileData[0])\n\tassert.Equal(t, \"foo\", s.FileData[1])\n}\n\nfunc TestMappingCustomSliceStopsWhenError(t *testing.T) {\n\tvar s struct {\n\t\tFileData customPath `form:\"path\"`\n\t}\n\terr := mappingByPtr(&s, formSource{\"path\": {\"invalid\"}}, \"form\")\n\trequire.ErrorContains(t, err, \"invalid format\")\n\trequire.Empty(t, s.FileData)\n}\n\nfunc TestMappingCustomSliceOfSliceUri(t *testing.T) {\n\tvar s struct {\n\t\tFileData []customPath `uri:\"path\" collection_format:\"csv\"`\n\t}\n\terr := mappingByPtr(&s, formSource{\"path\": {\"bar/foo,bar/foo/spam\"}}, \"uri\")\n\trequire.NoError(t, err)\n\tassert.Equal(t, []customPath{{\"bar\", \"foo\"}, {\"bar\", \"foo\", \"spam\"}}, s.FileData)\n}\n\nfunc TestMappingCustomSliceOfSliceForm(t *testing.T) {\n\tvar s struct {\n\t\tFileData []customPath `form:\"path\" collection_format:\"csv\"`\n\t}\n\terr := mappingByPtr(&s, formSource{\"path\": {\"bar/foo,bar/foo/spam\"}}, \"form\")\n\trequire.NoError(t, err)\n\tassert.Equal(t, []customPath{{\"bar\", \"foo\"}, {\"bar\", \"foo\", \"spam\"}}, s.FileData)\n}\n\ntype objectID [12]byte\n\nfunc (o *objectID) UnmarshalParam(param string) error {\n\toid, err := convertTo(param)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\t*o = oid\n\treturn nil\n}\n\nfunc convertTo(s string) (objectID, error) {\n\tvar nilObjectID objectID\n\tif len(s) != 24 {\n\t\treturn nilObjectID, errors.New(\"invalid format\")\n\t}\n\n\tvar oid [12]byte\n\t_, err := hex.Decode(oid[:], []byte(s))\n\tif err != nil {\n\t\treturn nilObjectID, err\n\t}\n\n\treturn oid, nil\n}\n\nfunc TestMappingCustomArrayUri(t *testing.T) {\n\tvar s struct {\n\t\tFileData objectID `uri:\"id\"`\n\t}\n\tval := `664a062ac74a8ad104e0e80f`\n\terr := mappingByPtr(&s, formSource{\"id\": {val}}, \"uri\")\n\trequire.NoError(t, err)\n\n\texpected, _ := convertTo(val)\n\tassert.Equal(t, expected, s.FileData)\n}\n\nfunc TestMappingCustomArrayForm(t *testing.T) {\n\tvar s struct {\n\t\tFileData objectID `form:\"id\"`\n\t}\n\tval := `664a062ac74a8ad104e0e80f`\n\terr := mappingByPtr(&s, formSource{\"id\": {val}}, \"form\")\n\trequire.NoError(t, err)\n\n\texpected, _ := convertTo(val)\n\tassert.Equal(t, expected, s.FileData)\n}\n\nfunc TestMappingCustomArrayOfArrayUri(t *testing.T) {\n\tid1, _ := convertTo(`664a062ac74a8ad104e0e80e`)\n\tid2, _ := convertTo(`664a062ac74a8ad104e0e80f`)\n\n\tvar s struct {\n\t\tFileData []objectID `uri:\"ids\" collection_format:\"csv\"`\n\t}\n\terr := mappingByPtr(&s, formSource{\"ids\": {`664a062ac74a8ad104e0e80e,664a062ac74a8ad104e0e80f`}}, \"uri\")\n\trequire.NoError(t, err)\n\tassert.Equal(t, []objectID{id1, id2}, s.FileData)\n}\n\nfunc TestMappingCustomArrayOfArrayForm(t *testing.T) {\n\tid1, _ := convertTo(`664a062ac74a8ad104e0e80e`)\n\tid2, _ := convertTo(`664a062ac74a8ad104e0e80f`)\n\n\tvar s struct {\n\t\tFileData []objectID `form:\"ids\" collection_format:\"csv\"`\n\t}\n\terr := mappingByPtr(&s, formSource{\"ids\": {`664a062ac74a8ad104e0e80e,664a062ac74a8ad104e0e80f`}}, \"form\")\n\trequire.NoError(t, err)\n\tassert.Equal(t, []objectID{id1, id2}, s.FileData)\n}\n\n// ====  TextUnmarshaler tests START ====\n\ntype customUnmarshalTextHex int\n\nfunc (f *customUnmarshalTextHex) UnmarshalText(text []byte) error {\n\tv, err := strconv.ParseInt(string(text), 16, 64)\n\tif err != nil {\n\t\treturn err\n\t}\n\t*f = customUnmarshalTextHex(v)\n\treturn nil\n}\n\n// verify type implements TextUnmarshaler\nvar _ encoding.TextUnmarshaler = (*customUnmarshalTextHex)(nil)\n\nfunc TestMappingCustomUnmarshalTextHexUri(t *testing.T) {\n\tvar s struct {\n\t\tField customUnmarshalTextHex `uri:\"field,parser=encoding.TextUnmarshaler\"`\n\t}\n\terr := mappingByPtr(&s, formSource{\"field\": {`f5`}}, \"uri\")\n\trequire.NoError(t, err)\n\tassert.EqualValues(t, 245, s.Field)\n}\n\nfunc TestMappingCustomUnmarshalTextHexForm(t *testing.T) {\n\tvar s struct {\n\t\tField customUnmarshalTextHex `form:\"field,parser=encoding.TextUnmarshaler\"`\n\t}\n\terr := mappingByPtr(&s, formSource{\"field\": {`f5`}}, \"form\")\n\trequire.NoError(t, err)\n\tassert.EqualValues(t, 245, s.Field)\n}\n\nfunc TestMappingCustomUnmarshalTextHexDefault(t *testing.T) {\n\tvar s struct {\n\t\tField customUnmarshalTextHex `form:\"field,default=f5,parser=encoding.TextUnmarshaler\"`\n\t}\n\terr := mappingByPtr(&s, formSource{\"field1\": {}}, \"form\")\n\trequire.NoError(t, err)\n\tassert.EqualValues(t, 0xf5, s.Field)\n}\n\ntype customUnmarshalTextType struct {\n\tProtocol string\n\tPath     string\n\tName     string\n}\n\nfunc (f *customUnmarshalTextType) UnmarshalText(text []byte) error {\n\tparts := strings.Split(string(text), \":\")\n\tif len(parts) != 3 {\n\t\treturn errors.New(\"invalid format\")\n\t}\n\tf.Protocol = parts[0]\n\tf.Path = parts[1]\n\tf.Name = parts[2]\n\treturn nil\n}\n\nvar _ encoding.TextUnmarshaler = (*customUnmarshalTextType)(nil)\n\nfunc TestMappingCustomStructTypeUnmarshalTextForm(t *testing.T) {\n\tvar s struct {\n\t\tFileData customUnmarshalTextType `form:\"data,parser=encoding.TextUnmarshaler\"`\n\t}\n\terr := mappingByPtr(&s, formSource{\"data\": {`file:/foo:happiness`}}, \"form\")\n\trequire.NoError(t, err)\n\n\tassert.Equal(t, \"file\", s.FileData.Protocol)\n\tassert.Equal(t, \"/foo\", s.FileData.Path)\n\tassert.Equal(t, \"happiness\", s.FileData.Name)\n}\n\nfunc TestMappingCustomStructTypeUnmarshalTextUri(t *testing.T) {\n\tvar s struct {\n\t\tFileData customUnmarshalTextType `uri:\"data,parser=encoding.TextUnmarshaler\"`\n\t}\n\terr := mappingByPtr(&s, formSource{\"data\": {`file:/foo:happiness`}}, \"uri\")\n\trequire.NoError(t, err)\n\n\tassert.Equal(t, \"file\", s.FileData.Protocol)\n\tassert.Equal(t, \"/foo\", s.FileData.Path)\n\tassert.Equal(t, \"happiness\", s.FileData.Name)\n}\n\nfunc TestMappingCustomPointerStructTypeUnmarshalTextForm(t *testing.T) {\n\tvar s struct {\n\t\tFileData *customUnmarshalTextType `form:\"data,parser=encoding.TextUnmarshaler\"`\n\t}\n\terr := mappingByPtr(&s, formSource{\"data\": {`file:/foo:happiness`}}, \"form\")\n\trequire.NoError(t, err)\n\n\tassert.Equal(t, \"file\", s.FileData.Protocol)\n\tassert.Equal(t, \"/foo\", s.FileData.Path)\n\tassert.Equal(t, \"happiness\", s.FileData.Name)\n}\n\nfunc TestMappingCustomPointerStructTypeUnmarshalTextUri(t *testing.T) {\n\tvar s struct {\n\t\tFileData *customUnmarshalTextType `uri:\"data,parser=encoding.TextUnmarshaler\"`\n\t}\n\terr := mappingByPtr(&s, formSource{\"data\": {`file:/foo:happiness`}}, \"uri\")\n\trequire.NoError(t, err)\n\n\tassert.Equal(t, \"file\", s.FileData.Protocol)\n\tassert.Equal(t, \"/foo\", s.FileData.Path)\n\tassert.Equal(t, \"happiness\", s.FileData.Name)\n}\n\ntype customPathUnmarshalText []string\n\nfunc (p *customPathUnmarshalText) UnmarshalText(text []byte) error {\n\telems := strings.Split(string(text), \"/\")\n\tn := len(elems)\n\tif n < 2 {\n\t\treturn errors.New(\"invalid format\")\n\t}\n\n\t*p = elems\n\treturn nil\n}\n\nvar _ encoding.TextUnmarshaler = (*customPathUnmarshalText)(nil)\n\nfunc TestMappingCustomSliceUnmarshalTextUri(t *testing.T) {\n\tvar s struct {\n\t\tFileData customPathUnmarshalText `uri:\"path,parser=encoding.TextUnmarshaler\"`\n\t}\n\terr := mappingByPtr(&s, formSource{\"path\": {`bar/foo`}}, \"uri\")\n\trequire.NoError(t, err)\n\n\tassert.Equal(t, \"bar\", s.FileData[0])\n\tassert.Equal(t, \"foo\", s.FileData[1])\n}\n\nfunc TestMappingCustomSliceUnmarshalTextForm(t *testing.T) {\n\tvar s struct {\n\t\tFileData customPathUnmarshalText `form:\"path,parser=encoding.TextUnmarshaler\"`\n\t}\n\terr := mappingByPtr(&s, formSource{\"path\": {`bar/foo`}}, \"form\")\n\trequire.NoError(t, err)\n\n\tassert.Equal(t, \"bar\", s.FileData[0])\n\tassert.Equal(t, \"foo\", s.FileData[1])\n}\n\nfunc TestMappingCustomSliceUnmarshalTextStopsWhenError(t *testing.T) {\n\tvar s struct {\n\t\tFileData customPathUnmarshalText `form:\"path,parser=encoding.TextUnmarshaler\"`\n\t}\n\terr := mappingByPtr(&s, formSource{\"path\": {\"invalid\"}}, \"form\")\n\trequire.ErrorContains(t, err, \"invalid format\")\n\trequire.Empty(t, s.FileData)\n}\n\nfunc TestMappingCustomSliceOfSliceUnmarshalTextUri(t *testing.T) {\n\tvar s struct {\n\t\tFileData []customPathUnmarshalText `uri:\"path,parser=encoding.TextUnmarshaler\" collection_format:\"csv\"`\n\t}\n\terr := mappingByPtr(&s, formSource{\"path\": {\"bar/foo,bar/foo/spam\"}}, \"uri\")\n\trequire.NoError(t, err)\n\tassert.Equal(t, []customPathUnmarshalText{{\"bar\", \"foo\"}, {\"bar\", \"foo\", \"spam\"}}, s.FileData)\n}\n\nfunc TestMappingCustomSliceOfSliceUnmarshalTextForm(t *testing.T) {\n\tvar s struct {\n\t\tFileData []customPathUnmarshalText `form:\"path,parser=encoding.TextUnmarshaler\" collection_format:\"csv\"`\n\t}\n\terr := mappingByPtr(&s, formSource{\"path\": {\"bar/foo,bar/foo/spam\"}}, \"form\")\n\trequire.NoError(t, err)\n\tassert.Equal(t, []customPathUnmarshalText{{\"bar\", \"foo\"}, {\"bar\", \"foo\", \"spam\"}}, s.FileData)\n}\n\nfunc TestMappingCustomSliceOfSliceUnmarshalTextDefault(t *testing.T) {\n\tvar s struct {\n\t\tFileData []customPathUnmarshalText `form:\"path,default=bar/foo;bar/foo/spam,parser=encoding.TextUnmarshaler\" collection_format:\"csv\"`\n\t}\n\terr := mappingByPtr(&s, formSource{\"path\": {}}, \"form\")\n\trequire.NoError(t, err)\n\tassert.Equal(t, []customPathUnmarshalText{{\"bar\", \"foo\"}, {\"bar\", \"foo\", \"spam\"}}, s.FileData)\n}\n\ntype objectIDUnmarshalText [12]byte\n\nfunc (o *objectIDUnmarshalText) UnmarshalText(text []byte) error {\n\toid, err := convertToOidUnmarshalText(string(text))\n\tif err != nil {\n\t\treturn err\n\t}\n\n\t*o = oid\n\treturn nil\n}\n\nfunc convertToOidUnmarshalText(s string) (objectIDUnmarshalText, error) {\n\toid, err := convertTo(s)\n\treturn objectIDUnmarshalText(oid), err\n}\n\nvar _ encoding.TextUnmarshaler = (*objectIDUnmarshalText)(nil)\n\nfunc TestMappingCustomArrayUnmarshalTextUri(t *testing.T) {\n\tvar s struct {\n\t\tFileData objectIDUnmarshalText `uri:\"id,parser=encoding.TextUnmarshaler\"`\n\t}\n\tval := `664a062ac74a8ad104e0e80f`\n\terr := mappingByPtr(&s, formSource{\"id\": {val}}, \"uri\")\n\trequire.NoError(t, err)\n\n\texpected, _ := convertToOidUnmarshalText(val)\n\tassert.Equal(t, expected, s.FileData)\n}\n\nfunc TestMappingCustomArrayUnmarshalTextForm(t *testing.T) {\n\tvar s struct {\n\t\tFileData objectIDUnmarshalText `form:\"id,parser=encoding.TextUnmarshaler\"`\n\t}\n\tval := `664a062ac74a8ad104e0e80f`\n\terr := mappingByPtr(&s, formSource{\"id\": {val}}, \"form\")\n\trequire.NoError(t, err)\n\n\texpected, _ := convertToOidUnmarshalText(val)\n\tassert.Equal(t, expected, s.FileData)\n}\n\nfunc TestMappingCustomArrayOfArrayUnmarshalTextUri(t *testing.T) {\n\tid1, _ := convertToOidUnmarshalText(`664a062ac74a8ad104e0e80e`)\n\tid2, _ := convertToOidUnmarshalText(`664a062ac74a8ad104e0e80f`)\n\n\tvar s struct {\n\t\tFileData []objectIDUnmarshalText `uri:\"ids,parser=encoding.TextUnmarshaler\" collection_format:\"csv\"`\n\t}\n\terr := mappingByPtr(&s, formSource{\"ids\": {`664a062ac74a8ad104e0e80e,664a062ac74a8ad104e0e80f`}}, \"uri\")\n\trequire.NoError(t, err)\n\tassert.Equal(t, []objectIDUnmarshalText{id1, id2}, s.FileData)\n}\n\nfunc TestMappingCustomArrayOfArrayUnmarshalTextForm(t *testing.T) {\n\tid1, _ := convertToOidUnmarshalText(`664a062ac74a8ad104e0e80e`)\n\tid2, _ := convertToOidUnmarshalText(`664a062ac74a8ad104e0e80f`)\n\n\tvar s struct {\n\t\tFileData []objectIDUnmarshalText `form:\"ids,parser=encoding.TextUnmarshaler\" collection_format:\"csv\"`\n\t}\n\terr := mappingByPtr(&s, formSource{\"ids\": {`664a062ac74a8ad104e0e80e,664a062ac74a8ad104e0e80f`}}, \"form\")\n\trequire.NoError(t, err)\n\tassert.Equal(t, []objectIDUnmarshalText{id1, id2}, s.FileData)\n}\n\nfunc TestMappingCustomArrayOfArrayUnmarshalTextDefault(t *testing.T) {\n\tid1, _ := convertToOidUnmarshalText(`664a062ac74a8ad104e0e80e`)\n\tid2, _ := convertToOidUnmarshalText(`664a062ac74a8ad104e0e80f`)\n\n\tvar s struct {\n\t\tFileData []objectIDUnmarshalText `form:\"ids,default=664a062ac74a8ad104e0e80e;664a062ac74a8ad104e0e80f,parser=encoding.TextUnmarshaler\" collection_format:\"csv\"`\n\t}\n\terr := mappingByPtr(&s, formSource{\"ids\": {}}, \"form\")\n\trequire.NoError(t, err)\n\tassert.Equal(t, []objectIDUnmarshalText{id1, id2}, s.FileData)\n}\n\n// If someone specifies parser=TextUnmarshaler and it's not defined for the type, gin should revert to using its default\n// binding logic.\nfunc TestMappingUsingBindUnmarshalerAndTextUnmarshalerWhenOnlyBindUnmarshalerDefined(t *testing.T) {\n\tvar s struct {\n\t\tHex                customUnmarshalParamHex `form:\"hex\"`\n\t\tHexByUnmarshalText customUnmarshalParamHex `form:\"hex2,parser=encoding.TextUnmarshaler\"`\n\t}\n\terr := mappingByPtr(&s, formSource{\n\t\t\"hex\":  {`f5`},\n\t\t\"hex2\": {`f5`},\n\t}, \"form\")\n\trequire.NoError(t, err)\n\n\tassert.EqualValues(t, 0xf5, s.Hex)\n\tassert.EqualValues(t, 0xf5, s.HexByUnmarshalText) // reverts to BindUnmarshaler binding\n}\n\n// If someone does not specify parser=TextUnmarshaler even when it's defined for the type, gin should ignore the\n// UnmarshalText logic and continue using its default binding logic. (This ensures gin does not break backwards\n// compatibility)\nfunc TestMappingUsingBindUnmarshalerAndTextUnmarshalerWhenOnlyTextUnmarshalerDefined(t *testing.T) {\n\tvar s struct {\n\t\tHex                customUnmarshalTextHex `form:\"hex\"`\n\t\tHexByUnmarshalText customUnmarshalTextHex `form:\"hex2,parser=encoding.TextUnmarshaler\"`\n\t}\n\terr := mappingByPtr(&s, formSource{\n\t\t\"hex\":  {`11`},\n\t\t\"hex2\": {`11`},\n\t}, \"form\")\n\trequire.NoError(t, err)\n\n\tassert.EqualValues(t, 11, s.Hex)                  // this is using default int binding, not our custom hex binding. 0x11 should be 17 in decimal\n\tassert.EqualValues(t, 0x11, s.HexByUnmarshalText) // correct expected value for normal hex binding\n}\n\ntype customHexUnmarshalParamAndUnmarshalText int\n\nfunc (f *customHexUnmarshalParamAndUnmarshalText) UnmarshalParam(param string) error {\n\treturn errors.New(\"should not be called in unit test if parser tag present\")\n}\n\nfunc (f *customHexUnmarshalParamAndUnmarshalText) UnmarshalText(text []byte) error {\n\tv, err := strconv.ParseInt(string(text), 16, 64)\n\tif err != nil {\n\t\treturn err\n\t}\n\t*f = customHexUnmarshalParamAndUnmarshalText(v)\n\treturn nil\n}\n\n// If a type has both UnmarshalParam and UnmarshalText methods defined, but the parser tag is set to TextUnmarshaler,\n// then only the UnmarshalText method should be invoked.\nfunc TestMappingUsingTextUnmarshalerWhenBindUnmarshalerAlsoDefined(t *testing.T) {\n\tvar s struct {\n\t\tHex customHexUnmarshalParamAndUnmarshalText `form:\"hex,parser=encoding.TextUnmarshaler\"`\n\t}\n\terr := mappingByPtr(&s, formSource{\n\t\t\"hex\": {`f5`},\n\t}, \"form\")\n\trequire.NoError(t, err)\n\n\tassert.EqualValues(t, 0xf5, s.Hex)\n}\n\n// ====  TextUnmarshaler tests END ====\n\nfunc TestMappingEmptyValues(t *testing.T) {\n\tt.Run(\"slice with default\", func(t *testing.T) {\n\t\tvar s struct {\n\t\t\tSlice []int `form:\"slice,default=5\"`\n\t\t}\n\n\t\t// field not present\n\t\terr := mappingByPtr(&s, formSource{}, \"form\")\n\t\trequire.NoError(t, err)\n\t\tassert.Equal(t, []int{5}, s.Slice)\n\n\t\t// field present but empty\n\t\terr = mappingByPtr(&s, formSource{\"slice\": {}}, \"form\")\n\t\trequire.NoError(t, err)\n\t\tassert.Equal(t, []int{5}, s.Slice)\n\n\t\t// field present with values\n\t\terr = mappingByPtr(&s, formSource{\"slice\": {\"1\", \"2\", \"3\"}}, \"form\")\n\t\trequire.NoError(t, err)\n\t\tassert.Equal(t, []int{1, 2, 3}, s.Slice)\n\t})\n\n\tt.Run(\"array with default\", func(t *testing.T) {\n\t\tvar s struct {\n\t\t\tArray [1]int `form:\"array,default=5\"`\n\t\t}\n\n\t\t// field not present\n\t\terr := mappingByPtr(&s, formSource{}, \"form\")\n\t\trequire.NoError(t, err)\n\t\tassert.Equal(t, [1]int{5}, s.Array)\n\n\t\t// field present but empty\n\t\terr = mappingByPtr(&s, formSource{\"array\": {}}, \"form\")\n\t\trequire.NoError(t, err)\n\t\tassert.Equal(t, [1]int{5}, s.Array)\n\t})\n\n\tt.Run(\"slice without default\", func(t *testing.T) {\n\t\tvar s struct {\n\t\t\tSlice []int `form:\"slice\"`\n\t\t}\n\n\t\t// field present but empty\n\t\terr := mappingByPtr(&s, formSource{\"slice\": {}}, \"form\")\n\t\trequire.NoError(t, err)\n\t\tassert.Equal(t, []int(nil), s.Slice)\n\t})\n\n\tt.Run(\"array without default\", func(t *testing.T) {\n\t\tvar s struct {\n\t\t\tArray [1]int `form:\"array\"`\n\t\t}\n\n\t\t// field present but empty\n\t\terr := mappingByPtr(&s, formSource{\"array\": {}}, \"form\")\n\t\trequire.NoError(t, err)\n\t\tassert.Equal(t, [1]int{0}, s.Array)\n\t})\n\n\tt.Run(\"slice with collection format\", func(t *testing.T) {\n\t\tvar s struct {\n\t\t\tSliceMulti []int `form:\"slice_multi,default=1;2;3\" collection_format:\"multi\"`\n\t\t\tSliceCsv   []int `form:\"slice_csv,default=1;2;3\" collection_format:\"csv\"`\n\t\t}\n\n\t\t// field not present\n\t\terr := mappingByPtr(&s, formSource{}, \"form\")\n\t\trequire.NoError(t, err)\n\t\tassert.Equal(t, []int{1, 2, 3}, s.SliceMulti)\n\t\tassert.Equal(t, []int{1, 2, 3}, s.SliceCsv)\n\n\t\t// field present but empty\n\t\terr = mappingByPtr(&s, formSource{\"slice_multi\": {}, \"slice_csv\": {}}, \"form\")\n\t\trequire.NoError(t, err)\n\t\tassert.Equal(t, []int{1, 2, 3}, s.SliceMulti)\n\t\tassert.Equal(t, []int{1, 2, 3}, s.SliceCsv)\n\t})\n}\n"
  },
  {
    "path": "binding/header.go",
    "content": "// Copyright 2022 Gin Core Team. All rights reserved.\n// Use of this source code is governed by a MIT style\n// license that can be found in the LICENSE file.\n\npackage binding\n\nimport (\n\t\"net/http\"\n\t\"net/textproto\"\n\t\"reflect\"\n)\n\ntype headerBinding struct{}\n\nfunc (headerBinding) Name() string {\n\treturn \"header\"\n}\n\nfunc (headerBinding) Bind(req *http.Request, obj any) error {\n\tif err := mapHeader(obj, req.Header); err != nil {\n\t\treturn err\n\t}\n\n\treturn validate(obj)\n}\n\nfunc mapHeader(ptr any, h map[string][]string) error {\n\treturn mappingByPtr(ptr, headerSource(h), \"header\")\n}\n\ntype headerSource map[string][]string\n\nvar _ setter = headerSource(nil)\n\nfunc (hs headerSource) TrySet(value reflect.Value, field reflect.StructField, tagValue string, opt setOptions) (bool, error) {\n\treturn setByForm(value, field, hs, textproto.CanonicalMIMEHeaderKey(tagValue), opt)\n}\n"
  },
  {
    "path": "binding/json.go",
    "content": "// Copyright 2014 Manu Martinez-Almeida. All rights reserved.\n// Use of this source code is governed by a MIT style\n// license that can be found in the LICENSE file.\n\npackage binding\n\nimport (\n\t\"bytes\"\n\t\"errors\"\n\t\"io\"\n\t\"net/http\"\n\n\t\"github.com/gin-gonic/gin/codec/json\"\n)\n\n// EnableDecoderUseNumber is used to call the UseNumber method on the JSON\n// Decoder instance. UseNumber causes the Decoder to unmarshal a number into an\n// any as a Number instead of as a float64.\nvar EnableDecoderUseNumber = false\n\n// EnableDecoderDisallowUnknownFields is used to call the DisallowUnknownFields method\n// on the JSON Decoder instance. DisallowUnknownFields causes the Decoder to\n// return an error when the destination is a struct and the input contains object\n// keys which do not match any non-ignored, exported fields in the destination.\nvar EnableDecoderDisallowUnknownFields = false\n\ntype jsonBinding struct{}\n\nfunc (jsonBinding) Name() string {\n\treturn \"json\"\n}\n\nfunc (jsonBinding) Bind(req *http.Request, obj any) error {\n\tif req == nil || req.Body == nil {\n\t\treturn errors.New(\"invalid request\")\n\t}\n\treturn decodeJSON(req.Body, obj)\n}\n\nfunc (jsonBinding) BindBody(body []byte, obj any) error {\n\treturn decodeJSON(bytes.NewReader(body), obj)\n}\n\nfunc decodeJSON(r io.Reader, obj any) error {\n\tdecoder := json.API.NewDecoder(r)\n\tif EnableDecoderUseNumber {\n\t\tdecoder.UseNumber()\n\t}\n\tif EnableDecoderDisallowUnknownFields {\n\t\tdecoder.DisallowUnknownFields()\n\t}\n\tif err := decoder.Decode(obj); err != nil {\n\t\treturn err\n\t}\n\treturn validate(obj)\n}\n"
  },
  {
    "path": "binding/json_test.go",
    "content": "// Copyright 2019 Gin Core Team. All rights reserved.\n// Use of this source code is governed by a MIT style\n// license that can be found in the LICENSE file.\n\npackage binding\n\nimport (\n\t\"io\"\n\t\"net/http/httptest\"\n\t\"testing\"\n\t\"time\"\n\t\"unsafe\"\n\n\t\"github.com/gin-gonic/gin/codec/json\"\n\t\"github.com/gin-gonic/gin/render\"\n\tjsoniter \"github.com/json-iterator/go\"\n\t\"github.com/modern-go/reflect2\"\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestJSONBindingBindBody(t *testing.T) {\n\tvar s struct {\n\t\tFoo string `json:\"foo\"`\n\t}\n\terr := jsonBinding{}.BindBody([]byte(`{\"foo\": \"FOO\"}`), &s)\n\trequire.NoError(t, err)\n\tassert.Equal(t, \"FOO\", s.Foo)\n}\n\nfunc TestJSONBindingBindBodyMap(t *testing.T) {\n\ts := make(map[string]string)\n\terr := jsonBinding{}.BindBody([]byte(`{\"foo\": \"FOO\",\"hello\":\"world\"}`), &s)\n\trequire.NoError(t, err)\n\tassert.Len(t, s, 2)\n\tassert.Equal(t, \"FOO\", s[\"foo\"])\n\tassert.Equal(t, \"world\", s[\"hello\"])\n}\n\nfunc TestCustomJsonCodec(t *testing.T) {\n\t// Restore json encoding configuration after testing\n\toldMarshal := json.API\n\tdefer func() {\n\t\tjson.API = oldMarshal\n\t}()\n\t// Custom json api\n\tjson.API = customJsonApi{}\n\n\t// test decode json\n\tobj := customReq{}\n\terr := jsonBinding{}.BindBody([]byte(`{\"time_empty\":null,\"time_struct\": \"2001-12-05 10:01:02.345\",\"time_nil\":null,\"time_pointer\":\"2002-12-05 10:01:02.345\"}`), &obj)\n\trequire.NoError(t, err)\n\tassert.Equal(t, zeroTime, obj.TimeEmpty)\n\tassert.Equal(t, time.Date(2001, 12, 5, 10, 1, 2, 345000000, time.Local), obj.TimeStruct)\n\tassert.Nil(t, obj.TimeNil)\n\tassert.Equal(t, time.Date(2002, 12, 5, 10, 1, 2, 345000000, time.Local), *obj.TimePointer)\n\t// test encode json\n\tw := httptest.NewRecorder()\n\terr2 := (render.PureJSON{Data: obj}).Render(w)\n\trequire.NoError(t, err2)\n\tassert.JSONEq(t, \"{\\\"time_empty\\\":null,\\\"time_struct\\\":\\\"2001-12-05 10:01:02.345\\\",\\\"time_nil\\\":null,\\\"time_pointer\\\":\\\"2002-12-05 10:01:02.345\\\"}\\n\", w.Body.String())\n\tassert.Equal(t, \"application/json; charset=utf-8\", w.Header().Get(\"Content-Type\"))\n}\n\ntype customReq struct {\n\tTimeEmpty   time.Time  `json:\"time_empty\"`\n\tTimeStruct  time.Time  `json:\"time_struct\"`\n\tTimeNil     *time.Time `json:\"time_nil\"`\n\tTimePointer *time.Time `json:\"time_pointer\"`\n}\n\nvar customConfig = jsoniter.Config{\n\tEscapeHTML:             true,\n\tSortMapKeys:            true,\n\tValidateJsonRawMessage: true,\n}.Froze()\n\nfunc init() {\n\tcustomConfig.RegisterExtension(&TimeEx{})\n\tcustomConfig.RegisterExtension(&TimePointerEx{})\n}\n\ntype customJsonApi struct{}\n\nfunc (j customJsonApi) Marshal(v any) ([]byte, error) {\n\treturn customConfig.Marshal(v)\n}\n\nfunc (j customJsonApi) Unmarshal(data []byte, v any) error {\n\treturn customConfig.Unmarshal(data, v)\n}\n\nfunc (j customJsonApi) MarshalIndent(v any, prefix, indent string) ([]byte, error) {\n\treturn customConfig.MarshalIndent(v, prefix, indent)\n}\n\nfunc (j customJsonApi) NewEncoder(writer io.Writer) json.Encoder {\n\treturn customConfig.NewEncoder(writer)\n}\n\nfunc (j customJsonApi) NewDecoder(reader io.Reader) json.Decoder {\n\treturn customConfig.NewDecoder(reader)\n}\n\n// region Time Extension\n\nvar (\n\tzeroTime         = time.Time{}\n\ttimeType         = reflect2.TypeOfPtr((*time.Time)(nil)).Elem()\n\tdefaultTimeCodec = &timeCodec{}\n)\n\ntype TimeEx struct {\n\tjsoniter.DummyExtension\n}\n\nfunc (te *TimeEx) CreateDecoder(typ reflect2.Type) jsoniter.ValDecoder {\n\tif typ == timeType {\n\t\treturn defaultTimeCodec\n\t}\n\treturn nil\n}\n\nfunc (te *TimeEx) CreateEncoder(typ reflect2.Type) jsoniter.ValEncoder {\n\tif typ == timeType {\n\t\treturn defaultTimeCodec\n\t}\n\treturn nil\n}\n\ntype timeCodec struct{}\n\nfunc (tc timeCodec) IsEmpty(ptr unsafe.Pointer) bool {\n\tt := *((*time.Time)(ptr))\n\treturn t.Equal(zeroTime)\n}\n\nfunc (tc timeCodec) Encode(ptr unsafe.Pointer, stream *jsoniter.Stream) {\n\tt := *((*time.Time)(ptr))\n\tif t.Equal(zeroTime) {\n\t\tstream.WriteNil()\n\t\treturn\n\t}\n\tstream.WriteString(t.In(time.Local).Format(\"2006-01-02 15:04:05.000\"))\n}\n\nfunc (tc timeCodec) Decode(ptr unsafe.Pointer, iter *jsoniter.Iterator) {\n\tts := iter.ReadString()\n\tif len(ts) == 0 {\n\t\t*((*time.Time)(ptr)) = zeroTime\n\t\treturn\n\t}\n\tt, err := time.ParseInLocation(\"2006-01-02 15:04:05.000\", ts, time.Local)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\t*((*time.Time)(ptr)) = t\n}\n\n// endregion\n\n// region *Time Extension\n\nvar (\n\ttimePointerType         = reflect2.TypeOfPtr((**time.Time)(nil)).Elem()\n\tdefaultTimePointerCodec = &timePointerCodec{}\n)\n\ntype TimePointerEx struct {\n\tjsoniter.DummyExtension\n}\n\nfunc (tpe *TimePointerEx) CreateDecoder(typ reflect2.Type) jsoniter.ValDecoder {\n\tif typ == timePointerType {\n\t\treturn defaultTimePointerCodec\n\t}\n\treturn nil\n}\n\nfunc (tpe *TimePointerEx) CreateEncoder(typ reflect2.Type) jsoniter.ValEncoder {\n\tif typ == timePointerType {\n\t\treturn defaultTimePointerCodec\n\t}\n\treturn nil\n}\n\ntype timePointerCodec struct{}\n\nfunc (tpc timePointerCodec) IsEmpty(ptr unsafe.Pointer) bool {\n\tt := *((**time.Time)(ptr))\n\treturn t == nil || (*t).Equal(zeroTime)\n}\n\nfunc (tpc timePointerCodec) Encode(ptr unsafe.Pointer, stream *jsoniter.Stream) {\n\tt := *((**time.Time)(ptr))\n\tif t == nil || (*t).Equal(zeroTime) {\n\t\tstream.WriteNil()\n\t\treturn\n\t}\n\tstream.WriteString(t.In(time.Local).Format(\"2006-01-02 15:04:05.000\"))\n}\n\nfunc (tpc timePointerCodec) Decode(ptr unsafe.Pointer, iter *jsoniter.Iterator) {\n\tts := iter.ReadString()\n\tif len(ts) == 0 {\n\t\t*((**time.Time)(ptr)) = nil\n\t\treturn\n\t}\n\tt, err := time.ParseInLocation(\"2006-01-02 15:04:05.000\", ts, time.Local)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\t*((**time.Time)(ptr)) = &t\n}\n\n// endregion\n"
  },
  {
    "path": "binding/msgpack.go",
    "content": "// Copyright 2017 Manu Martinez-Almeida. All rights reserved.\n// Use of this source code is governed by a MIT style\n// license that can be found in the LICENSE file.\n\n//go:build !nomsgpack\n\npackage binding\n\nimport (\n\t\"bytes\"\n\t\"io\"\n\t\"net/http\"\n\n\t\"github.com/ugorji/go/codec\"\n)\n\ntype msgpackBinding struct{}\n\nfunc (msgpackBinding) Name() string {\n\treturn \"msgpack\"\n}\n\nfunc (msgpackBinding) Bind(req *http.Request, obj any) error {\n\treturn decodeMsgPack(req.Body, obj)\n}\n\nfunc (msgpackBinding) BindBody(body []byte, obj any) error {\n\treturn decodeMsgPack(bytes.NewReader(body), obj)\n}\n\nfunc decodeMsgPack(r io.Reader, obj any) error {\n\tcdc := new(codec.MsgpackHandle)\n\tif err := codec.NewDecoder(r, cdc).Decode(&obj); err != nil {\n\t\treturn err\n\t}\n\treturn validate(obj)\n}\n"
  },
  {
    "path": "binding/msgpack_test.go",
    "content": "// Copyright 2019 Gin Core Team. All rights reserved.\n// Use of this source code is governed by a MIT style\n// license that can be found in the LICENSE file.\n\n//go:build !nomsgpack\n\npackage binding\n\nimport (\n\t\"bytes\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n\t\"github.com/ugorji/go/codec\"\n)\n\nfunc TestMsgpackBindingBindBody(t *testing.T) {\n\ttype teststruct struct {\n\t\tFoo string `msgpack:\"foo\"`\n\t}\n\tvar s teststruct\n\terr := msgpackBinding{}.BindBody(msgpackBody(t, teststruct{\"FOO\"}), &s)\n\trequire.NoError(t, err)\n\tassert.Equal(t, \"FOO\", s.Foo)\n}\n\nfunc msgpackBody(t *testing.T, obj any) []byte {\n\tvar bs bytes.Buffer\n\th := &codec.MsgpackHandle{}\n\terr := codec.NewEncoder(&bs, h).Encode(obj)\n\trequire.NoError(t, err)\n\treturn bs.Bytes()\n}\n"
  },
  {
    "path": "binding/multipart_form_mapping.go",
    "content": "// Copyright 2019 Gin Core Team. All rights reserved.\n// Use of this source code is governed by a MIT style\n// license that can be found in the LICENSE file.\n\npackage binding\n\nimport (\n\t\"errors\"\n\t\"mime/multipart\"\n\t\"net/http\"\n\t\"reflect\"\n)\n\ntype multipartRequest http.Request\n\nvar _ setter = (*multipartRequest)(nil)\n\nvar (\n\t// ErrMultiFileHeader multipart.FileHeader invalid\n\tErrMultiFileHeader = errors.New(\"unsupported field type for multipart.FileHeader\")\n\n\t// ErrMultiFileHeaderLenInvalid array for []*multipart.FileHeader len invalid\n\tErrMultiFileHeaderLenInvalid = errors.New(\"unsupported len of array for []*multipart.FileHeader\")\n)\n\n// TrySet tries to set a value by the multipart request with the binding a form file\nfunc (r *multipartRequest) TrySet(value reflect.Value, field reflect.StructField, key string, opt setOptions) (bool, error) {\n\tif files := r.MultipartForm.File[key]; len(files) != 0 {\n\t\treturn setByMultipartFormFile(value, field, files)\n\t}\n\n\treturn setByForm(value, field, r.MultipartForm.Value, key, opt)\n}\n\nfunc setByMultipartFormFile(value reflect.Value, field reflect.StructField, files []*multipart.FileHeader) (isSet bool, err error) {\n\tswitch value.Kind() {\n\tcase reflect.Ptr:\n\t\tswitch value.Interface().(type) {\n\t\tcase *multipart.FileHeader:\n\t\t\tvalue.Set(reflect.ValueOf(files[0]))\n\t\t\treturn true, nil\n\t\t}\n\tcase reflect.Struct:\n\t\tswitch value.Interface().(type) {\n\t\tcase multipart.FileHeader:\n\t\t\tvalue.Set(reflect.ValueOf(*files[0]))\n\t\t\treturn true, nil\n\t\t}\n\tcase reflect.Slice:\n\t\tslice := reflect.MakeSlice(value.Type(), len(files), len(files))\n\t\tisSet, err = setArrayOfMultipartFormFiles(slice, field, files)\n\t\tif err != nil || !isSet {\n\t\t\treturn isSet, err\n\t\t}\n\t\tvalue.Set(slice)\n\t\treturn true, nil\n\tcase reflect.Array:\n\t\treturn setArrayOfMultipartFormFiles(value, field, files)\n\t}\n\treturn false, ErrMultiFileHeader\n}\n\nfunc setArrayOfMultipartFormFiles(value reflect.Value, field reflect.StructField, files []*multipart.FileHeader) (isSet bool, err error) {\n\tif value.Len() != len(files) {\n\t\treturn false, ErrMultiFileHeaderLenInvalid\n\t}\n\tfor i := range files {\n\t\tset, err := setByMultipartFormFile(value.Index(i), field, files[i:i+1])\n\t\tif err != nil || !set {\n\t\t\treturn set, err\n\t\t}\n\t}\n\treturn true, nil\n}\n"
  },
  {
    "path": "binding/multipart_form_mapping_test.go",
    "content": "// Copyright 2019 Gin Core Team. All rights reserved.\n// Use of this source code is governed by a MIT style\n// license that can be found in the LICENSE file.\n\npackage binding\n\nimport (\n\t\"bytes\"\n\t\"io\"\n\t\"mime/multipart\"\n\t\"net/http\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestFormMultipartBindingBindOneFile(t *testing.T) {\n\tvar s struct {\n\t\tFileValue   multipart.FileHeader     `form:\"file\"`\n\t\tFilePtr     *multipart.FileHeader    `form:\"file\"`\n\t\tSliceValues []multipart.FileHeader   `form:\"file\"`\n\t\tSlicePtrs   []*multipart.FileHeader  `form:\"file\"`\n\t\tArrayValues [1]multipart.FileHeader  `form:\"file\"`\n\t\tArrayPtrs   [1]*multipart.FileHeader `form:\"file\"`\n\t}\n\tfile := testFile{\"file\", \"file1\", []byte(\"hello\")}\n\n\treq := createRequestMultipartFiles(t, file)\n\terr := FormMultipart.Bind(req, &s)\n\trequire.NoError(t, err)\n\n\tassertMultipartFileHeader(t, &s.FileValue, file)\n\tassertMultipartFileHeader(t, s.FilePtr, file)\n\tassert.Len(t, s.SliceValues, 1)\n\tassertMultipartFileHeader(t, &s.SliceValues[0], file)\n\tassert.Len(t, s.SlicePtrs, 1)\n\tassertMultipartFileHeader(t, s.SlicePtrs[0], file)\n\tassertMultipartFileHeader(t, &s.ArrayValues[0], file)\n\tassertMultipartFileHeader(t, s.ArrayPtrs[0], file)\n}\n\nfunc TestFormMultipartBindingBindTwoFiles(t *testing.T) {\n\tvar s struct {\n\t\tSliceValues []multipart.FileHeader   `form:\"file\"`\n\t\tSlicePtrs   []*multipart.FileHeader  `form:\"file\"`\n\t\tArrayValues [2]multipart.FileHeader  `form:\"file\"`\n\t\tArrayPtrs   [2]*multipart.FileHeader `form:\"file\"`\n\t}\n\tfiles := []testFile{\n\t\t{\"file\", \"file1\", []byte(\"hello\")},\n\t\t{\"file\", \"file2\", []byte(\"world\")},\n\t}\n\n\treq := createRequestMultipartFiles(t, files...)\n\terr := FormMultipart.Bind(req, &s)\n\trequire.NoError(t, err)\n\n\tassert.Len(t, s.SliceValues, len(files))\n\tassert.Len(t, s.SlicePtrs, len(files))\n\tassert.Len(t, s.ArrayValues, len(files))\n\tassert.Len(t, s.ArrayPtrs, len(files))\n\n\tfor i, file := range files {\n\t\tassertMultipartFileHeader(t, &s.SliceValues[i], file)\n\t\tassertMultipartFileHeader(t, s.SlicePtrs[i], file)\n\t\tassertMultipartFileHeader(t, &s.ArrayValues[i], file)\n\t\tassertMultipartFileHeader(t, s.ArrayPtrs[i], file)\n\t}\n}\n\nfunc TestFormMultipartBindingBindError(t *testing.T) {\n\tfiles := []testFile{\n\t\t{\"file\", \"file1\", []byte(\"hello\")},\n\t\t{\"file\", \"file2\", []byte(\"world\")},\n\t}\n\n\tfor _, tt := range []struct {\n\t\tname string\n\t\ts    any\n\t}{\n\t\t{\"wrong type\", &struct {\n\t\t\tFiles int `form:\"file\"`\n\t\t}{}},\n\t\t{\"wrong array size\", &struct {\n\t\t\tFiles [1]*multipart.FileHeader `form:\"file\"`\n\t\t}{}},\n\t\t{\"wrong slice type\", &struct {\n\t\t\tFiles []int `form:\"file\"`\n\t\t}{}},\n\t} {\n\t\treq := createRequestMultipartFiles(t, files...)\n\t\terr := FormMultipart.Bind(req, tt.s)\n\t\trequire.Error(t, err)\n\t}\n}\n\ntype testFile struct {\n\tFieldname string\n\tFilename  string\n\tContent   []byte\n}\n\nfunc createRequestMultipartFiles(t *testing.T, files ...testFile) *http.Request {\n\tvar body bytes.Buffer\n\n\tmw := multipart.NewWriter(&body)\n\tfor _, file := range files {\n\t\tfw, err := mw.CreateFormFile(file.Fieldname, file.Filename)\n\t\trequire.NoError(t, err)\n\n\t\tn, err := fw.Write(file.Content)\n\t\trequire.NoError(t, err)\n\t\tassert.Equal(t, len(file.Content), n)\n\t}\n\terr := mw.Close()\n\trequire.NoError(t, err)\n\n\treq, err := http.NewRequest(http.MethodPost, \"/\", &body)\n\trequire.NoError(t, err)\n\n\treq.Header.Set(\"Content-Type\", MIMEMultipartPOSTForm+\"; boundary=\"+mw.Boundary())\n\treturn req\n}\n\nfunc assertMultipartFileHeader(t *testing.T, fh *multipart.FileHeader, file testFile) {\n\tassert.Equal(t, file.Filename, fh.Filename)\n\tassert.Equal(t, int64(len(file.Content)), fh.Size)\n\n\tfl, err := fh.Open()\n\trequire.NoError(t, err)\n\n\tbody, err := io.ReadAll(fl)\n\trequire.NoError(t, err)\n\tassert.Equal(t, string(file.Content), string(body))\n\n\terr = fl.Close()\n\trequire.NoError(t, err)\n}\n"
  },
  {
    "path": "binding/plain.go",
    "content": "package binding\n\nimport (\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"reflect\"\n\n\t\"github.com/gin-gonic/gin/internal/bytesconv\"\n)\n\ntype plainBinding struct{}\n\nfunc (plainBinding) Name() string {\n\treturn \"plain\"\n}\n\nfunc (plainBinding) Bind(req *http.Request, obj any) error {\n\tall, err := io.ReadAll(req.Body)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn decodePlain(all, obj)\n}\n\nfunc (plainBinding) BindBody(body []byte, obj any) error {\n\treturn decodePlain(body, obj)\n}\n\nfunc decodePlain(data []byte, obj any) error {\n\tif obj == nil {\n\t\treturn nil\n\t}\n\n\tv := reflect.ValueOf(obj)\n\n\tfor v.Kind() == reflect.Ptr {\n\t\tif v.IsNil() {\n\t\t\treturn nil\n\t\t}\n\t\tv = v.Elem()\n\t}\n\n\tif v.Kind() == reflect.String {\n\t\tv.SetString(bytesconv.BytesToString(data))\n\t\treturn nil\n\t}\n\n\tif _, ok := v.Interface().([]byte); ok {\n\t\tv.SetBytes(data)\n\t\treturn nil\n\t}\n\n\treturn fmt.Errorf(\"type (%T) unknown type\", v)\n}\n"
  },
  {
    "path": "binding/protobuf.go",
    "content": "// Copyright 2014 Manu Martinez-Almeida. All rights reserved.\n// Use of this source code is governed by a MIT style\n// license that can be found in the LICENSE file.\n\npackage binding\n\nimport (\n\t\"errors\"\n\t\"io\"\n\t\"net/http\"\n\n\t\"google.golang.org/protobuf/proto\"\n)\n\ntype protobufBinding struct{}\n\nfunc (protobufBinding) Name() string {\n\treturn \"protobuf\"\n}\n\nfunc (b protobufBinding) Bind(req *http.Request, obj any) error {\n\tbuf, err := io.ReadAll(req.Body)\n\tif err != nil {\n\t\treturn err\n\t}\n\treturn b.BindBody(buf, obj)\n}\n\nfunc (protobufBinding) BindBody(body []byte, obj any) error {\n\tmsg, ok := obj.(proto.Message)\n\tif !ok {\n\t\treturn errors.New(\"obj is not ProtoMessage\")\n\t}\n\tif err := proto.Unmarshal(body, msg); err != nil {\n\t\treturn err\n\t}\n\t// Here it's same to return validate(obj), but until now we can't add\n\t// `binding:\"\"` to the struct which automatically generate by gen-proto\n\treturn nil\n\t// return validate(obj)\n}\n"
  },
  {
    "path": "binding/query.go",
    "content": "// Copyright 2017 Manu Martinez-Almeida. All rights reserved.\n// Use of this source code is governed by a MIT style\n// license that can be found in the LICENSE file.\n\npackage binding\n\nimport \"net/http\"\n\ntype queryBinding struct{}\n\nfunc (queryBinding) Name() string {\n\treturn \"query\"\n}\n\nfunc (queryBinding) Bind(req *http.Request, obj any) error {\n\tvalues := req.URL.Query()\n\tif err := mapForm(obj, values); err != nil {\n\t\treturn err\n\t}\n\treturn validate(obj)\n}\n"
  },
  {
    "path": "binding/toml.go",
    "content": "// Copyright 2022 Gin Core Team. All rights reserved.\n// Use of this source code is governed by a MIT style\n// license that can be found in the LICENSE file.\n\npackage binding\n\nimport (\n\t\"bytes\"\n\t\"io\"\n\t\"net/http\"\n\n\t\"github.com/pelletier/go-toml/v2\"\n)\n\ntype tomlBinding struct{}\n\nfunc (tomlBinding) Name() string {\n\treturn \"toml\"\n}\n\nfunc (tomlBinding) Bind(req *http.Request, obj any) error {\n\treturn decodeToml(req.Body, obj)\n}\n\nfunc (tomlBinding) BindBody(body []byte, obj any) error {\n\treturn decodeToml(bytes.NewReader(body), obj)\n}\n\nfunc decodeToml(r io.Reader, obj any) error {\n\tdecoder := toml.NewDecoder(r)\n\tif err := decoder.Decode(obj); err != nil {\n\t\treturn err\n\t}\n\treturn validate(obj)\n}\n"
  },
  {
    "path": "binding/toml_test.go",
    "content": "// Copyright 2022 Gin Core Team. All rights reserved.\n// Use of this source code is governed by a MIT style\n// license that can be found in the LICENSE file.\n\npackage binding\n\nimport (\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestTOMLBindingBindBody(t *testing.T) {\n\tvar s struct {\n\t\tFoo string `toml:\"foo\"`\n\t}\n\ttomlBody := `foo=\"FOO\"`\n\terr := tomlBinding{}.BindBody([]byte(tomlBody), &s)\n\trequire.NoError(t, err)\n\tassert.Equal(t, \"FOO\", s.Foo)\n}\n"
  },
  {
    "path": "binding/uri.go",
    "content": "// Copyright 2018 Gin Core Team. All rights reserved.\n// Use of this source code is governed by a MIT style\n// license that can be found in the LICENSE file.\n\npackage binding\n\ntype uriBinding struct{}\n\nfunc (uriBinding) Name() string {\n\treturn \"uri\"\n}\n\nfunc (uriBinding) BindUri(m map[string][]string, obj any) error {\n\tif err := mapURI(obj, m); err != nil {\n\t\treturn err\n\t}\n\treturn validate(obj)\n}\n"
  },
  {
    "path": "binding/validate_test.go",
    "content": "// Copyright 2014 Manu Martinez-Almeida. All rights reserved.\n// Use of this source code is governed by a MIT style\n// license that can be found in the LICENSE file.\n\npackage binding\n\nimport (\n\t\"bytes\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/go-playground/validator/v10\"\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n)\n\ntype testInterface interface {\n\tString() string\n}\n\ntype substructNoValidation struct {\n\tIString string\n\tIInt    int\n}\n\ntype mapNoValidationSub map[string]substructNoValidation\n\ntype structNoValidationValues struct {\n\tsubstructNoValidation\n\n\tBoolean bool\n\n\tUinteger   uint\n\tInteger    int\n\tInteger8   int8\n\tInteger16  int16\n\tInteger32  int32\n\tInteger64  int64\n\tUinteger8  uint8\n\tUinteger16 uint16\n\tUinteger32 uint32\n\tUinteger64 uint64\n\n\tFloat32 float32\n\tFloat64 float64\n\n\tString string\n\n\tDate time.Time\n\n\tStruct        substructNoValidation\n\tInlinedStruct struct {\n\t\tString  []string\n\t\tInteger int\n\t}\n\n\tIntSlice           []int\n\tIntPointerSlice    []*int\n\tStructPointerSlice []*substructNoValidation\n\tStructSlice        []substructNoValidation\n\tInterfaceSlice     []testInterface\n\n\tUniversalInterface any\n\tCustomInterface    testInterface\n\n\tFloatMap  map[string]float32\n\tStructMap mapNoValidationSub\n}\n\nfunc createNoValidationValues() structNoValidationValues {\n\tinteger := 1\n\ts := structNoValidationValues{\n\t\tBoolean:            true,\n\t\tUinteger:           1 << 29,\n\t\tInteger:            -10000,\n\t\tInteger8:           120,\n\t\tInteger16:          -20000,\n\t\tInteger32:          1 << 29,\n\t\tInteger64:          1 << 61,\n\t\tUinteger8:          250,\n\t\tUinteger16:         50000,\n\t\tUinteger32:         1 << 31,\n\t\tUinteger64:         1 << 62,\n\t\tFloat32:            123.456,\n\t\tFloat64:            123.456789,\n\t\tString:             \"text\",\n\t\tDate:               time.Time{},\n\t\tCustomInterface:    &bytes.Buffer{},\n\t\tStruct:             substructNoValidation{},\n\t\tIntSlice:           []int{-3, -2, 1, 0, 1, 2, 3},\n\t\tIntPointerSlice:    []*int{&integer},\n\t\tStructSlice:        []substructNoValidation{},\n\t\tUniversalInterface: 1.2,\n\t\tFloatMap: map[string]float32{\n\t\t\t\"foo\": 1.23,\n\t\t\t\"bar\": 232.323,\n\t\t},\n\t\tStructMap: mapNoValidationSub{\n\t\t\t\"foo\": substructNoValidation{},\n\t\t\t\"bar\": substructNoValidation{},\n\t\t},\n\t\t// StructPointerSlice []noValidationSub\n\t\t// InterfaceSlice     []testInterface\n\t}\n\ts.InlinedStruct.Integer = 1000\n\ts.InlinedStruct.String = []string{\"first\", \"second\"}\n\ts.IString = \"substring\"\n\ts.IInt = 987654\n\treturn s\n}\n\nfunc TestValidateNoValidationValues(t *testing.T) {\n\torigin := createNoValidationValues()\n\ttest := createNoValidationValues()\n\tempty := structNoValidationValues{}\n\n\trequire.NoError(t, validate(test))\n\trequire.NoError(t, validate(&test))\n\trequire.NoError(t, validate(empty))\n\trequire.NoError(t, validate(&empty))\n\n\tassert.Equal(t, origin, test)\n}\n\ntype structNoValidationPointer struct {\n\tsubstructNoValidation\n\n\tBoolean bool\n\n\tUinteger   *uint\n\tInteger    *int\n\tInteger8   *int8\n\tInteger16  *int16\n\tInteger32  *int32\n\tInteger64  *int64\n\tUinteger8  *uint8\n\tUinteger16 *uint16\n\tUinteger32 *uint32\n\tUinteger64 *uint64\n\n\tFloat32 *float32\n\tFloat64 *float64\n\n\tString *string\n\n\tDate *time.Time\n\n\tStruct *substructNoValidation\n\n\tIntSlice           *[]int\n\tIntPointerSlice    *[]*int\n\tStructPointerSlice *[]*substructNoValidation\n\tStructSlice        *[]substructNoValidation\n\tInterfaceSlice     *[]testInterface\n\n\tFloatMap  *map[string]float32\n\tStructMap *mapNoValidationSub\n}\n\nfunc TestValidateNoValidationPointers(t *testing.T) {\n\t// origin := createNoValidation_values()\n\t// test := createNoValidation_values()\n\tempty := structNoValidationPointer{}\n\n\t// assert.Nil(t, validate(test))\n\t// assert.Nil(t, validate(&test))\n\trequire.NoError(t, validate(empty))\n\trequire.NoError(t, validate(&empty))\n\n\t// assert.Equal(t, origin, test)\n}\n\ntype Object map[string]any\n\nfunc TestValidatePrimitives(t *testing.T) {\n\tobj := Object{\"foo\": \"bar\", \"bar\": 1}\n\trequire.NoError(t, validate(obj))\n\trequire.NoError(t, validate(&obj))\n\tassert.Equal(t, Object{\"foo\": \"bar\", \"bar\": 1}, obj)\n\n\tobj2 := []Object{{\"foo\": \"bar\", \"bar\": 1}, {\"foo\": \"bar\", \"bar\": 1}}\n\trequire.NoError(t, validate(obj2))\n\trequire.NoError(t, validate(&obj2))\n\n\tnu := 10\n\trequire.NoError(t, validate(nu))\n\trequire.NoError(t, validate(&nu))\n\tassert.Equal(t, 10, nu)\n\n\tstr := \"value\"\n\trequire.NoError(t, validate(str))\n\trequire.NoError(t, validate(&str))\n\tassert.Equal(t, \"value\", str)\n}\n\ntype structModifyValidation struct {\n\tInteger int\n}\n\nfunc toZero(sl validator.StructLevel) {\n\ts := sl.Top().Interface().(*structModifyValidation)\n\ts.Integer = 0\n}\n\nfunc TestValidateAndModifyStruct(t *testing.T) {\n\t// This validates that pointers to structs are passed to the validator\n\t// giving us the ability to modify the struct being validated.\n\tengine, ok := Validator.Engine().(*validator.Validate)\n\tassert.True(t, ok)\n\n\tengine.RegisterStructValidation(toZero, structModifyValidation{})\n\n\ts := structModifyValidation{Integer: 1}\n\terrs := validate(&s)\n\n\trequire.NoError(t, errs)\n\tassert.Equal(t, structModifyValidation{Integer: 0}, s)\n}\n\n// structCustomValidation is a helper struct we use to check that\n// custom validation can be registered on it.\n// The `notone` binding directive is for custom validation and registered later.\ntype structCustomValidation struct {\n\tInteger int `binding:\"notone\"`\n}\n\nfunc notOne(f1 validator.FieldLevel) bool {\n\tif val, ok := f1.Field().Interface().(int); ok {\n\t\treturn val != 1\n\t}\n\treturn false\n}\n\nfunc TestValidatorEngine(t *testing.T) {\n\t// This validates that the function `notOne` matches\n\t// the expected function signature by `defaultValidator`\n\t// and by extension the validator library.\n\tengine, ok := Validator.Engine().(*validator.Validate)\n\tassert.True(t, ok)\n\n\terr := engine.RegisterValidation(\"notone\", notOne)\n\t// Check that we can register custom validation without error\n\trequire.NoError(t, err)\n\n\t// Create an instance which will fail validation\n\twithOne := structCustomValidation{Integer: 1}\n\terrs := validate(withOne)\n\n\t// Check that we got back non-nil errs\n\trequire.Error(t, errs)\n\t// Check that the error matches expectation\n\trequire.Error(t, errs, \"notone\")\n}\n"
  },
  {
    "path": "binding/xml.go",
    "content": "// Copyright 2014 Manu Martinez-Almeida. All rights reserved.\n// Use of this source code is governed by a MIT style\n// license that can be found in the LICENSE file.\n\npackage binding\n\nimport (\n\t\"bytes\"\n\t\"encoding/xml\"\n\t\"io\"\n\t\"net/http\"\n)\n\ntype xmlBinding struct{}\n\nfunc (xmlBinding) Name() string {\n\treturn \"xml\"\n}\n\nfunc (xmlBinding) Bind(req *http.Request, obj any) error {\n\treturn decodeXML(req.Body, obj)\n}\n\nfunc (xmlBinding) BindBody(body []byte, obj any) error {\n\treturn decodeXML(bytes.NewReader(body), obj)\n}\n\nfunc decodeXML(r io.Reader, obj any) error {\n\tdecoder := xml.NewDecoder(r)\n\tif err := decoder.Decode(obj); err != nil {\n\t\treturn err\n\t}\n\treturn validate(obj)\n}\n"
  },
  {
    "path": "binding/xml_test.go",
    "content": "// Copyright 2019 Gin Core Team. All rights reserved.\n// Use of this source code is governed by a MIT style\n// license that can be found in the LICENSE file.\n\npackage binding\n\nimport (\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestXMLBindingBindBody(t *testing.T) {\n\tvar s struct {\n\t\tFoo string `xml:\"foo\"`\n\t}\n\txmlBody := `<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<root>\n   <foo>FOO</foo>\n</root>`\n\terr := xmlBinding{}.BindBody([]byte(xmlBody), &s)\n\trequire.NoError(t, err)\n\tassert.Equal(t, \"FOO\", s.Foo)\n}\n"
  },
  {
    "path": "binding/yaml.go",
    "content": "// Copyright 2018 Gin Core Team. All rights reserved.\n// Use of this source code is governed by a MIT style\n// license that can be found in the LICENSE file.\n\npackage binding\n\nimport (\n\t\"bytes\"\n\t\"io\"\n\t\"net/http\"\n\n\t\"github.com/goccy/go-yaml\"\n)\n\ntype yamlBinding struct{}\n\nfunc (yamlBinding) Name() string {\n\treturn \"yaml\"\n}\n\nfunc (yamlBinding) Bind(req *http.Request, obj any) error {\n\treturn decodeYAML(req.Body, obj)\n}\n\nfunc (yamlBinding) BindBody(body []byte, obj any) error {\n\treturn decodeYAML(bytes.NewReader(body), obj)\n}\n\nfunc decodeYAML(r io.Reader, obj any) error {\n\tdecoder := yaml.NewDecoder(r)\n\tif err := decoder.Decode(obj); err != nil {\n\t\treturn err\n\t}\n\treturn validate(obj)\n}\n"
  },
  {
    "path": "binding/yaml_test.go",
    "content": "// Copyright 2019 Gin Core Team. All rights reserved.\n// Use of this source code is governed by a MIT style\n// license that can be found in the LICENSE file.\n\npackage binding\n\nimport (\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestYAMLBindingBindBody(t *testing.T) {\n\tvar s struct {\n\t\tFoo string `yaml:\"foo\"`\n\t}\n\terr := yamlBinding{}.BindBody([]byte(\"foo: FOO\"), &s)\n\trequire.NoError(t, err)\n\tassert.Equal(t, \"FOO\", s.Foo)\n}\n"
  },
  {
    "path": "codec/json/api.go",
    "content": "// Copyright 2025 Gin Core Team. All rights reserved.\n// Use of this source code is governed by a MIT style\n// license that can be found in the LICENSE file.\n\npackage json\n\nimport \"io\"\n\n// API the json codec in use.\nvar API Core\n\n// Core the api for json codec.\ntype Core interface {\n\tMarshal(v any) ([]byte, error)\n\tUnmarshal(data []byte, v any) error\n\tMarshalIndent(v any, prefix, indent string) ([]byte, error)\n\tNewEncoder(writer io.Writer) Encoder\n\tNewDecoder(reader io.Reader) Decoder\n}\n\n// Encoder an interface writes JSON values to an output stream.\ntype Encoder interface {\n\t// SetEscapeHTML specifies whether problematic HTML characters\n\t// should be escaped inside JSON quoted strings.\n\t// The default behavior is to escape &, <, and > to \\u0026, \\u003c, and \\u003e\n\t// to avoid certain safety problems that can arise when embedding JSON in HTML.\n\t//\n\t// In non-HTML settings where the escaping interferes with the readability\n\t// of the output, SetEscapeHTML(false) disables this behavior.\n\tSetEscapeHTML(on bool)\n\n\t// Encode writes the JSON encoding of v to the stream,\n\t// followed by a newline character.\n\t//\n\t// See the documentation for Marshal for details about the\n\t// conversion of Go values to JSON.\n\tEncode(v any) error\n}\n\n// Decoder an interface reads and decodes JSON values from an input stream.\ntype Decoder interface {\n\t// UseNumber causes the Decoder to unmarshal a number into an any as a\n\t// Number instead of as a float64.\n\tUseNumber()\n\n\t// DisallowUnknownFields causes the Decoder to return an error when the destination\n\t// is a struct and the input contains object keys which do not match any\n\t// non-ignored, exported fields in the destination.\n\tDisallowUnknownFields()\n\n\t// Decode reads the next JSON-encoded value from its\n\t// input and stores it in the value pointed to by v.\n\t//\n\t// See the documentation for Unmarshal for details about\n\t// the conversion of JSON into a Go value.\n\tDecode(v any) error\n}\n"
  },
  {
    "path": "codec/json/go_json.go",
    "content": "// Copyright 2025 Gin Core Team. All rights reserved.\n// Use of this source code is governed by a MIT style\n// license that can be found in the LICENSE file.\n\n//go:build go_json\n\npackage json\n\nimport (\n\t\"io\"\n\n\t\"github.com/goccy/go-json\"\n)\n\n// Package indicates what library is being used for JSON encoding.\nconst Package = \"github.com/goccy/go-json\"\n\nfunc init() {\n\tAPI = gojsonApi{}\n}\n\ntype gojsonApi struct{}\n\nfunc (j gojsonApi) Marshal(v any) ([]byte, error) {\n\treturn json.Marshal(v)\n}\n\nfunc (j gojsonApi) Unmarshal(data []byte, v any) error {\n\treturn json.Unmarshal(data, v)\n}\n\nfunc (j gojsonApi) MarshalIndent(v any, prefix, indent string) ([]byte, error) {\n\treturn json.MarshalIndent(v, prefix, indent)\n}\n\nfunc (j gojsonApi) NewEncoder(writer io.Writer) Encoder {\n\treturn json.NewEncoder(writer)\n}\n\nfunc (j gojsonApi) NewDecoder(reader io.Reader) Decoder {\n\treturn json.NewDecoder(reader)\n}\n"
  },
  {
    "path": "codec/json/json.go",
    "content": "// Copyright 2025 Gin Core Team. All rights reserved.\n// Use of this source code is governed by a MIT style\n// license that can be found in the LICENSE file.\n\n//go:build !jsoniter && !go_json && !(sonic && (linux || windows || darwin))\n\npackage json\n\nimport (\n\t\"encoding/json\"\n\t\"io\"\n)\n\n// Package indicates what library is being used for JSON encoding.\nconst Package = \"encoding/json\"\n\nfunc init() {\n\tAPI = jsonApi{}\n}\n\ntype jsonApi struct{}\n\nfunc (j jsonApi) Marshal(v any) ([]byte, error) {\n\treturn json.Marshal(v)\n}\n\nfunc (j jsonApi) Unmarshal(data []byte, v any) error {\n\treturn json.Unmarshal(data, v)\n}\n\nfunc (j jsonApi) MarshalIndent(v any, prefix, indent string) ([]byte, error) {\n\treturn json.MarshalIndent(v, prefix, indent)\n}\n\nfunc (j jsonApi) NewEncoder(writer io.Writer) Encoder {\n\treturn json.NewEncoder(writer)\n}\n\nfunc (j jsonApi) NewDecoder(reader io.Reader) Decoder {\n\treturn json.NewDecoder(reader)\n}\n"
  },
  {
    "path": "codec/json/jsoniter.go",
    "content": "// Copyright 2025 Gin Core Team. All rights reserved.\n// Use of this source code is governed by a MIT style\n// license that can be found in the LICENSE file.\n\n//go:build jsoniter\n\npackage json\n\nimport (\n\t\"io\"\n\n\tjsoniter \"github.com/json-iterator/go\"\n)\n\n// Package indicates what library is being used for JSON encoding.\nconst Package = \"github.com/json-iterator/go\"\n\nfunc init() {\n\tAPI = jsoniterApi{}\n}\n\nvar json = jsoniter.ConfigCompatibleWithStandardLibrary\n\ntype jsoniterApi struct{}\n\nfunc (j jsoniterApi) Marshal(v any) ([]byte, error) {\n\treturn json.Marshal(v)\n}\n\nfunc (j jsoniterApi) Unmarshal(data []byte, v any) error {\n\treturn json.Unmarshal(data, v)\n}\n\nfunc (j jsoniterApi) MarshalIndent(v any, prefix, indent string) ([]byte, error) {\n\treturn json.MarshalIndent(v, prefix, indent)\n}\n\nfunc (j jsoniterApi) NewEncoder(writer io.Writer) Encoder {\n\treturn json.NewEncoder(writer)\n}\n\nfunc (j jsoniterApi) NewDecoder(reader io.Reader) Decoder {\n\treturn json.NewDecoder(reader)\n}\n"
  },
  {
    "path": "codec/json/sonic.go",
    "content": "// Copyright 2025 Gin Core Team. All rights reserved.\n// Use of this source code is governed by a MIT style\n// license that can be found in the LICENSE file.\n\n//go:build sonic && (linux || windows || darwin)\n\npackage json\n\nimport (\n\t\"io\"\n\n\t\"github.com/bytedance/sonic\"\n)\n\n// Package indicates what library is being used for JSON encoding.\nconst Package = \"github.com/bytedance/sonic\"\n\nfunc init() {\n\tAPI = sonicApi{}\n}\n\nvar json = sonic.ConfigStd\n\ntype sonicApi struct{}\n\nfunc (j sonicApi) Marshal(v any) ([]byte, error) {\n\treturn json.Marshal(v)\n}\n\nfunc (j sonicApi) Unmarshal(data []byte, v any) error {\n\treturn json.Unmarshal(data, v)\n}\n\nfunc (j sonicApi) MarshalIndent(v any, prefix, indent string) ([]byte, error) {\n\treturn json.MarshalIndent(v, prefix, indent)\n}\n\nfunc (j sonicApi) NewEncoder(writer io.Writer) Encoder {\n\treturn json.NewEncoder(writer)\n}\n\nfunc (j sonicApi) NewDecoder(reader io.Reader) Decoder {\n\treturn json.NewDecoder(reader)\n}\n"
  },
  {
    "path": "codecov.yml",
    "content": "coverage:\n  require_ci_to_pass: true\n\n  status:\n    project:\n      default:\n        target: 99%\n        threshold: 99%\n\n    patch:\n      default:\n        target: 99%\n        threshold: 95%"
  },
  {
    "path": "context.go",
    "content": "// Copyright 2014 Manu Martinez-Almeida. All rights reserved.\n// Use of this source code is governed by a MIT style\n// license that can be found in the LICENSE file.\n\npackage gin\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"io/fs\"\n\t\"log\"\n\t\"maps\"\n\t\"math\"\n\t\"mime/multipart\"\n\t\"net\"\n\t\"net/http\"\n\t\"net/url\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"strings\"\n\t\"sync\"\n\t\"time\"\n\n\t\"github.com/gin-contrib/sse\"\n\t\"github.com/gin-gonic/gin/binding\"\n\t\"github.com/gin-gonic/gin/render\"\n)\n\n// Content-Type MIME of the most common data formats.\nconst (\n\tMIMEJSON              = binding.MIMEJSON\n\tMIMEHTML              = binding.MIMEHTML\n\tMIMEXML               = binding.MIMEXML\n\tMIMEXML2              = binding.MIMEXML2\n\tMIMEPlain             = binding.MIMEPlain\n\tMIMEPOSTForm          = binding.MIMEPOSTForm\n\tMIMEMultipartPOSTForm = binding.MIMEMultipartPOSTForm\n\tMIMEYAML              = binding.MIMEYAML\n\tMIMEYAML2             = binding.MIMEYAML2\n\tMIMETOML              = binding.MIMETOML\n\tMIMEPROTOBUF          = binding.MIMEPROTOBUF\n\tMIMEBSON              = binding.MIMEBSON\n)\n\n// BodyBytesKey indicates a default body bytes key.\nconst BodyBytesKey = \"_gin-gonic/gin/bodybyteskey\"\n\n// ContextKey is the key that a Context returns itself for.\nconst ContextKey = \"_gin-gonic/gin/contextkey\"\n\ntype ContextKeyType int\n\nconst ContextRequestKey ContextKeyType = 0\n\n// abortIndex represents a typical value used in abort functions.\nconst abortIndex int8 = math.MaxInt8 >> 1\n\n// Context is the most important part of gin. It allows us to pass variables between middleware,\n// manage the flow, validate the JSON of a request and render a JSON response for example.\ntype Context struct {\n\twritermem responseWriter\n\tRequest   *http.Request\n\tWriter    ResponseWriter\n\n\tParams   Params\n\thandlers HandlersChain\n\tindex    int8\n\tfullPath string\n\n\tengine       *Engine\n\tparams       *Params\n\tskippedNodes *[]skippedNode\n\n\t// This mutex protects Keys map.\n\tmu sync.RWMutex\n\n\t// Keys is a key/value pair exclusively for the context of each request.\n\tKeys map[any]any\n\n\t// Errors is a list of errors attached to all the handlers/middlewares who used this context.\n\tErrors errorMsgs\n\n\t// Accepted defines a list of manually accepted formats for content negotiation.\n\tAccepted []string\n\n\t// queryCache caches the query result from c.Request.URL.Query().\n\tqueryCache url.Values\n\n\t// formCache caches c.Request.PostForm, which contains the parsed form data from POST, PATCH,\n\t// or PUT body parameters.\n\tformCache url.Values\n\n\t// SameSite allows a server to define a cookie attribute making it impossible for\n\t// the browser to send this cookie along with cross-site requests.\n\tsameSite http.SameSite\n}\n\n/************************************/\n/********** CONTEXT CREATION ********/\n/************************************/\n\nfunc (c *Context) reset() {\n\tc.Writer = &c.writermem\n\tc.Params = c.Params[:0]\n\tc.handlers = nil\n\tc.index = -1\n\n\tc.fullPath = \"\"\n\tc.Keys = nil\n\tc.Errors = c.Errors[:0]\n\tc.Accepted = nil\n\tc.queryCache = nil\n\tc.formCache = nil\n\tc.sameSite = 0\n\t*c.params = (*c.params)[:0]\n\t*c.skippedNodes = (*c.skippedNodes)[:0]\n}\n\n// Copy returns a copy of the current context that can be safely used outside the request's scope.\n// This has to be used when the context has to be passed to a goroutine.\nfunc (c *Context) Copy() *Context {\n\tcp := Context{\n\t\twritermem: c.writermem,\n\t\tRequest:   c.Request,\n\t\tengine:    c.engine,\n\t}\n\n\tcp.writermem.ResponseWriter = nil\n\tcp.Writer = &cp.writermem\n\tcp.index = abortIndex\n\tcp.handlers = nil\n\tcp.fullPath = c.fullPath\n\n\tcKeys := c.Keys\n\tc.mu.RLock()\n\tcp.Keys = maps.Clone(cKeys)\n\tc.mu.RUnlock()\n\n\tcParams := c.Params\n\tcp.Params = make([]Param, len(cParams))\n\tcopy(cp.Params, cParams)\n\n\treturn &cp\n}\n\n// HandlerName returns the main handler's name. For example if the handler is \"handleGetUsers()\",\n// this function will return \"main.handleGetUsers\".\nfunc (c *Context) HandlerName() string {\n\treturn nameOfFunction(c.handlers.Last())\n}\n\n// HandlerNames returns a list of all registered handlers for this context in descending order,\n// following the semantics of HandlerName()\nfunc (c *Context) HandlerNames() []string {\n\thn := make([]string, 0, len(c.handlers))\n\tfor _, val := range c.handlers {\n\t\tif val == nil {\n\t\t\tcontinue\n\t\t}\n\t\thn = append(hn, nameOfFunction(val))\n\t}\n\treturn hn\n}\n\n// Handler returns the main handler.\nfunc (c *Context) Handler() HandlerFunc {\n\treturn c.handlers.Last()\n}\n\n// FullPath returns a matched route full path. For not found routes\n// returns an empty string.\n//\n//\trouter.GET(\"/user/:id\", func(c *gin.Context) {\n//\t    c.FullPath() == \"/user/:id\" // true\n//\t})\nfunc (c *Context) FullPath() string {\n\treturn c.fullPath\n}\n\n/************************************/\n/*********** FLOW CONTROL ***********/\n/************************************/\n\n// Next should be used only inside middleware.\n// It executes the pending handlers in the chain inside the calling handler.\n// See example in GitHub.\nfunc (c *Context) Next() {\n\tc.index++\n\tfor c.index < safeInt8(len(c.handlers)) {\n\t\tif c.handlers[c.index] != nil {\n\t\t\tc.handlers[c.index](c)\n\t\t}\n\t\tc.index++\n\t}\n}\n\n// IsAborted returns true if the current context was aborted.\nfunc (c *Context) IsAborted() bool {\n\treturn c.index >= abortIndex\n}\n\n// Abort prevents pending handlers from being called. Note that this will not stop the current handler.\n// Let's say you have an authorization middleware that validates that the current request is authorized.\n// If the authorization fails (ex: the password does not match), call Abort to ensure the remaining handlers\n// for this request are not called.\nfunc (c *Context) Abort() {\n\tc.index = abortIndex\n}\n\n// AbortWithStatus calls `Abort()` and writes the headers with the specified status code.\n// For example, a failed attempt to authenticate a request could use: context.AbortWithStatus(401).\nfunc (c *Context) AbortWithStatus(code int) {\n\tc.Status(code)\n\tc.Writer.WriteHeaderNow()\n\tc.Abort()\n}\n\n// AbortWithStatusPureJSON calls `Abort()` and then `PureJSON` internally.\n// This method stops the chain, writes the status code and return a JSON body without escaping.\n// It also sets the Content-Type as \"application/json\".\nfunc (c *Context) AbortWithStatusPureJSON(code int, jsonObj any) {\n\tc.Abort()\n\tc.PureJSON(code, jsonObj)\n}\n\n// AbortWithStatusJSON calls `Abort()` and then `JSON` internally.\n// This method stops the chain, writes the status code and return a JSON body.\n// It also sets the Content-Type as \"application/json\".\nfunc (c *Context) AbortWithStatusJSON(code int, jsonObj any) {\n\tc.Abort()\n\tc.JSON(code, jsonObj)\n}\n\n// AbortWithError calls `AbortWithStatus()` and `Error()` internally.\n// This method stops the chain, writes the status code and pushes the specified error to `c.Errors`.\n// See Context.Error() for more details.\nfunc (c *Context) AbortWithError(code int, err error) *Error {\n\tc.AbortWithStatus(code)\n\treturn c.Error(err)\n}\n\n/************************************/\n/********* ERROR MANAGEMENT *********/\n/************************************/\n\n// Error attaches an error to the current context. The error is pushed to a list of errors.\n// It's a good idea to call Error for each error that occurred during the resolution of a request.\n// A middleware can be used to collect all the errors and push them to a database together,\n// print a log, or append it in the HTTP response.\n// Error will panic if err is nil.\nfunc (c *Context) Error(err error) *Error {\n\tif err == nil {\n\t\tpanic(\"err is nil\")\n\t}\n\n\tvar parsedError *Error\n\tok := errors.As(err, &parsedError)\n\tif !ok {\n\t\tparsedError = &Error{\n\t\t\tErr:  err,\n\t\t\tType: ErrorTypePrivate,\n\t\t}\n\t}\n\n\tc.Errors = append(c.Errors, parsedError)\n\treturn parsedError\n}\n\n/************************************/\n/******** METADATA MANAGEMENT********/\n/************************************/\n\n// Set is used to store a new key/value pair exclusively for this context.\n// It also lazy initializes c.Keys if it was not used previously.\nfunc (c *Context) Set(key any, value any) {\n\tc.mu.Lock()\n\tdefer c.mu.Unlock()\n\tif c.Keys == nil {\n\t\tc.Keys = make(map[any]any)\n\t}\n\n\tc.Keys[key] = value\n}\n\n// Get returns the value for the given key, ie: (value, true).\n// If the value does not exist it returns (nil, false)\nfunc (c *Context) Get(key any) (value any, exists bool) {\n\tc.mu.RLock()\n\tdefer c.mu.RUnlock()\n\tvalue, exists = c.Keys[key]\n\treturn\n}\n\n// MustGet returns the value for the given key if it exists, otherwise it panics.\nfunc (c *Context) MustGet(key any) any {\n\tif value, exists := c.Get(key); exists {\n\t\treturn value\n\t}\n\tpanic(fmt.Sprintf(\"key %v does not exist\", key))\n}\n\nfunc getTyped[T any](c *Context, key any) (res T) {\n\tif val, ok := c.Get(key); ok && val != nil {\n\t\tres, _ = val.(T)\n\t}\n\treturn\n}\n\n// GetString returns the value associated with the key as a string.\nfunc (c *Context) GetString(key any) string {\n\treturn getTyped[string](c, key)\n}\n\n// GetBool returns the value associated with the key as a boolean.\nfunc (c *Context) GetBool(key any) bool {\n\treturn getTyped[bool](c, key)\n}\n\n// GetInt returns the value associated with the key as an integer.\nfunc (c *Context) GetInt(key any) int {\n\treturn getTyped[int](c, key)\n}\n\n// GetInt8 returns the value associated with the key as an integer 8.\nfunc (c *Context) GetInt8(key any) int8 {\n\treturn getTyped[int8](c, key)\n}\n\n// GetInt16 returns the value associated with the key as an integer 16.\nfunc (c *Context) GetInt16(key any) int16 {\n\treturn getTyped[int16](c, key)\n}\n\n// GetInt32 returns the value associated with the key as an integer 32.\nfunc (c *Context) GetInt32(key any) int32 {\n\treturn getTyped[int32](c, key)\n}\n\n// GetInt64 returns the value associated with the key as an integer 64.\nfunc (c *Context) GetInt64(key any) int64 {\n\treturn getTyped[int64](c, key)\n}\n\n// GetUint returns the value associated with the key as an unsigned integer.\nfunc (c *Context) GetUint(key any) uint {\n\treturn getTyped[uint](c, key)\n}\n\n// GetUint8 returns the value associated with the key as an unsigned integer 8.\nfunc (c *Context) GetUint8(key any) uint8 {\n\treturn getTyped[uint8](c, key)\n}\n\n// GetUint16 returns the value associated with the key as an unsigned integer 16.\nfunc (c *Context) GetUint16(key any) uint16 {\n\treturn getTyped[uint16](c, key)\n}\n\n// GetUint32 returns the value associated with the key as an unsigned integer 32.\nfunc (c *Context) GetUint32(key any) uint32 {\n\treturn getTyped[uint32](c, key)\n}\n\n// GetUint64 returns the value associated with the key as an unsigned integer 64.\nfunc (c *Context) GetUint64(key any) uint64 {\n\treturn getTyped[uint64](c, key)\n}\n\n// GetFloat32 returns the value associated with the key as a float32.\nfunc (c *Context) GetFloat32(key any) float32 {\n\treturn getTyped[float32](c, key)\n}\n\n// GetFloat64 returns the value associated with the key as a float64.\nfunc (c *Context) GetFloat64(key any) float64 {\n\treturn getTyped[float64](c, key)\n}\n\n// GetTime returns the value associated with the key as time.\nfunc (c *Context) GetTime(key any) time.Time {\n\treturn getTyped[time.Time](c, key)\n}\n\n// GetDuration returns the value associated with the key as a duration.\nfunc (c *Context) GetDuration(key any) time.Duration {\n\treturn getTyped[time.Duration](c, key)\n}\n\n// GetError returns the value associated with the key as an error.\nfunc (c *Context) GetError(key any) error {\n\treturn getTyped[error](c, key)\n}\n\n// GetIntSlice returns the value associated with the key as a slice of integers.\nfunc (c *Context) GetIntSlice(key any) []int {\n\treturn getTyped[[]int](c, key)\n}\n\n// GetInt8Slice returns the value associated with the key as a slice of int8 integers.\nfunc (c *Context) GetInt8Slice(key any) []int8 {\n\treturn getTyped[[]int8](c, key)\n}\n\n// GetInt16Slice returns the value associated with the key as a slice of int16 integers.\nfunc (c *Context) GetInt16Slice(key any) []int16 {\n\treturn getTyped[[]int16](c, key)\n}\n\n// GetInt32Slice returns the value associated with the key as a slice of int32 integers.\nfunc (c *Context) GetInt32Slice(key any) []int32 {\n\treturn getTyped[[]int32](c, key)\n}\n\n// GetInt64Slice returns the value associated with the key as a slice of int64 integers.\nfunc (c *Context) GetInt64Slice(key any) []int64 {\n\treturn getTyped[[]int64](c, key)\n}\n\n// GetUintSlice returns the value associated with the key as a slice of unsigned integers.\nfunc (c *Context) GetUintSlice(key any) []uint {\n\treturn getTyped[[]uint](c, key)\n}\n\n// GetUint8Slice returns the value associated with the key as a slice of uint8 integers.\nfunc (c *Context) GetUint8Slice(key any) []uint8 {\n\treturn getTyped[[]uint8](c, key)\n}\n\n// GetUint16Slice returns the value associated with the key as a slice of uint16 integers.\nfunc (c *Context) GetUint16Slice(key any) []uint16 {\n\treturn getTyped[[]uint16](c, key)\n}\n\n// GetUint32Slice returns the value associated with the key as a slice of uint32 integers.\nfunc (c *Context) GetUint32Slice(key any) []uint32 {\n\treturn getTyped[[]uint32](c, key)\n}\n\n// GetUint64Slice returns the value associated with the key as a slice of uint64 integers.\nfunc (c *Context) GetUint64Slice(key any) []uint64 {\n\treturn getTyped[[]uint64](c, key)\n}\n\n// GetFloat32Slice returns the value associated with the key as a slice of float32 numbers.\nfunc (c *Context) GetFloat32Slice(key any) []float32 {\n\treturn getTyped[[]float32](c, key)\n}\n\n// GetFloat64Slice returns the value associated with the key as a slice of float64 numbers.\nfunc (c *Context) GetFloat64Slice(key any) []float64 {\n\treturn getTyped[[]float64](c, key)\n}\n\n// GetStringSlice returns the value associated with the key as a slice of strings.\nfunc (c *Context) GetStringSlice(key any) []string {\n\treturn getTyped[[]string](c, key)\n}\n\n// GetErrorSlice returns the value associated with the key as a slice of errors.\nfunc (c *Context) GetErrorSlice(key any) []error {\n\treturn getTyped[[]error](c, key)\n}\n\n// GetStringMap returns the value associated with the key as a map of interfaces.\nfunc (c *Context) GetStringMap(key any) map[string]any {\n\treturn getTyped[map[string]any](c, key)\n}\n\n// GetStringMapString returns the value associated with the key as a map of strings.\nfunc (c *Context) GetStringMapString(key any) map[string]string {\n\treturn getTyped[map[string]string](c, key)\n}\n\n// GetStringMapStringSlice returns the value associated with the key as a map to a slice of strings.\nfunc (c *Context) GetStringMapStringSlice(key any) map[string][]string {\n\treturn getTyped[map[string][]string](c, key)\n}\n\n// Delete deletes the key from the Context's Key map, if it exists.\n// This operation is safe to be used by concurrent go-routines\nfunc (c *Context) Delete(key any) {\n\tc.mu.Lock()\n\tdefer c.mu.Unlock()\n\tif c.Keys != nil {\n\t\tdelete(c.Keys, key)\n\t}\n}\n\n/************************************/\n/************ INPUT DATA ************/\n/************************************/\n\n// Param returns the value of the URL param.\n// It is a shortcut for c.Params.ByName(key)\n//\n//\trouter.GET(\"/user/:id\", func(c *gin.Context) {\n//\t    // a GET request to /user/john\n//\t    id := c.Param(\"id\") // id == \"john\"\n//\t    // a GET request to /user/john/\n//\t    id := c.Param(\"id\") // id == \"/john/\"\n//\t})\nfunc (c *Context) Param(key string) string {\n\treturn c.Params.ByName(key)\n}\n\n// AddParam adds param to context and\n// replaces path param key with given value for e2e testing purposes\n// Example Route: \"/user/:id\"\n// AddParam(\"id\", 1)\n// Result: \"/user/1\"\nfunc (c *Context) AddParam(key, value string) {\n\tc.Params = append(c.Params, Param{Key: key, Value: value})\n}\n\n// Query returns the keyed url query value if it exists,\n// otherwise it returns an empty string `(\"\")`.\n// It is shortcut for `c.Request.URL.Query().Get(key)`\n//\n//\t    GET /path?id=1234&name=Manu&value=\n//\t\t   c.Query(\"id\") == \"1234\"\n//\t\t   c.Query(\"name\") == \"Manu\"\n//\t\t   c.Query(\"value\") == \"\"\n//\t\t   c.Query(\"wtf\") == \"\"\nfunc (c *Context) Query(key string) (value string) {\n\tvalue, _ = c.GetQuery(key)\n\treturn\n}\n\n// DefaultQuery returns the keyed url query value if it exists,\n// otherwise it returns the specified defaultValue string.\n// See: Query() and GetQuery() for further information.\n//\n//\tGET /?name=Manu&lastname=\n//\tc.DefaultQuery(\"name\", \"unknown\") == \"Manu\"\n//\tc.DefaultQuery(\"id\", \"none\") == \"none\"\n//\tc.DefaultQuery(\"lastname\", \"none\") == \"\"\nfunc (c *Context) DefaultQuery(key, defaultValue string) string {\n\tif value, ok := c.GetQuery(key); ok {\n\t\treturn value\n\t}\n\treturn defaultValue\n}\n\n// GetQuery is like Query(), it returns the keyed url query value\n// if it exists `(value, true)` (even when the value is an empty string),\n// otherwise it returns `(\"\", false)`.\n// It is shortcut for `c.Request.URL.Query().Get(key)`\n//\n//\tGET /?name=Manu&lastname=\n//\t(\"Manu\", true) == c.GetQuery(\"name\")\n//\t(\"\", false) == c.GetQuery(\"id\")\n//\t(\"\", true) == c.GetQuery(\"lastname\")\nfunc (c *Context) GetQuery(key string) (string, bool) {\n\tif values, ok := c.GetQueryArray(key); ok {\n\t\treturn values[0], ok\n\t}\n\treturn \"\", false\n}\n\n// QueryArray returns a slice of strings for a given query key.\n// The length of the slice depends on the number of params with the given key.\nfunc (c *Context) QueryArray(key string) (values []string) {\n\tvalues, _ = c.GetQueryArray(key)\n\treturn\n}\n\nfunc (c *Context) initQueryCache() {\n\tif c.queryCache == nil {\n\t\tif c.Request != nil && c.Request.URL != nil {\n\t\t\tc.queryCache = c.Request.URL.Query()\n\t\t} else {\n\t\t\tc.queryCache = url.Values{}\n\t\t}\n\t}\n}\n\n// GetQueryArray returns a slice of strings for a given query key, plus\n// a boolean value whether at least one value exists for the given key.\nfunc (c *Context) GetQueryArray(key string) (values []string, ok bool) {\n\tc.initQueryCache()\n\tvalues, ok = c.queryCache[key]\n\treturn\n}\n\n// QueryMap returns a map for a given query key.\nfunc (c *Context) QueryMap(key string) (dicts map[string]string) {\n\tdicts, _ = c.GetQueryMap(key)\n\treturn\n}\n\n// GetQueryMap returns a map for a given query key, plus a boolean value\n// whether at least one value exists for the given key.\nfunc (c *Context) GetQueryMap(key string) (map[string]string, bool) {\n\tc.initQueryCache()\n\treturn getMapFromFormData(c.queryCache, key)\n}\n\n// PostForm returns the specified key from a POST urlencoded form or multipart form\n// when it exists, otherwise it returns an empty string `(\"\")`.\nfunc (c *Context) PostForm(key string) (value string) {\n\tvalue, _ = c.GetPostForm(key)\n\treturn\n}\n\n// DefaultPostForm returns the specified key from a POST urlencoded form or multipart form\n// when it exists, otherwise it returns the specified defaultValue string.\n// See: PostForm() and GetPostForm() for further information.\nfunc (c *Context) DefaultPostForm(key, defaultValue string) string {\n\tif value, ok := c.GetPostForm(key); ok {\n\t\treturn value\n\t}\n\treturn defaultValue\n}\n\n// GetPostForm is like PostForm(key). It returns the specified key from a POST urlencoded\n// form or multipart form when it exists `(value, true)` (even when the value is an empty string),\n// otherwise it returns (\"\", false).\n// For example, during a PATCH request to update the user's email:\n//\n//\t    email=mail@example.com  -->  (\"mail@example.com\", true) := GetPostForm(\"email\") // set email to \"mail@example.com\"\n//\t\t   email=                  -->  (\"\", true) := GetPostForm(\"email\") // set email to \"\"\n//\t                            -->  (\"\", false) := GetPostForm(\"email\") // do nothing with email\nfunc (c *Context) GetPostForm(key string) (string, bool) {\n\tif values, ok := c.GetPostFormArray(key); ok {\n\t\treturn values[0], ok\n\t}\n\treturn \"\", false\n}\n\n// PostFormArray returns a slice of strings for a given form key.\n// The length of the slice depends on the number of params with the given key.\nfunc (c *Context) PostFormArray(key string) (values []string) {\n\tvalues, _ = c.GetPostFormArray(key)\n\treturn\n}\n\nfunc (c *Context) initFormCache() {\n\tif c.formCache == nil {\n\t\tc.formCache = make(url.Values)\n\t\treq := c.Request\n\t\tif err := req.ParseMultipartForm(c.engine.MaxMultipartMemory); err != nil {\n\t\t\tif !errors.Is(err, http.ErrNotMultipart) {\n\t\t\t\tdebugPrint(\"error on parse multipart form array: %v\", err)\n\t\t\t}\n\t\t}\n\t\tc.formCache = req.PostForm\n\t}\n}\n\n// GetPostFormArray returns a slice of strings for a given form key, plus\n// a boolean value whether at least one value exists for the given key.\nfunc (c *Context) GetPostFormArray(key string) (values []string, ok bool) {\n\tc.initFormCache()\n\tvalues, ok = c.formCache[key]\n\treturn\n}\n\n// PostFormMap returns a map for a given form key.\nfunc (c *Context) PostFormMap(key string) (dicts map[string]string) {\n\tdicts, _ = c.GetPostFormMap(key)\n\treturn\n}\n\n// GetPostFormMap returns a map for a given form key, plus a boolean value\n// whether at least one value exists for the given key.\nfunc (c *Context) GetPostFormMap(key string) (map[string]string, bool) {\n\tc.initFormCache()\n\treturn getMapFromFormData(c.formCache, key)\n}\n\n// getMapFromFormData return a map which satisfies conditions.\n// It parses from data with bracket notation like \"key[subkey]=value\" into a map.\nfunc getMapFromFormData(m map[string][]string, key string) (map[string]string, bool) {\n\td := make(map[string]string)\n\tfound := false\n\tkeyLen := len(key)\n\n\tfor k, v := range m {\n\t\tif len(k) < keyLen+3 { // key + \"[\" + at least one char + \"]\"\n\t\t\tcontinue\n\t\t}\n\n\t\tif k[:keyLen] != key || k[keyLen] != '[' {\n\t\t\tcontinue\n\t\t}\n\n\t\tif j := strings.IndexByte(k[keyLen+1:], ']'); j > 0 {\n\t\t\tfound = true\n\t\t\td[k[keyLen+1:keyLen+1+j]] = v[0]\n\t\t}\n\t}\n\n\treturn d, found\n}\n\n// FormFile returns the first file for the provided form key.\nfunc (c *Context) FormFile(name string) (*multipart.FileHeader, error) {\n\tif c.Request.MultipartForm == nil {\n\t\tif err := c.Request.ParseMultipartForm(c.engine.MaxMultipartMemory); err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\tf, fh, err := c.Request.FormFile(name)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tf.Close()\n\treturn fh, err\n}\n\n// MultipartForm is the parsed multipart form, including file uploads.\nfunc (c *Context) MultipartForm() (*multipart.Form, error) {\n\terr := c.Request.ParseMultipartForm(c.engine.MaxMultipartMemory)\n\treturn c.Request.MultipartForm, err\n}\n\n// SaveUploadedFile uploads the form file to specific dst.\nfunc (c *Context) SaveUploadedFile(file *multipart.FileHeader, dst string, perm ...fs.FileMode) error {\n\tsrc, err := file.Open()\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer src.Close()\n\n\tvar mode os.FileMode = 0o750\n\tif len(perm) > 0 {\n\t\tmode = perm[0]\n\t}\n\tdir := filepath.Dir(dst)\n\tif err = os.MkdirAll(dir, mode); err != nil {\n\t\treturn err\n\t}\n\tif err = os.Chmod(dir, mode); err != nil {\n\t\treturn err\n\t}\n\n\tout, err := os.Create(dst)\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer out.Close()\n\n\t_, err = io.Copy(out, src)\n\treturn err\n}\n\n// Bind checks the Method and Content-Type to select a binding engine automatically,\n// Depending on the \"Content-Type\" header different bindings are used, for example:\n//\n//\t\"application/json\" --> JSON binding\n//\t\"application/xml\"  --> XML binding\n//\n// It parses the request's body based on the Content-Type (e.g., JSON or XML).\n// It decodes the payload into the struct specified as a pointer.\n// It writes a 400 error and sets Content-Type header \"text/plain\" in the response if input is not valid.\nfunc (c *Context) Bind(obj any) error {\n\tb := binding.Default(c.Request.Method, c.ContentType())\n\treturn c.MustBindWith(obj, b)\n}\n\n// BindJSON is a shortcut for c.MustBindWith(obj, binding.JSON).\nfunc (c *Context) BindJSON(obj any) error {\n\treturn c.MustBindWith(obj, binding.JSON)\n}\n\n// BindXML is a shortcut for c.MustBindWith(obj, binding.BindXML).\nfunc (c *Context) BindXML(obj any) error {\n\treturn c.MustBindWith(obj, binding.XML)\n}\n\n// BindQuery is a shortcut for c.MustBindWith(obj, binding.Query).\nfunc (c *Context) BindQuery(obj any) error {\n\treturn c.MustBindWith(obj, binding.Query)\n}\n\n// BindYAML is a shortcut for c.MustBindWith(obj, binding.YAML).\nfunc (c *Context) BindYAML(obj any) error {\n\treturn c.MustBindWith(obj, binding.YAML)\n}\n\n// BindTOML is a shortcut for c.MustBindWith(obj, binding.TOML).\nfunc (c *Context) BindTOML(obj any) error {\n\treturn c.MustBindWith(obj, binding.TOML)\n}\n\n// BindPlain is a shortcut for c.MustBindWith(obj, binding.Plain).\nfunc (c *Context) BindPlain(obj any) error {\n\treturn c.MustBindWith(obj, binding.Plain)\n}\n\n// BindHeader is a shortcut for c.MustBindWith(obj, binding.Header).\nfunc (c *Context) BindHeader(obj any) error {\n\treturn c.MustBindWith(obj, binding.Header)\n}\n\n// BindUri binds the passed struct pointer using binding.Uri.\n// It will abort the request with HTTP 400 if any error occurs.\nfunc (c *Context) BindUri(obj any) error {\n\tif err := c.ShouldBindUri(obj); err != nil {\n\t\tc.AbortWithError(http.StatusBadRequest, err).SetType(ErrorTypeBind) //nolint: errcheck\n\t\treturn err\n\t}\n\treturn nil\n}\n\n// MustBindWith binds the passed struct pointer using the specified binding engine.\n// It will abort the request with HTTP 400 if any error occurs.\n// See the binding package.\nfunc (c *Context) MustBindWith(obj any, b binding.Binding) error {\n\terr := c.ShouldBindWith(obj, b)\n\tif err != nil {\n\t\tvar maxBytesErr *http.MaxBytesError\n\n\t\t// Note: When using sonic or go-json as JSON encoder, they do not propagate the http.MaxBytesError error\n\t\t// https://github.com/goccy/go-json/issues/485\n\t\t// https://github.com/bytedance/sonic/issues/800\n\t\tswitch {\n\t\tcase errors.As(err, &maxBytesErr):\n\t\t\tc.AbortWithError(http.StatusRequestEntityTooLarge, err).SetType(ErrorTypeBind) //nolint: errcheck\n\t\tdefault:\n\t\t\tc.AbortWithError(http.StatusBadRequest, err).SetType(ErrorTypeBind) //nolint: errcheck\n\t\t}\n\t\treturn err\n\t}\n\treturn nil\n}\n\n// ShouldBind checks the Method and Content-Type to select a binding engine automatically,\n// Depending on the \"Content-Type\" header different bindings are used, for example:\n//\n//\t\"application/json\" --> JSON binding\n//\t\"application/xml\"  --> XML binding\n//\n// It parses the request's body based on the Content-Type (e.g., JSON or XML).\n// It decodes the payload into the struct specified as a pointer.\n// Like c.Bind() but this method does not set the response status code to 400 or abort if input is not valid.\nfunc (c *Context) ShouldBind(obj any) error {\n\tb := binding.Default(c.Request.Method, c.ContentType())\n\treturn c.ShouldBindWith(obj, b)\n}\n\n// ShouldBindJSON is a shortcut for c.ShouldBindWith(obj, binding.JSON).\n//\n// Example:\n//\n//\tPOST /user\n//\tContent-Type: application/json\n//\n//\tRequest Body:\n//\t{\n//\t\t\"name\": \"Manu\",\n//\t\t\"age\": 20\n//\t}\n//\n//\ttype User struct {\n//\t\tName string `json:\"name\"`\n//\t\tAge  int    `json:\"age\"`\n//\t}\n//\n//\tvar user User\n//\tif err := c.ShouldBindJSON(&user); err != nil {\n//\t\tc.JSON(http.StatusBadRequest, gin.H{\"error\": err.Error()})\n//\t\treturn\n//\t}\n//\tc.JSON(http.StatusOK, user)\nfunc (c *Context) ShouldBindJSON(obj any) error {\n\treturn c.ShouldBindWith(obj, binding.JSON)\n}\n\n// ShouldBindXML is a shortcut for c.ShouldBindWith(obj, binding.XML).\n// It works like ShouldBindJSON but binds the request body as XML data.\nfunc (c *Context) ShouldBindXML(obj any) error {\n\treturn c.ShouldBindWith(obj, binding.XML)\n}\n\n// ShouldBindQuery is a shortcut for c.ShouldBindWith(obj, binding.Query).\n// It works like ShouldBindJSON but binds query parameters from the URL.\nfunc (c *Context) ShouldBindQuery(obj any) error {\n\treturn c.ShouldBindWith(obj, binding.Query)\n}\n\n// ShouldBindYAML is a shortcut for c.ShouldBindWith(obj, binding.YAML).\n// It works like ShouldBindJSON but binds the request body as YAML data.\nfunc (c *Context) ShouldBindYAML(obj any) error {\n\treturn c.ShouldBindWith(obj, binding.YAML)\n}\n\n// ShouldBindTOML is a shortcut for c.ShouldBindWith(obj, binding.TOML).\n// It works like ShouldBindJSON but binds the request body as TOML data.\nfunc (c *Context) ShouldBindTOML(obj any) error {\n\treturn c.ShouldBindWith(obj, binding.TOML)\n}\n\n// ShouldBindPlain is a shortcut for c.ShouldBindWith(obj, binding.Plain).\n// It works like ShouldBindJSON but binds plain text data from the request body.\nfunc (c *Context) ShouldBindPlain(obj any) error {\n\treturn c.ShouldBindWith(obj, binding.Plain)\n}\n\n// ShouldBindHeader is a shortcut for c.ShouldBindWith(obj, binding.Header).\n// It works like ShouldBindJSON but binds values from HTTP headers.\nfunc (c *Context) ShouldBindHeader(obj any) error {\n\treturn c.ShouldBindWith(obj, binding.Header)\n}\n\n// ShouldBindUri binds the passed struct pointer using the specified binding engine.\n// It works like ShouldBindJSON but binds parameters from the URI.\nfunc (c *Context) ShouldBindUri(obj any) error {\n\tm := make(map[string][]string, len(c.Params))\n\tfor _, v := range c.Params {\n\t\tm[v.Key] = []string{v.Value}\n\t}\n\treturn binding.Uri.BindUri(m, obj)\n}\n\n// ShouldBindWith binds the passed struct pointer using the specified binding engine.\n// See the binding package.\nfunc (c *Context) ShouldBindWith(obj any, b binding.Binding) error {\n\treturn b.Bind(c.Request, obj)\n}\n\n// ShouldBindBodyWith is similar with ShouldBindWith, but it stores the request\n// body into the context, and reuse when it is called again.\n//\n// NOTE: This method reads the body before binding. So you should use\n// ShouldBindWith for better performance if you need to call only once.\nfunc (c *Context) ShouldBindBodyWith(obj any, bb binding.BindingBody) (err error) {\n\tvar body []byte\n\tif cb, ok := c.Get(BodyBytesKey); ok {\n\t\tif cbb, ok := cb.([]byte); ok {\n\t\t\tbody = cbb\n\t\t}\n\t}\n\tif body == nil {\n\t\tbody, err = io.ReadAll(c.Request.Body)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tc.Set(BodyBytesKey, body)\n\t}\n\treturn bb.BindBody(body, obj)\n}\n\n// ShouldBindBodyWithJSON is a shortcut for c.ShouldBindBodyWith(obj, binding.JSON).\nfunc (c *Context) ShouldBindBodyWithJSON(obj any) error {\n\treturn c.ShouldBindBodyWith(obj, binding.JSON)\n}\n\n// ShouldBindBodyWithXML is a shortcut for c.ShouldBindBodyWith(obj, binding.XML).\nfunc (c *Context) ShouldBindBodyWithXML(obj any) error {\n\treturn c.ShouldBindBodyWith(obj, binding.XML)\n}\n\n// ShouldBindBodyWithYAML is a shortcut for c.ShouldBindBodyWith(obj, binding.YAML).\nfunc (c *Context) ShouldBindBodyWithYAML(obj any) error {\n\treturn c.ShouldBindBodyWith(obj, binding.YAML)\n}\n\n// ShouldBindBodyWithTOML is a shortcut for c.ShouldBindBodyWith(obj, binding.TOML).\nfunc (c *Context) ShouldBindBodyWithTOML(obj any) error {\n\treturn c.ShouldBindBodyWith(obj, binding.TOML)\n}\n\n// ShouldBindBodyWithPlain is a shortcut for c.ShouldBindBodyWith(obj, binding.Plain).\nfunc (c *Context) ShouldBindBodyWithPlain(obj any) error {\n\treturn c.ShouldBindBodyWith(obj, binding.Plain)\n}\n\n// ClientIP implements one best effort algorithm to return the real client IP.\n// It calls c.RemoteIP() under the hood, to check if the remote IP is a trusted proxy or not.\n// If it is it will then try to parse the headers defined in Engine.RemoteIPHeaders (defaulting to [X-Forwarded-For, X-Real-IP]).\n// If the headers are not syntactically valid OR the remote IP does not correspond to a trusted proxy,\n// the remote IP (coming from Request.RemoteAddr) is returned.\nfunc (c *Context) ClientIP() string {\n\t// Check if we're running on a trusted platform, continue running backwards if error\n\tif c.engine.TrustedPlatform != \"\" {\n\t\t// Developers can define their own header of Trusted Platform or use predefined constants\n\t\tif addr := c.requestHeader(c.engine.TrustedPlatform); addr != \"\" {\n\t\t\treturn addr\n\t\t}\n\t}\n\n\t// Legacy \"AppEngine\" flag\n\tif c.engine.AppEngine {\n\t\tlog.Println(`The AppEngine flag is going to be deprecated. Please check issues #2723 and #2739 and use 'TrustedPlatform: gin.PlatformGoogleAppEngine' instead.`)\n\t\tif addr := c.requestHeader(\"X-Appengine-Remote-Addr\"); addr != \"\" {\n\t\t\treturn addr\n\t\t}\n\t}\n\n\tvar (\n\t\ttrusted  bool\n\t\tremoteIP net.IP\n\t)\n\t// If gin is listening a unix socket, always trust it.\n\tlocalAddr, ok := c.Request.Context().Value(http.LocalAddrContextKey).(net.Addr)\n\tif ok && strings.HasPrefix(localAddr.Network(), \"unix\") {\n\t\ttrusted = true\n\t}\n\n\t// Fallback\n\tif !trusted {\n\t\t// It also checks if the remoteIP is a trusted proxy or not.\n\t\t// In order to perform this validation, it will see if the IP is contained within at least one of the CIDR blocks\n\t\t// defined by Engine.SetTrustedProxies()\n\t\tremoteIP = net.ParseIP(c.RemoteIP())\n\t\tif remoteIP == nil {\n\t\t\treturn \"\"\n\t\t}\n\t\ttrusted = c.engine.isTrustedProxy(remoteIP)\n\t}\n\n\tif trusted && c.engine.ForwardedByClientIP && c.engine.RemoteIPHeaders != nil {\n\t\tfor _, headerName := range c.engine.RemoteIPHeaders {\n\t\t\theaderValue := strings.Join(c.Request.Header.Values(headerName), \",\")\n\t\t\tip, valid := c.engine.validateHeader(headerValue)\n\t\t\tif valid {\n\t\t\t\treturn ip\n\t\t\t}\n\t\t}\n\t}\n\treturn remoteIP.String()\n}\n\n// RemoteIP parses the IP from Request.RemoteAddr, normalizes and returns the IP (without the port).\nfunc (c *Context) RemoteIP() string {\n\tip, _, err := net.SplitHostPort(strings.TrimSpace(c.Request.RemoteAddr))\n\tif err != nil {\n\t\treturn \"\"\n\t}\n\treturn ip\n}\n\n// ContentType returns the Content-Type header of the request.\nfunc (c *Context) ContentType() string {\n\treturn filterFlags(c.requestHeader(\"Content-Type\"))\n}\n\n// IsWebsocket returns true if the request headers indicate that a websocket\n// handshake is being initiated by the client.\nfunc (c *Context) IsWebsocket() bool {\n\tif strings.Contains(strings.ToLower(c.requestHeader(\"Connection\")), \"upgrade\") &&\n\t\tstrings.EqualFold(c.requestHeader(\"Upgrade\"), \"websocket\") {\n\t\treturn true\n\t}\n\treturn false\n}\n\nfunc (c *Context) requestHeader(key string) string {\n\treturn c.Request.Header.Get(key)\n}\n\n/************************************/\n/******** RESPONSE RENDERING ********/\n/************************************/\n\n// bodyAllowedForStatus is a copy of http.bodyAllowedForStatus non-exported function.\n// Uses http.StatusContinue constant for better code clarity.\nfunc bodyAllowedForStatus(status int) bool {\n\tswitch {\n\tcase status >= http.StatusContinue && status < http.StatusOK:\n\t\treturn false\n\tcase status == http.StatusNoContent:\n\t\treturn false\n\tcase status == http.StatusNotModified:\n\t\treturn false\n\t}\n\treturn true\n}\n\n// Status sets the HTTP response code.\nfunc (c *Context) Status(code int) {\n\tc.Writer.WriteHeader(code)\n}\n\n// Header is an intelligent shortcut for c.Writer.Header().Set(key, value).\n// It writes a header in the response.\n// If value == \"\", this method removes the header `c.Writer.Header().Del(key)`\nfunc (c *Context) Header(key, value string) {\n\tif value == \"\" {\n\t\tc.Writer.Header().Del(key)\n\t\treturn\n\t}\n\tc.Writer.Header().Set(key, value)\n}\n\n// GetHeader returns value from request headers.\nfunc (c *Context) GetHeader(key string) string {\n\treturn c.requestHeader(key)\n}\n\n// GetRawData returns stream data.\nfunc (c *Context) GetRawData() ([]byte, error) {\n\tif c.Request.Body == nil {\n\t\treturn nil, errors.New(\"cannot read nil body\")\n\t}\n\treturn io.ReadAll(c.Request.Body)\n}\n\n// SetSameSite with cookie\nfunc (c *Context) SetSameSite(samesite http.SameSite) {\n\tc.sameSite = samesite\n}\n\n// SetCookie adds a Set-Cookie header to the ResponseWriter's headers.\n// The provided cookie must have a valid Name. Invalid cookies may be\n// silently dropped.\nfunc (c *Context) SetCookie(name, value string, maxAge int, path, domain string, secure, httpOnly bool) {\n\tif path == \"\" {\n\t\tpath = \"/\"\n\t}\n\thttp.SetCookie(c.Writer, &http.Cookie{\n\t\tName:     name,\n\t\tValue:    url.QueryEscape(value),\n\t\tMaxAge:   maxAge,\n\t\tPath:     path,\n\t\tDomain:   domain,\n\t\tSameSite: c.sameSite,\n\t\tSecure:   secure,\n\t\tHttpOnly: httpOnly,\n\t})\n}\n\n// SetCookieData adds a Set-Cookie header to the ResponseWriter's headers.\n// It accepts a pointer to http.Cookie structure for more flexibility in setting cookie attributes.\n// The provided cookie must have a valid Name. Invalid cookies may be silently dropped.\nfunc (c *Context) SetCookieData(cookie *http.Cookie) {\n\tif cookie.Path == \"\" {\n\t\tcookie.Path = \"/\"\n\t}\n\tif cookie.SameSite == http.SameSiteDefaultMode {\n\t\tcookie.SameSite = c.sameSite\n\t}\n\thttp.SetCookie(c.Writer, cookie)\n}\n\n// Cookie returns the named cookie provided in the request or\n// ErrNoCookie if not found. And return the named cookie is unescaped.\n// If multiple cookies match the given name, only one cookie will\n// be returned.\nfunc (c *Context) Cookie(name string) (string, error) {\n\tcookie, err := c.Request.Cookie(name)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\tval, _ := url.QueryUnescape(cookie.Value)\n\treturn val, nil\n}\n\n// Render writes the response headers and calls render.Render to render data.\nfunc (c *Context) Render(code int, r render.Render) {\n\tc.Status(code)\n\n\tif !bodyAllowedForStatus(code) {\n\t\tr.WriteContentType(c.Writer)\n\t\tc.Writer.WriteHeaderNow()\n\t\treturn\n\t}\n\n\tif err := r.Render(c.Writer); err != nil {\n\t\t// Pushing error to c.Errors\n\t\t_ = c.Error(err)\n\t\tc.Abort()\n\t}\n}\n\n// HTML renders the HTTP template specified by its file name.\n// It also updates the HTTP code and sets the Content-Type as \"text/html\".\n// See http://golang.org/doc/articles/wiki/\nfunc (c *Context) HTML(code int, name string, obj any) {\n\tinstance := c.engine.HTMLRender.Instance(name, obj)\n\tc.Render(code, instance)\n}\n\n// IndentedJSON serializes the given struct as pretty JSON (indented + endlines) into the response body.\n// It also sets the Content-Type as \"application/json\".\n// WARNING: we recommend using this only for development purposes since printing pretty JSON is\n// more CPU and bandwidth consuming. Use Context.JSON() instead.\nfunc (c *Context) IndentedJSON(code int, obj any) {\n\tc.Render(code, render.IndentedJSON{Data: obj})\n}\n\n// SecureJSON serializes the given struct as Secure JSON into the response body.\n// Default prepends \"while(1),\" to response body if the given struct is array values.\n// It also sets the Content-Type as \"application/json\".\nfunc (c *Context) SecureJSON(code int, obj any) {\n\tc.Render(code, render.SecureJSON{Prefix: c.engine.secureJSONPrefix, Data: obj})\n}\n\n// JSONP serializes the given struct as JSON into the response body.\n// It adds padding to response body to request data from a server residing in a different domain than the client.\n// It also sets the Content-Type as \"application/javascript\".\nfunc (c *Context) JSONP(code int, obj any) {\n\tcallback := c.DefaultQuery(\"callback\", \"\")\n\tif callback == \"\" {\n\t\tc.Render(code, render.JSON{Data: obj})\n\t\treturn\n\t}\n\tc.Render(code, render.JsonpJSON{Callback: callback, Data: obj})\n}\n\n// JSON serializes the given struct as JSON into the response body.\n// It also sets the Content-Type as \"application/json\".\nfunc (c *Context) JSON(code int, obj any) {\n\tc.Render(code, render.JSON{Data: obj})\n}\n\n// AsciiJSON serializes the given struct as JSON into the response body with unicode to ASCII string.\n// It also sets the Content-Type as \"application/json\".\nfunc (c *Context) AsciiJSON(code int, obj any) {\n\tc.Render(code, render.AsciiJSON{Data: obj})\n}\n\n// PureJSON serializes the given struct as JSON into the response body.\n// PureJSON, unlike JSON, does not replace special html characters with their unicode entities.\nfunc (c *Context) PureJSON(code int, obj any) {\n\tc.Render(code, render.PureJSON{Data: obj})\n}\n\n// XML serializes the given struct as XML into the response body.\n// It also sets the Content-Type as \"application/xml\".\nfunc (c *Context) XML(code int, obj any) {\n\tc.Render(code, render.XML{Data: obj})\n}\n\n// PDF writes the given PDF binary data into the response body.\n// It also sets the Content-Type as \"application/pdf\".\nfunc (c *Context) PDF(code int, data []byte) {\n\tc.Render(code, render.PDF{Data: data})\n}\n\n// YAML serializes the given struct as YAML into the response body.\nfunc (c *Context) YAML(code int, obj any) {\n\tc.Render(code, render.YAML{Data: obj})\n}\n\n// TOML serializes the given struct as TOML into the response body.\nfunc (c *Context) TOML(code int, obj any) {\n\tc.Render(code, render.TOML{Data: obj})\n}\n\n// ProtoBuf serializes the given struct as ProtoBuf into the response body.\nfunc (c *Context) ProtoBuf(code int, obj any) {\n\tc.Render(code, render.ProtoBuf{Data: obj})\n}\n\n// BSON serializes the given struct as BSON into the response body.\nfunc (c *Context) BSON(code int, obj any) {\n\tc.Render(code, render.BSON{Data: obj})\n}\n\n// String writes the given string into the response body.\nfunc (c *Context) String(code int, format string, values ...any) {\n\tc.Render(code, render.String{Format: format, Data: values})\n}\n\n// Redirect returns an HTTP redirect to the specific location.\nfunc (c *Context) Redirect(code int, location string) {\n\tc.Render(-1, render.Redirect{\n\t\tCode:     code,\n\t\tLocation: location,\n\t\tRequest:  c.Request,\n\t})\n}\n\n// Data writes some data into the body stream and updates the HTTP code.\nfunc (c *Context) Data(code int, contentType string, data []byte) {\n\tc.Render(code, render.Data{\n\t\tContentType: contentType,\n\t\tData:        data,\n\t})\n}\n\n// DataFromReader writes the specified reader into the body stream and updates the HTTP code.\nfunc (c *Context) DataFromReader(code int, contentLength int64, contentType string, reader io.Reader, extraHeaders map[string]string) {\n\tc.Render(code, render.Reader{\n\t\tHeaders:       extraHeaders,\n\t\tContentType:   contentType,\n\t\tContentLength: contentLength,\n\t\tReader:        reader,\n\t})\n}\n\n// File writes the specified file into the body stream in an efficient way.\nfunc (c *Context) File(filepath string) {\n\thttp.ServeFile(c.Writer, c.Request, filepath)\n}\n\n// FileFromFS writes the specified file from http.FileSystem into the body stream in an efficient way.\nfunc (c *Context) FileFromFS(filepath string, fs http.FileSystem) {\n\tdefer func(old string) {\n\t\tc.Request.URL.Path = old\n\t}(c.Request.URL.Path)\n\n\tc.Request.URL.Path = filepath\n\n\thttp.FileServer(fs).ServeHTTP(c.Writer, c.Request)\n}\n\nvar quoteEscaper = strings.NewReplacer(\"\\\\\", \"\\\\\\\\\", `\"`, \"\\\\\\\"\")\n\nfunc escapeQuotes(s string) string {\n\treturn quoteEscaper.Replace(s)\n}\n\n// FileAttachment writes the specified file into the body stream in an efficient way\n// On the client side, the file will typically be downloaded with the given filename\nfunc (c *Context) FileAttachment(filepath, filename string) {\n\tif isASCII(filename) {\n\t\tc.Writer.Header().Set(\"Content-Disposition\", `attachment; filename=\"`+escapeQuotes(filename)+`\"`)\n\t} else {\n\t\tc.Writer.Header().Set(\"Content-Disposition\", `attachment; filename*=UTF-8''`+url.QueryEscape(filename))\n\t}\n\thttp.ServeFile(c.Writer, c.Request, filepath)\n}\n\n// SSEvent writes a Server-Sent Event into the body stream.\nfunc (c *Context) SSEvent(name string, message any) {\n\tc.Render(-1, sse.Event{\n\t\tEvent: name,\n\t\tData:  message,\n\t})\n}\n\n// Stream sends a streaming response and returns a boolean\n// indicates \"Is client disconnected in middle of stream\"\nfunc (c *Context) Stream(step func(w io.Writer) bool) bool {\n\tw := c.Writer\n\tclientGone := w.CloseNotify()\n\tfor {\n\t\tselect {\n\t\tcase <-clientGone:\n\t\t\treturn true\n\t\tdefault:\n\t\t\tkeepOpen := step(w)\n\t\t\tw.Flush()\n\t\t\tif !keepOpen {\n\t\t\t\treturn false\n\t\t\t}\n\t\t}\n\t}\n}\n\n/************************************/\n/******** CONTENT NEGOTIATION *******/\n/************************************/\n\n// Negotiate contains all negotiations data.\ntype Negotiate struct {\n\tOffered      []string\n\tHTMLName     string\n\tHTMLData     any\n\tJSONData     any\n\tXMLData      any\n\tYAMLData     any\n\tData         any\n\tTOMLData     any\n\tPROTOBUFData any\n\tBSONData     any\n}\n\n// Negotiate calls different Render according to acceptable Accept format.\nfunc (c *Context) Negotiate(code int, config Negotiate) {\n\tswitch c.NegotiateFormat(config.Offered...) {\n\tcase binding.MIMEJSON:\n\t\tdata := chooseData(config.JSONData, config.Data)\n\t\tc.JSON(code, data)\n\n\tcase binding.MIMEHTML:\n\t\tdata := chooseData(config.HTMLData, config.Data)\n\t\tc.HTML(code, config.HTMLName, data)\n\n\tcase binding.MIMEXML:\n\t\tdata := chooseData(config.XMLData, config.Data)\n\t\tc.XML(code, data)\n\n\tcase binding.MIMEYAML, binding.MIMEYAML2:\n\t\tdata := chooseData(config.YAMLData, config.Data)\n\t\tc.YAML(code, data)\n\n\tcase binding.MIMETOML:\n\t\tdata := chooseData(config.TOMLData, config.Data)\n\t\tc.TOML(code, data)\n\n\tcase binding.MIMEPROTOBUF:\n\t\tdata := chooseData(config.PROTOBUFData, config.Data)\n\t\tc.ProtoBuf(code, data)\n\n\tcase binding.MIMEBSON:\n\t\tdata := chooseData(config.BSONData, config.Data)\n\t\tc.BSON(code, data)\n\n\tdefault:\n\t\tc.AbortWithError(http.StatusNotAcceptable, errors.New(\"the accepted formats are not offered by the server\")) //nolint: errcheck\n\t}\n}\n\n// NegotiateFormat returns an acceptable Accept format.\nfunc (c *Context) NegotiateFormat(offered ...string) string {\n\tassert1(len(offered) > 0, \"you must provide at least one offer\")\n\n\tif c.Accepted == nil {\n\t\tc.Accepted = parseAccept(c.requestHeader(\"Accept\"))\n\t}\n\tif len(c.Accepted) == 0 {\n\t\treturn offered[0]\n\t}\n\tfor _, accepted := range c.Accepted {\n\t\tfor _, offer := range offered {\n\t\t\t// According to RFC 2616 and RFC 2396, non-ASCII characters are not allowed in headers,\n\t\t\t// therefore we can just iterate over the string without casting it into []rune\n\t\t\ti := 0\n\t\t\tfor ; i < len(accepted) && i < len(offer); i++ {\n\t\t\t\tif accepted[i] == '*' || offer[i] == '*' {\n\t\t\t\t\treturn offer\n\t\t\t\t}\n\t\t\t\tif accepted[i] != offer[i] {\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t\tif i == len(accepted) {\n\t\t\t\treturn offer\n\t\t\t}\n\t\t}\n\t}\n\treturn \"\"\n}\n\n// SetAccepted sets Accept header data.\nfunc (c *Context) SetAccepted(formats ...string) {\n\tc.Accepted = formats\n}\n\n/************************************/\n/***** GOLANG.ORG/X/NET/CONTEXT *****/\n/************************************/\n\n// hasRequestContext returns whether c.Request has Context and fallback.\nfunc (c *Context) hasRequestContext() bool {\n\thasFallback := c.engine != nil && c.engine.ContextWithFallback\n\thasRequestContext := c.Request != nil && c.Request.Context() != nil\n\treturn hasFallback && hasRequestContext\n}\n\n// Deadline returns that there is no deadline (ok==false) when c.Request has no Context.\nfunc (c *Context) Deadline() (deadline time.Time, ok bool) {\n\tif !c.hasRequestContext() {\n\t\treturn\n\t}\n\treturn c.Request.Context().Deadline()\n}\n\n// Done returns nil (chan which will wait forever) when c.Request has no Context.\nfunc (c *Context) Done() <-chan struct{} {\n\tif !c.hasRequestContext() {\n\t\treturn nil\n\t}\n\treturn c.Request.Context().Done()\n}\n\n// Err returns nil when c.Request has no Context.\nfunc (c *Context) Err() error {\n\tif !c.hasRequestContext() {\n\t\treturn nil\n\t}\n\treturn c.Request.Context().Err()\n}\n\n// Value returns the value associated with this context for key, or nil\n// if no value is associated with key. Successive calls to Value with\n// the same key returns the same result.\nfunc (c *Context) Value(key any) any {\n\tif key == ContextRequestKey {\n\t\treturn c.Request\n\t}\n\tif key == ContextKey {\n\t\treturn c\n\t}\n\tif keyAsString, ok := key.(string); ok {\n\t\tif val, exists := c.Get(keyAsString); exists {\n\t\t\treturn val\n\t\t}\n\t}\n\tif !c.hasRequestContext() {\n\t\treturn nil\n\t}\n\treturn c.Request.Context().Value(key)\n}\n"
  },
  {
    "path": "context_appengine.go",
    "content": "// Copyright 2017 Manu Martinez-Almeida. All rights reserved.\n// Use of this source code is governed by a MIT style\n// license that can be found in the LICENSE file.\n\n//go:build appengine\n\npackage gin\n\nfunc init() {\n\tdefaultPlatform = PlatformGoogleAppEngine\n}\n"
  },
  {
    "path": "context_file_test.go",
    "content": "package gin\n\nimport (\n\t\"net/http\"\n\t\"net/http/httptest\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n)\n\n// TestContextFileSimple tests the Context.File() method with a simple case\nfunc TestContextFileSimple(t *testing.T) {\n\t// Test serving an existing file\n\ttestFile := \"testdata/test_file.txt\"\n\tw := httptest.NewRecorder()\n\tc, _ := CreateTestContext(w)\n\tc.Request = httptest.NewRequest(http.MethodGet, \"/test\", nil)\n\n\tc.File(testFile)\n\n\tassert.Equal(t, http.StatusOK, w.Code)\n\tassert.Contains(t, w.Body.String(), \"This is a test file\")\n\tassert.Equal(t, \"text/plain; charset=utf-8\", w.Header().Get(\"Content-Type\"))\n}\n\n// TestContextFileNotFound tests serving a non-existent file\nfunc TestContextFileNotFound(t *testing.T) {\n\tw := httptest.NewRecorder()\n\tc, _ := CreateTestContext(w)\n\tc.Request = httptest.NewRequest(http.MethodGet, \"/test\", nil)\n\n\tc.File(\"non_existent_file.txt\")\n\n\tassert.Equal(t, http.StatusNotFound, w.Code)\n}\n"
  },
  {
    "path": "context_test.go",
    "content": "// Copyright 2014 Manu Martinez-Almeida. All rights reserved.\n// Use of this source code is governed by a MIT style\n// license that can be found in the LICENSE file.\n\npackage gin\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"html/template\"\n\t\"io\"\n\t\"io/fs\"\n\t\"mime/multipart\"\n\t\"net\"\n\t\"net/http\"\n\t\"net/http/httptest\"\n\t\"net/url\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"reflect\"\n\t\"strconv\"\n\t\"strings\"\n\t\"sync\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/gin-contrib/sse\"\n\t\"github.com/gin-gonic/gin/binding\"\n\t\"github.com/gin-gonic/gin/codec/json\"\n\ttestdata \"github.com/gin-gonic/gin/testdata/protoexample\"\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n\t\"go.mongodb.org/mongo-driver/v2/bson\"\n\t\"google.golang.org/protobuf/proto\"\n)\n\nvar _ context.Context = (*Context)(nil)\n\nvar errTestRender = errors.New(\"TestRender\")\n\n// Unit tests TODO\n// func (c *Context) File(filepath string) {\n// func (c *Context) Negotiate(code int, config Negotiate) {\n// BAD case: func (c *Context) Render(code int, render render.Render, obj ...any) {\n// test that information is not leaked when reusing Contexts (using the Pool)\n\nfunc createMultipartRequest() *http.Request {\n\tboundary := \"--testboundary\"\n\tbody := new(bytes.Buffer)\n\tmw := multipart.NewWriter(body)\n\tdefer mw.Close()\n\n\tmust(mw.SetBoundary(boundary))\n\tmust(mw.WriteField(\"foo\", \"bar\"))\n\tmust(mw.WriteField(\"bar\", \"10\"))\n\tmust(mw.WriteField(\"bar\", \"foo2\"))\n\tmust(mw.WriteField(\"array\", \"first\"))\n\tmust(mw.WriteField(\"array\", \"second\"))\n\tmust(mw.WriteField(\"id\", \"\"))\n\tmust(mw.WriteField(\"time_local\", \"31/12/2016 14:55\"))\n\tmust(mw.WriteField(\"time_utc\", \"31/12/2016 14:55\"))\n\tmust(mw.WriteField(\"time_location\", \"31/12/2016 14:55\"))\n\tmust(mw.WriteField(\"names[a]\", \"thinkerou\"))\n\tmust(mw.WriteField(\"names[b]\", \"tianou\"))\n\treq, err := http.NewRequest(http.MethodPost, \"/\", body)\n\tmust(err)\n\treq.Header.Set(\"Content-Type\", MIMEMultipartPOSTForm+\"; boundary=\"+boundary)\n\treturn req\n}\n\nfunc must(err error) {\n\tif err != nil {\n\t\tpanic(err.Error())\n\t}\n}\n\n// TestContextFile tests the Context.File() method\nfunc TestContextFile(t *testing.T) {\n\t// Test serving an existing file\n\tt.Run(\"serve existing file\", func(t *testing.T) {\n\t\t// Create a temporary test file\n\t\ttestFile := \"testdata/test_file.txt\"\n\n\t\tw := httptest.NewRecorder()\n\t\tc, _ := CreateTestContext(w)\n\t\tc.Request = httptest.NewRequest(http.MethodGet, \"/test\", nil)\n\n\t\tc.File(testFile)\n\n\t\tassert.Equal(t, http.StatusOK, w.Code)\n\t\tassert.Contains(t, w.Body.String(), \"This is a test file\")\n\t\tassert.Equal(t, \"text/plain; charset=utf-8\", w.Header().Get(\"Content-Type\"))\n\t})\n\n\t// Test serving a non-existent file\n\tt.Run(\"serve non-existent file\", func(t *testing.T) {\n\t\tw := httptest.NewRecorder()\n\t\tc, _ := CreateTestContext(w)\n\t\tc.Request = httptest.NewRequest(http.MethodGet, \"/test\", nil)\n\n\t\tc.File(\"non_existent_file.txt\")\n\n\t\tassert.Equal(t, http.StatusNotFound, w.Code)\n\t})\n\n\t// Test serving a directory (should return 200 with directory listing or 403 Forbidden)\n\tt.Run(\"serve directory\", func(t *testing.T) {\n\t\tw := httptest.NewRecorder()\n\t\tc, _ := CreateTestContext(w)\n\t\tc.Request = httptest.NewRequest(http.MethodGet, \"/test\", nil)\n\n\t\tc.File(\".\")\n\n\t\t// Directory serving can return either 200 (with listing) or 403 (forbidden)\n\t\tassert.True(t, w.Code == http.StatusOK || w.Code == http.StatusForbidden)\n\t})\n\n\t// Test with HEAD request\n\tt.Run(\"HEAD request\", func(t *testing.T) {\n\t\ttestFile := \"testdata/test_file.txt\"\n\n\t\tw := httptest.NewRecorder()\n\t\tc, _ := CreateTestContext(w)\n\t\tc.Request = httptest.NewRequest(http.MethodHead, \"/test\", nil)\n\n\t\tc.File(testFile)\n\n\t\tassert.Equal(t, http.StatusOK, w.Code)\n\t\tassert.Empty(t, w.Body.String()) // HEAD request should not return body\n\t\tassert.Equal(t, \"text/plain; charset=utf-8\", w.Header().Get(\"Content-Type\"))\n\t})\n\n\t// Test with Range request\n\tt.Run(\"Range request\", func(t *testing.T) {\n\t\ttestFile := \"testdata/test_file.txt\"\n\n\t\tw := httptest.NewRecorder()\n\t\tc, _ := CreateTestContext(w)\n\t\tc.Request = httptest.NewRequest(http.MethodGet, \"/test\", nil)\n\t\tc.Request.Header.Set(\"Range\", \"bytes=0-10\")\n\n\t\tc.File(testFile)\n\n\t\tassert.Equal(t, http.StatusPartialContent, w.Code)\n\t\tassert.Equal(t, \"bytes\", w.Header().Get(\"Accept-Ranges\"))\n\t\tassert.Contains(t, w.Header().Get(\"Content-Range\"), \"bytes 0-10\")\n\t})\n}\n\nfunc TestContextFormFile(t *testing.T) {\n\tbuf := new(bytes.Buffer)\n\tmw := multipart.NewWriter(buf)\n\tw, err := mw.CreateFormFile(\"file\", \"test\")\n\trequire.NoError(t, err)\n\t_, err = w.Write([]byte(\"test\"))\n\trequire.NoError(t, err)\n\tmw.Close()\n\tc, _ := CreateTestContext(httptest.NewRecorder())\n\tc.Request, _ = http.NewRequest(http.MethodPost, \"/\", buf)\n\tc.Request.Header.Set(\"Content-Type\", mw.FormDataContentType())\n\tf, err := c.FormFile(\"file\")\n\trequire.NoError(t, err)\n\tassert.Equal(t, \"test\", f.Filename)\n\n\trequire.NoError(t, c.SaveUploadedFile(f, \"test\"))\n}\n\nfunc TestContextFormFileFailed(t *testing.T) {\n\tbuf := new(bytes.Buffer)\n\tmw := multipart.NewWriter(buf)\n\tmw.Close()\n\tc, _ := CreateTestContext(httptest.NewRecorder())\n\tc.Request, _ = http.NewRequest(http.MethodPost, \"/\", nil)\n\tc.Request.Header.Set(\"Content-Type\", mw.FormDataContentType())\n\tc.engine.MaxMultipartMemory = 8 << 20\n\tf, err := c.FormFile(\"file\")\n\trequire.Error(t, err)\n\tassert.Nil(t, f)\n}\n\nfunc TestContextMultipartForm(t *testing.T) {\n\tbuf := new(bytes.Buffer)\n\tmw := multipart.NewWriter(buf)\n\trequire.NoError(t, mw.WriteField(\"foo\", \"bar\"))\n\tw, err := mw.CreateFormFile(\"file\", \"test\")\n\trequire.NoError(t, err)\n\t_, err = w.Write([]byte(\"test\"))\n\trequire.NoError(t, err)\n\tmw.Close()\n\tc, _ := CreateTestContext(httptest.NewRecorder())\n\tc.Request, _ = http.NewRequest(http.MethodPost, \"/\", buf)\n\tc.Request.Header.Set(\"Content-Type\", mw.FormDataContentType())\n\tf, err := c.MultipartForm()\n\trequire.NoError(t, err)\n\tassert.NotNil(t, f)\n\n\trequire.NoError(t, c.SaveUploadedFile(f.File[\"file\"][0], \"test\"))\n}\n\nfunc TestSaveUploadedOpenFailed(t *testing.T) {\n\tbuf := new(bytes.Buffer)\n\tmw := multipart.NewWriter(buf)\n\tmw.Close()\n\n\tc, _ := CreateTestContext(httptest.NewRecorder())\n\tc.Request, _ = http.NewRequest(http.MethodPost, \"/\", buf)\n\tc.Request.Header.Set(\"Content-Type\", mw.FormDataContentType())\n\n\tf := &multipart.FileHeader{\n\t\tFilename: \"file\",\n\t}\n\trequire.Error(t, c.SaveUploadedFile(f, \"test\"))\n}\n\nfunc TestSaveUploadedCreateFailed(t *testing.T) {\n\tbuf := new(bytes.Buffer)\n\tmw := multipart.NewWriter(buf)\n\tw, err := mw.CreateFormFile(\"file\", \"test\")\n\trequire.NoError(t, err)\n\t_, err = w.Write([]byte(\"test\"))\n\trequire.NoError(t, err)\n\tmw.Close()\n\tc, _ := CreateTestContext(httptest.NewRecorder())\n\tc.Request, _ = http.NewRequest(http.MethodPost, \"/\", buf)\n\tc.Request.Header.Set(\"Content-Type\", mw.FormDataContentType())\n\tf, err := c.FormFile(\"file\")\n\trequire.NoError(t, err)\n\tassert.Equal(t, \"test\", f.Filename)\n\n\trequire.Error(t, c.SaveUploadedFile(f, \"/\"))\n}\n\nfunc TestSaveUploadedFileWithPermission(t *testing.T) {\n\tbuf := new(bytes.Buffer)\n\tmw := multipart.NewWriter(buf)\n\tw, err := mw.CreateFormFile(\"file\", \"permission_test\")\n\trequire.NoError(t, err)\n\t_, err = w.Write([]byte(\"permission_test\"))\n\trequire.NoError(t, err)\n\tmw.Close()\n\tc, _ := CreateTestContext(httptest.NewRecorder())\n\tc.Request, _ = http.NewRequest(http.MethodPost, \"/\", buf)\n\tc.Request.Header.Set(\"Content-Type\", mw.FormDataContentType())\n\tf, err := c.FormFile(\"file\")\n\trequire.NoError(t, err)\n\tassert.Equal(t, \"permission_test\", f.Filename)\n\tvar mode fs.FileMode = 0o755\n\trequire.NoError(t, c.SaveUploadedFile(f, \"permission_test\", mode))\n\tt.Cleanup(func() {\n\t\tassert.NoError(t, os.Remove(\"permission_test\"))\n\t})\n\tinfo, err := os.Stat(filepath.Dir(\"permission_test\"))\n\trequire.NoError(t, err)\n\tassert.Equal(t, info.Mode().Perm(), mode)\n}\n\nfunc TestSaveUploadedFileWithPermissionFailed(t *testing.T) {\n\tbuf := new(bytes.Buffer)\n\tmw := multipart.NewWriter(buf)\n\tw, err := mw.CreateFormFile(\"file\", \"permission_test\")\n\trequire.NoError(t, err)\n\t_, err = w.Write([]byte(\"permission_test\"))\n\trequire.NoError(t, err)\n\tmw.Close()\n\tc, _ := CreateTestContext(httptest.NewRecorder())\n\tc.Request, _ = http.NewRequest(http.MethodPost, \"/\", buf)\n\tc.Request.Header.Set(\"Content-Type\", mw.FormDataContentType())\n\tf, err := c.FormFile(\"file\")\n\trequire.NoError(t, err)\n\tassert.Equal(t, \"permission_test\", f.Filename)\n\tvar mode fs.FileMode = 0o644\n\trequire.Error(t, c.SaveUploadedFile(f, \"test/permission_test\", mode))\n}\n\nfunc TestContextReset(t *testing.T) {\n\trouter := New()\n\tc := router.allocateContext(0)\n\tassert.Equal(t, c.engine, router)\n\n\tc.index = 2\n\tc.Writer = &responseWriter{ResponseWriter: httptest.NewRecorder()}\n\tc.Params = Params{Param{}}\n\tc.Error(errors.New(\"test\")) //nolint: errcheck\n\tc.Set(\"foo\", \"bar\")\n\tc.reset()\n\n\tassert.False(t, c.IsAborted())\n\tassert.Nil(t, c.Keys)\n\tassert.Nil(t, c.Accepted)\n\tassert.Empty(t, c.Errors)\n\tassert.Empty(t, c.Errors.Errors())\n\tassert.Empty(t, c.Errors.ByType(ErrorTypeAny))\n\tassert.Empty(t, c.Params)\n\tassert.EqualValues(t, -1, c.index)\n\tassert.Equal(t, c.Writer.(*responseWriter), &c.writermem)\n}\n\nfunc TestContextHandlers(t *testing.T) {\n\tc, _ := CreateTestContext(httptest.NewRecorder())\n\tassert.Nil(t, c.handlers)\n\tassert.Nil(t, c.handlers.Last())\n\n\tc.handlers = HandlersChain{}\n\tassert.NotNil(t, c.handlers)\n\tassert.Nil(t, c.handlers.Last())\n\n\tf := func(c *Context) {}\n\tg := func(c *Context) {}\n\n\tc.handlers = HandlersChain{f}\n\tcompareFunc(t, f, c.handlers.Last())\n\n\tc.handlers = HandlersChain{f, g}\n\tcompareFunc(t, g, c.handlers.Last())\n}\n\n// TestContextSetGet tests that a parameter is set correctly on the\n// current context and can be retrieved using Get.\nfunc TestContextSetGet(t *testing.T) {\n\tc, _ := CreateTestContext(httptest.NewRecorder())\n\tc.Set(\"foo\", \"bar\")\n\n\tvalue, err := c.Get(\"foo\")\n\tassert.Equal(t, \"bar\", value)\n\tassert.True(t, err)\n\n\tvalue, err = c.Get(\"foo2\")\n\tassert.Nil(t, value)\n\tassert.False(t, err)\n\n\tassert.Equal(t, \"bar\", c.MustGet(\"foo\"))\n\tassert.Panicsf(t, func() {\n\t\tc.MustGet(\"no_exist\")\n\t}, \"key no_exist does not exist\")\n}\n\nfunc TestContextSetGetAnyKey(t *testing.T) {\n\tc, _ := CreateTestContext(httptest.NewRecorder())\n\n\ttype key struct{}\n\n\ttests := []struct {\n\t\tkey any\n\t}{\n\t\t{1},\n\t\t{int32(1)},\n\t\t{int64(1)},\n\t\t{uint(1)},\n\t\t{float32(1)},\n\t\t{key{}},\n\t\t{&key{}},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(reflect.TypeOf(tt.key).String(), func(t *testing.T) {\n\t\t\tc.Set(tt.key, 1)\n\t\t\tvalue, ok := c.Get(tt.key)\n\t\t\tassert.True(t, ok)\n\t\t\tassert.Equal(t, 1, value)\n\t\t})\n\t}\n}\n\nfunc TestContextSetGetPanicsWhenKeyNotComparable(t *testing.T) {\n\tc, _ := CreateTestContext(httptest.NewRecorder())\n\n\tassert.Panics(t, func() {\n\t\tc.Set([]int{1}, 1)\n\t\tc.Set(func() {}, 1)\n\t\tc.Set(make(chan int), 1)\n\t})\n}\n\nfunc TestContextSetGetValues(t *testing.T) {\n\tc, _ := CreateTestContext(httptest.NewRecorder())\n\tc.Set(\"string\", \"this is a string\")\n\tc.Set(\"int32\", int32(-42))\n\tc.Set(\"int64\", int64(42424242424242))\n\tc.Set(\"uint64\", uint64(42))\n\tc.Set(\"float32\", float32(4.2))\n\tc.Set(\"float64\", 4.2)\n\tvar a any = 1\n\tc.Set(\"intInterface\", a)\n\n\tassert.Exactly(t, \"this is a string\", c.MustGet(\"string\").(string))\n\tassert.Exactly(t, int32(-42), c.MustGet(\"int32\").(int32))\n\tassert.Exactly(t, int64(42424242424242), c.MustGet(\"int64\").(int64))\n\tassert.Exactly(t, uint64(42), c.MustGet(\"uint64\").(uint64))\n\tassert.InDelta(t, float32(4.2), c.MustGet(\"float32\").(float32), 0.01)\n\tassert.InDelta(t, 4.2, c.MustGet(\"float64\").(float64), 0.01)\n\tassert.Exactly(t, 1, c.MustGet(\"intInterface\").(int))\n}\n\nfunc TestContextGetString(t *testing.T) {\n\tc, _ := CreateTestContext(httptest.NewRecorder())\n\tc.Set(\"string\", \"this is a string\")\n\tassert.Equal(t, \"this is a string\", c.GetString(\"string\"))\n}\n\nfunc TestContextSetGetBool(t *testing.T) {\n\tc, _ := CreateTestContext(httptest.NewRecorder())\n\tc.Set(\"bool\", true)\n\tassert.True(t, c.GetBool(\"bool\"))\n}\n\nfunc TestSetGetDelete(t *testing.T) {\n\tc, _ := CreateTestContext(httptest.NewRecorder())\n\tkey := \"example-key\"\n\tvalue := \"example-value\"\n\tc.Set(key, value)\n\tval, exists := c.Get(key)\n\tassert.True(t, exists)\n\tassert.Equal(t, val, value)\n\tc.Delete(key)\n\t_, exists = c.Get(key)\n\tassert.False(t, exists)\n}\n\nfunc TestContextGetInt(t *testing.T) {\n\tc, _ := CreateTestContext(httptest.NewRecorder())\n\tc.Set(\"int\", 1)\n\tassert.Equal(t, 1, c.GetInt(\"int\"))\n}\n\nfunc TestContextGetInt8(t *testing.T) {\n\tc, _ := CreateTestContext(httptest.NewRecorder())\n\tkey := \"int8\"\n\tvalue := int8(0x7F)\n\tc.Set(key, value)\n\tassert.Equal(t, value, c.GetInt8(key))\n}\n\nfunc TestContextGetInt16(t *testing.T) {\n\tc, _ := CreateTestContext(httptest.NewRecorder())\n\tkey := \"int16\"\n\tvalue := int16(0x7FFF)\n\tc.Set(key, value)\n\tassert.Equal(t, value, c.GetInt16(key))\n}\n\nfunc TestContextGetInt32(t *testing.T) {\n\tc, _ := CreateTestContext(httptest.NewRecorder())\n\tkey := \"int32\"\n\tvalue := int32(0x7FFFFFFF)\n\tc.Set(key, value)\n\tassert.Equal(t, value, c.GetInt32(key))\n}\n\nfunc TestContextGetInt64(t *testing.T) {\n\tc, _ := CreateTestContext(httptest.NewRecorder())\n\tc.Set(\"int64\", int64(42424242424242))\n\tassert.Equal(t, int64(42424242424242), c.GetInt64(\"int64\"))\n}\n\nfunc TestContextGetUint(t *testing.T) {\n\tc, _ := CreateTestContext(httptest.NewRecorder())\n\tc.Set(\"uint\", uint(1))\n\tassert.Equal(t, uint(1), c.GetUint(\"uint\"))\n}\n\nfunc TestContextGetUint8(t *testing.T) {\n\tc, _ := CreateTestContext(httptest.NewRecorder())\n\tkey := \"uint8\"\n\tvalue := uint8(0xFF)\n\tc.Set(key, value)\n\tassert.Equal(t, value, c.GetUint8(key))\n}\n\nfunc TestContextGetUint16(t *testing.T) {\n\tc, _ := CreateTestContext(httptest.NewRecorder())\n\tkey := \"uint16\"\n\tvalue := uint16(0xFFFF)\n\tc.Set(key, value)\n\tassert.Equal(t, value, c.GetUint16(key))\n}\n\nfunc TestContextGetUint32(t *testing.T) {\n\tc, _ := CreateTestContext(httptest.NewRecorder())\n\tkey := \"uint32\"\n\tvalue := uint32(0xFFFFFFFF)\n\tc.Set(key, value)\n\tassert.Equal(t, value, c.GetUint32(key))\n}\n\nfunc TestContextGetUint64(t *testing.T) {\n\tc, _ := CreateTestContext(httptest.NewRecorder())\n\tc.Set(\"uint64\", uint64(18446744073709551615))\n\tassert.Equal(t, uint64(18446744073709551615), c.GetUint64(\"uint64\"))\n}\n\nfunc TestContextGetFloat32(t *testing.T) {\n\tc, _ := CreateTestContext(httptest.NewRecorder())\n\tkey := \"float32\"\n\tvalue := float32(3.14)\n\tc.Set(key, value)\n\tassert.InDelta(t, value, c.GetFloat32(key), 0.01)\n}\n\nfunc TestContextGetFloat64(t *testing.T) {\n\tc, _ := CreateTestContext(httptest.NewRecorder())\n\tc.Set(\"float64\", 4.2)\n\tassert.InDelta(t, 4.2, c.GetFloat64(\"float64\"), 0.01)\n}\n\nfunc TestContextGetTime(t *testing.T) {\n\tc, _ := CreateTestContext(httptest.NewRecorder())\n\tt1, _ := time.Parse(\"1/2/2006 15:04:05\", \"01/01/2017 12:00:00\")\n\tc.Set(\"time\", t1)\n\tassert.Equal(t, t1, c.GetTime(\"time\"))\n}\n\nfunc TestContextGetDuration(t *testing.T) {\n\tc, _ := CreateTestContext(httptest.NewRecorder())\n\tc.Set(\"duration\", time.Second)\n\tassert.Equal(t, time.Second, c.GetDuration(\"duration\"))\n}\n\nfunc TestContextGetError(t *testing.T) {\n\tc, _ := CreateTestContext(httptest.NewRecorder())\n\tkey := \"error\"\n\tvalue := errors.New(\"test error\")\n\tc.Set(key, value)\n\tassert.Equal(t, value, c.GetError(key))\n}\n\nfunc TestContextGetIntSlice(t *testing.T) {\n\tc, _ := CreateTestContext(httptest.NewRecorder())\n\tkey := \"int-slice\"\n\tvalue := []int{1, 2}\n\tc.Set(key, value)\n\tassert.Equal(t, value, c.GetIntSlice(key))\n}\n\nfunc TestContextGetInt8Slice(t *testing.T) {\n\tc, _ := CreateTestContext(httptest.NewRecorder())\n\tkey := \"int8-slice\"\n\tvalue := []int8{1, 2}\n\tc.Set(key, value)\n\tassert.Equal(t, value, c.GetInt8Slice(key))\n}\n\nfunc TestContextGetInt16Slice(t *testing.T) {\n\tc, _ := CreateTestContext(httptest.NewRecorder())\n\tkey := \"int16-slice\"\n\tvalue := []int16{1, 2}\n\tc.Set(key, value)\n\tassert.Equal(t, value, c.GetInt16Slice(key))\n}\n\nfunc TestContextGetInt32Slice(t *testing.T) {\n\tc, _ := CreateTestContext(httptest.NewRecorder())\n\tkey := \"int32-slice\"\n\tvalue := []int32{1, 2}\n\tc.Set(key, value)\n\tassert.Equal(t, value, c.GetInt32Slice(key))\n}\n\nfunc TestContextGetInt64Slice(t *testing.T) {\n\tc, _ := CreateTestContext(httptest.NewRecorder())\n\tkey := \"int64-slice\"\n\tvalue := []int64{1, 2}\n\tc.Set(key, value)\n\tassert.Equal(t, value, c.GetInt64Slice(key))\n}\n\nfunc TestContextGetUintSlice(t *testing.T) {\n\tc, _ := CreateTestContext(httptest.NewRecorder())\n\tkey := \"uint-slice\"\n\tvalue := []uint{1, 2}\n\tc.Set(key, value)\n\tassert.Equal(t, value, c.GetUintSlice(key))\n}\n\nfunc TestContextGetUint8Slice(t *testing.T) {\n\tc, _ := CreateTestContext(httptest.NewRecorder())\n\tkey := \"uint8-slice\"\n\tvalue := []uint8{1, 2}\n\tc.Set(key, value)\n\tassert.Equal(t, value, c.GetUint8Slice(key))\n}\n\nfunc TestContextGetUint16Slice(t *testing.T) {\n\tc, _ := CreateTestContext(httptest.NewRecorder())\n\tkey := \"uint16-slice\"\n\tvalue := []uint16{1, 2}\n\tc.Set(key, value)\n\tassert.Equal(t, value, c.GetUint16Slice(key))\n}\n\nfunc TestContextGetUint32Slice(t *testing.T) {\n\tc, _ := CreateTestContext(httptest.NewRecorder())\n\tkey := \"uint32-slice\"\n\tvalue := []uint32{1, 2}\n\tc.Set(key, value)\n\tassert.Equal(t, value, c.GetUint32Slice(key))\n}\n\nfunc TestContextGetUint64Slice(t *testing.T) {\n\tc, _ := CreateTestContext(httptest.NewRecorder())\n\tkey := \"uint64-slice\"\n\tvalue := []uint64{1, 2}\n\tc.Set(key, value)\n\tassert.Equal(t, value, c.GetUint64Slice(key))\n}\n\nfunc TestContextGetFloat32Slice(t *testing.T) {\n\tc, _ := CreateTestContext(httptest.NewRecorder())\n\tkey := \"float32-slice\"\n\tvalue := []float32{1, 2}\n\tc.Set(key, value)\n\tassert.Equal(t, value, c.GetFloat32Slice(key))\n}\n\nfunc TestContextGetFloat64Slice(t *testing.T) {\n\tc, _ := CreateTestContext(httptest.NewRecorder())\n\tkey := \"float64-slice\"\n\tvalue := []float64{1, 2}\n\tc.Set(key, value)\n\tassert.Equal(t, value, c.GetFloat64Slice(key))\n}\n\nfunc TestContextGetStringSlice(t *testing.T) {\n\tc, _ := CreateTestContext(httptest.NewRecorder())\n\tc.Set(\"slice\", []string{\"foo\"})\n\tassert.Equal(t, []string{\"foo\"}, c.GetStringSlice(\"slice\"))\n}\n\nfunc TestContextGetErrorSlice(t *testing.T) {\n\tc, _ := CreateTestContext(httptest.NewRecorder())\n\tkey := \"error-slice\"\n\tvalue := []error{errors.New(\"error1\"), errors.New(\"error2\")}\n\tc.Set(key, value)\n\tassert.Equal(t, value, c.GetErrorSlice(key))\n}\n\nfunc TestContextGetStringMap(t *testing.T) {\n\tc, _ := CreateTestContext(httptest.NewRecorder())\n\tm := make(map[string]any)\n\tm[\"foo\"] = 1\n\tc.Set(\"map\", m)\n\n\tassert.Equal(t, m, c.GetStringMap(\"map\"))\n\tassert.Equal(t, 1, c.GetStringMap(\"map\")[\"foo\"])\n}\n\nfunc TestContextGetStringMapString(t *testing.T) {\n\tc, _ := CreateTestContext(httptest.NewRecorder())\n\tm := make(map[string]string)\n\tm[\"foo\"] = \"bar\"\n\tc.Set(\"map\", m)\n\n\tassert.Equal(t, m, c.GetStringMapString(\"map\"))\n\tassert.Equal(t, \"bar\", c.GetStringMapString(\"map\")[\"foo\"])\n}\n\nfunc TestContextGetStringMapStringSlice(t *testing.T) {\n\tc, _ := CreateTestContext(httptest.NewRecorder())\n\tm := make(map[string][]string)\n\tm[\"foo\"] = []string{\"foo\"}\n\tc.Set(\"map\", m)\n\n\tassert.Equal(t, m, c.GetStringMapStringSlice(\"map\"))\n\tassert.Equal(t, []string{\"foo\"}, c.GetStringMapStringSlice(\"map\")[\"foo\"])\n}\n\nfunc TestContextCopy(t *testing.T) {\n\tc, _ := CreateTestContext(httptest.NewRecorder())\n\tc.index = 2\n\tc.Request, _ = http.NewRequest(http.MethodPost, \"/hola\", nil)\n\tc.handlers = HandlersChain{func(c *Context) {}}\n\tc.Params = Params{Param{Key: \"foo\", Value: \"bar\"}}\n\tc.Set(\"foo\", \"bar\")\n\tc.fullPath = \"/hola\"\n\n\tcp := c.Copy()\n\tassert.Nil(t, cp.handlers)\n\tassert.Nil(t, cp.writermem.ResponseWriter)\n\tassert.Equal(t, &cp.writermem, cp.Writer.(*responseWriter))\n\tassert.Equal(t, cp.Request, c.Request)\n\tassert.Equal(t, abortIndex, cp.index)\n\tassert.Equal(t, cp.Keys, c.Keys)\n\tassert.Equal(t, cp.engine, c.engine)\n\tassert.Equal(t, cp.Params, c.Params)\n\tcp.Set(\"foo\", \"notBar\")\n\tassert.NotEqual(t, cp.Keys[\"foo\"], c.Keys[\"foo\"])\n\tassert.Equal(t, cp.fullPath, c.fullPath)\n}\n\nfunc TestContextHandlerName(t *testing.T) {\n\tc, _ := CreateTestContext(httptest.NewRecorder())\n\tc.handlers = HandlersChain{func(c *Context) {}, handlerNameTest}\n\n\tassert.Regexp(t, \"^(.*/vendor/)?github.com/gin-gonic/gin.handlerNameTest$\", c.HandlerName())\n}\n\nfunc TestContextHandlerNames(t *testing.T) {\n\tc, _ := CreateTestContext(httptest.NewRecorder())\n\tc.handlers = HandlersChain{func(c *Context) {}, nil, handlerNameTest, func(c *Context) {}, handlerNameTest2}\n\n\tnames := c.HandlerNames()\n\n\tassert.Len(t, names, 4)\n\tfor _, name := range names {\n\t\tassert.Regexp(t, `^(.*/vendor/)?(github\\.com/gin-gonic/gin\\.){1}(TestContextHandlerNames\\.func.*){0,1}(handlerNameTest.*){0,1}`, name)\n\t}\n}\n\nfunc handlerNameTest(c *Context) {\n}\n\nfunc handlerNameTest2(c *Context) {\n}\n\nvar handlerTest HandlerFunc = func(c *Context) {\n}\n\nfunc TestContextHandler(t *testing.T) {\n\tc, _ := CreateTestContext(httptest.NewRecorder())\n\tc.handlers = HandlersChain{func(c *Context) {}, handlerTest}\n\n\tassert.Equal(t, reflect.ValueOf(handlerTest).Pointer(), reflect.ValueOf(c.Handler()).Pointer())\n}\n\nfunc TestContextQuery(t *testing.T) {\n\tc, _ := CreateTestContext(httptest.NewRecorder())\n\tc.Request, _ = http.NewRequest(http.MethodGet, \"http://example.com/?foo=bar&page=10&id=\", nil)\n\n\tvalue, ok := c.GetQuery(\"foo\")\n\tassert.True(t, ok)\n\tassert.Equal(t, \"bar\", value)\n\tassert.Equal(t, \"bar\", c.DefaultQuery(\"foo\", \"none\"))\n\tassert.Equal(t, \"bar\", c.Query(\"foo\"))\n\n\tvalue, ok = c.GetQuery(\"page\")\n\tassert.True(t, ok)\n\tassert.Equal(t, \"10\", value)\n\tassert.Equal(t, \"10\", c.DefaultQuery(\"page\", \"0\"))\n\tassert.Equal(t, \"10\", c.Query(\"page\"))\n\n\tvalue, ok = c.GetQuery(\"id\")\n\tassert.True(t, ok)\n\tassert.Empty(t, value)\n\tassert.Empty(t, c.DefaultQuery(\"id\", \"nada\"))\n\tassert.Empty(t, c.Query(\"id\"))\n\n\tvalue, ok = c.GetQuery(\"NoKey\")\n\tassert.False(t, ok)\n\tassert.Empty(t, value)\n\tassert.Equal(t, \"nada\", c.DefaultQuery(\"NoKey\", \"nada\"))\n\tassert.Empty(t, c.Query(\"NoKey\"))\n\n\t// postform should not mess\n\tvalue, ok = c.GetPostForm(\"page\")\n\tassert.False(t, ok)\n\tassert.Empty(t, value)\n\tassert.Empty(t, c.PostForm(\"foo\"))\n}\n\nfunc TestContextInitQueryCache(t *testing.T) {\n\tvalidURL, err := url.Parse(\"https://github.com/gin-gonic/gin/pull/3969?key=value&otherkey=othervalue\")\n\trequire.NoError(t, err)\n\n\ttests := []struct {\n\t\ttestName           string\n\t\ttestContext        *Context\n\t\texpectedQueryCache url.Values\n\t}{\n\t\t{\n\t\t\ttestName: \"queryCache should remain unchanged if already not nil\",\n\t\t\ttestContext: &Context{\n\t\t\t\tqueryCache: url.Values{\"a\": []string{\"b\"}},\n\t\t\t\tRequest:    &http.Request{URL: validURL}, // valid request for evidence that values weren't extracted\n\t\t\t},\n\t\t\texpectedQueryCache: url.Values{\"a\": []string{\"b\"}},\n\t\t},\n\t\t{\n\t\t\ttestName:           \"queryCache should be empty when Request is nil\",\n\t\t\ttestContext:        &Context{Request: nil}, // explicit nil for readability\n\t\t\texpectedQueryCache: url.Values{},\n\t\t},\n\t\t{\n\t\t\ttestName:           \"queryCache should be empty when Request.URL is nil\",\n\t\t\ttestContext:        &Context{Request: &http.Request{URL: nil}}, // explicit nil for readability\n\t\t\texpectedQueryCache: url.Values{},\n\t\t},\n\t\t{\n\t\t\ttestName:           \"queryCache should be populated when it not yet populated and Request + Request.URL are non nil\",\n\t\t\ttestContext:        &Context{Request: &http.Request{URL: validURL}}, // explicit nil for readability\n\t\t\texpectedQueryCache: url.Values{\"key\": []string{\"value\"}, \"otherkey\": []string{\"othervalue\"}},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.testName, func(t *testing.T) {\n\t\t\ttest.testContext.initQueryCache()\n\t\t\tassert.Equal(t, test.expectedQueryCache, test.testContext.queryCache)\n\t\t})\n\t}\n}\n\nfunc TestContextDefaultQueryOnEmptyRequest(t *testing.T) {\n\tc, _ := CreateTestContext(httptest.NewRecorder()) // here c.Request == nil\n\tassert.NotPanics(t, func() {\n\t\tvalue, ok := c.GetQuery(\"NoKey\")\n\t\tassert.False(t, ok)\n\t\tassert.Empty(t, value)\n\t})\n\tassert.NotPanics(t, func() {\n\t\tassert.Equal(t, \"nada\", c.DefaultQuery(\"NoKey\", \"nada\"))\n\t})\n\tassert.NotPanics(t, func() {\n\t\tassert.Empty(t, c.Query(\"NoKey\"))\n\t})\n}\n\nfunc TestContextQueryAndPostForm(t *testing.T) {\n\tc, _ := CreateTestContext(httptest.NewRecorder())\n\tbody := strings.NewReader(\"foo=bar&page=11&both=&foo=second\")\n\tc.Request, _ = http.NewRequest(http.MethodPost,\n\t\t\"/?both=GET&id=main&id=omit&array[]=first&array[]=second&ids[a]=hi&ids[b]=3.14\", body)\n\tc.Request.Header.Add(\"Content-Type\", MIMEPOSTForm)\n\n\tassert.Equal(t, \"bar\", c.DefaultPostForm(\"foo\", \"none\"))\n\tassert.Equal(t, \"bar\", c.PostForm(\"foo\"))\n\tassert.Empty(t, c.Query(\"foo\"))\n\n\tvalue, ok := c.GetPostForm(\"page\")\n\tassert.True(t, ok)\n\tassert.Equal(t, \"11\", value)\n\tassert.Equal(t, \"11\", c.DefaultPostForm(\"page\", \"0\"))\n\tassert.Equal(t, \"11\", c.PostForm(\"page\"))\n\tassert.Empty(t, c.Query(\"page\"))\n\n\tvalue, ok = c.GetPostForm(\"both\")\n\tassert.True(t, ok)\n\tassert.Empty(t, value)\n\tassert.Empty(t, c.PostForm(\"both\"))\n\tassert.Empty(t, c.DefaultPostForm(\"both\", \"nothing\"))\n\tassert.Equal(t, http.MethodGet, c.Query(\"both\"), http.MethodGet)\n\n\tvalue, ok = c.GetQuery(\"id\")\n\tassert.True(t, ok)\n\tassert.Equal(t, \"main\", value)\n\tassert.Equal(t, \"000\", c.DefaultPostForm(\"id\", \"000\"))\n\tassert.Equal(t, \"main\", c.Query(\"id\"))\n\tassert.Empty(t, c.PostForm(\"id\"))\n\n\tvalue, ok = c.GetQuery(\"NoKey\")\n\tassert.False(t, ok)\n\tassert.Empty(t, value)\n\tvalue, ok = c.GetPostForm(\"NoKey\")\n\tassert.False(t, ok)\n\tassert.Empty(t, value)\n\tassert.Equal(t, \"nada\", c.DefaultPostForm(\"NoKey\", \"nada\"))\n\tassert.Equal(t, \"nothing\", c.DefaultQuery(\"NoKey\", \"nothing\"))\n\tassert.Empty(t, c.PostForm(\"NoKey\"))\n\tassert.Empty(t, c.Query(\"NoKey\"))\n\n\tvar obj struct {\n\t\tFoo   string   `form:\"foo\"`\n\t\tID    string   `form:\"id\"`\n\t\tPage  int      `form:\"page\"`\n\t\tBoth  string   `form:\"both\"`\n\t\tArray []string `form:\"array[]\"`\n\t}\n\trequire.NoError(t, c.Bind(&obj))\n\tassert.Equal(t, \"bar\", obj.Foo, \"bar\")\n\tassert.Equal(t, \"main\", obj.ID, \"main\")\n\tassert.Equal(t, 11, obj.Page, 11)\n\tassert.Empty(t, obj.Both)\n\tassert.Equal(t, []string{\"first\", \"second\"}, obj.Array)\n\n\tvalues, ok := c.GetQueryArray(\"array[]\")\n\tassert.True(t, ok)\n\tassert.Equal(t, \"first\", values[0])\n\tassert.Equal(t, \"second\", values[1])\n\n\tvalues = c.QueryArray(\"array[]\")\n\tassert.Equal(t, \"first\", values[0])\n\tassert.Equal(t, \"second\", values[1])\n\n\tvalues = c.QueryArray(\"nokey\")\n\tassert.Empty(t, values)\n\n\tvalues = c.QueryArray(\"both\")\n\tassert.Len(t, values, 1)\n\tassert.Equal(t, http.MethodGet, values[0])\n\n\tdicts, ok := c.GetQueryMap(\"ids\")\n\tassert.True(t, ok)\n\tassert.Equal(t, \"hi\", dicts[\"a\"])\n\tassert.Equal(t, \"3.14\", dicts[\"b\"])\n\n\tdicts, ok = c.GetQueryMap(\"nokey\")\n\tassert.False(t, ok)\n\tassert.Empty(t, dicts)\n\n\tdicts, ok = c.GetQueryMap(\"both\")\n\tassert.False(t, ok)\n\tassert.Empty(t, dicts)\n\n\tdicts, ok = c.GetQueryMap(\"array\")\n\tassert.False(t, ok)\n\tassert.Empty(t, dicts)\n\n\tdicts = c.QueryMap(\"ids\")\n\tassert.Equal(t, \"hi\", dicts[\"a\"])\n\tassert.Equal(t, \"3.14\", dicts[\"b\"])\n\n\tdicts = c.QueryMap(\"nokey\")\n\tassert.Empty(t, dicts)\n}\n\nfunc TestContextPostFormMultipart(t *testing.T) {\n\tc, _ := CreateTestContext(httptest.NewRecorder())\n\tc.Request = createMultipartRequest()\n\n\tvar obj struct {\n\t\tFoo          string    `form:\"foo\"`\n\t\tBar          string    `form:\"bar\"`\n\t\tBarAsInt     int       `form:\"bar\"`\n\t\tArray        []string  `form:\"array\"`\n\t\tID           string    `form:\"id\"`\n\t\tTimeLocal    time.Time `form:\"time_local\" time_format:\"02/01/2006 15:04\"`\n\t\tTimeUTC      time.Time `form:\"time_utc\" time_format:\"02/01/2006 15:04\" time_utc:\"1\"`\n\t\tTimeLocation time.Time `form:\"time_location\" time_format:\"02/01/2006 15:04\" time_location:\"Asia/Tokyo\"`\n\t\tBlankTime    time.Time `form:\"blank_time\" time_format:\"02/01/2006 15:04\"`\n\t}\n\trequire.NoError(t, c.Bind(&obj))\n\tassert.Equal(t, \"bar\", obj.Foo)\n\tassert.Equal(t, \"10\", obj.Bar)\n\tassert.Equal(t, 10, obj.BarAsInt)\n\tassert.Equal(t, []string{\"first\", \"second\"}, obj.Array)\n\tassert.Empty(t, obj.ID)\n\tassert.Equal(t, \"31/12/2016 14:55\", obj.TimeLocal.Format(\"02/01/2006 15:04\"))\n\tassert.Equal(t, time.Local, obj.TimeLocal.Location())\n\tassert.Equal(t, \"31/12/2016 14:55\", obj.TimeUTC.Format(\"02/01/2006 15:04\"))\n\tassert.Equal(t, time.UTC, obj.TimeUTC.Location())\n\tloc, _ := time.LoadLocation(\"Asia/Tokyo\")\n\tassert.Equal(t, \"31/12/2016 14:55\", obj.TimeLocation.Format(\"02/01/2006 15:04\"))\n\tassert.Equal(t, loc, obj.TimeLocation.Location())\n\tassert.True(t, obj.BlankTime.IsZero())\n\n\tvalue, ok := c.GetQuery(\"foo\")\n\tassert.False(t, ok)\n\tassert.Empty(t, value)\n\tassert.Empty(t, c.Query(\"bar\"))\n\tassert.Equal(t, \"nothing\", c.DefaultQuery(\"id\", \"nothing\"))\n\n\tvalue, ok = c.GetPostForm(\"foo\")\n\tassert.True(t, ok)\n\tassert.Equal(t, \"bar\", value)\n\tassert.Equal(t, \"bar\", c.PostForm(\"foo\"))\n\n\tvalue, ok = c.GetPostForm(\"array\")\n\tassert.True(t, ok)\n\tassert.Equal(t, \"first\", value)\n\tassert.Equal(t, \"first\", c.PostForm(\"array\"))\n\n\tassert.Equal(t, \"10\", c.DefaultPostForm(\"bar\", \"nothing\"))\n\n\tvalue, ok = c.GetPostForm(\"id\")\n\tassert.True(t, ok)\n\tassert.Empty(t, value)\n\tassert.Empty(t, c.PostForm(\"id\"))\n\tassert.Empty(t, c.DefaultPostForm(\"id\", \"nothing\"))\n\n\tvalue, ok = c.GetPostForm(\"nokey\")\n\tassert.False(t, ok)\n\tassert.Empty(t, value)\n\tassert.Equal(t, \"nothing\", c.DefaultPostForm(\"nokey\", \"nothing\"))\n\n\tvalues, ok := c.GetPostFormArray(\"array\")\n\tassert.True(t, ok)\n\tassert.Equal(t, \"first\", values[0])\n\tassert.Equal(t, \"second\", values[1])\n\n\tvalues = c.PostFormArray(\"array\")\n\tassert.Equal(t, \"first\", values[0])\n\tassert.Equal(t, \"second\", values[1])\n\n\tvalues = c.PostFormArray(\"nokey\")\n\tassert.Empty(t, values)\n\n\tvalues = c.PostFormArray(\"foo\")\n\tassert.Len(t, values, 1)\n\tassert.Equal(t, \"bar\", values[0])\n\n\tdicts, ok := c.GetPostFormMap(\"names\")\n\tassert.True(t, ok)\n\tassert.Equal(t, \"thinkerou\", dicts[\"a\"])\n\tassert.Equal(t, \"tianou\", dicts[\"b\"])\n\n\tdicts, ok = c.GetPostFormMap(\"nokey\")\n\tassert.False(t, ok)\n\tassert.Empty(t, dicts)\n\n\tdicts = c.PostFormMap(\"names\")\n\tassert.Equal(t, \"thinkerou\", dicts[\"a\"])\n\tassert.Equal(t, \"tianou\", dicts[\"b\"])\n\n\tdicts = c.PostFormMap(\"nokey\")\n\tassert.Empty(t, dicts)\n}\n\nfunc TestContextSetCookie(t *testing.T) {\n\tc, _ := CreateTestContext(httptest.NewRecorder())\n\tc.SetSameSite(http.SameSiteLaxMode)\n\tc.SetCookie(\"user\", \"gin\", 1, \"/\", \"localhost\", true, true)\n\tassert.Equal(t, \"user=gin; Path=/; Domain=localhost; Max-Age=1; HttpOnly; Secure; SameSite=Lax\", c.Writer.Header().Get(\"Set-Cookie\"))\n}\n\nfunc TestContextSetCookiePathEmpty(t *testing.T) {\n\tc, _ := CreateTestContext(httptest.NewRecorder())\n\tc.SetSameSite(http.SameSiteLaxMode)\n\tc.SetCookie(\"user\", \"gin\", 1, \"\", \"localhost\", true, true)\n\tassert.Equal(t, \"user=gin; Path=/; Domain=localhost; Max-Age=1; HttpOnly; Secure; SameSite=Lax\", c.Writer.Header().Get(\"Set-Cookie\"))\n}\n\nfunc TestContextGetCookie(t *testing.T) {\n\tc, _ := CreateTestContext(httptest.NewRecorder())\n\tc.Request, _ = http.NewRequest(http.MethodGet, \"/get\", nil)\n\tc.Request.Header.Set(\"Cookie\", \"user=gin\")\n\tcookie, _ := c.Cookie(\"user\")\n\tassert.Equal(t, \"gin\", cookie)\n\n\t_, err := c.Cookie(\"nokey\")\n\trequire.Error(t, err)\n}\n\nfunc TestContextBodyAllowedForStatus(t *testing.T) {\n\tassert.False(t, bodyAllowedForStatus(http.StatusContinue))\n\tassert.False(t, bodyAllowedForStatus(http.StatusProcessing))\n\tassert.False(t, bodyAllowedForStatus(http.StatusNoContent))\n\tassert.False(t, bodyAllowedForStatus(http.StatusNotModified))\n\tassert.True(t, bodyAllowedForStatus(http.StatusInternalServerError))\n}\n\ntype TestRender struct{}\n\nfunc (*TestRender) Render(http.ResponseWriter) error {\n\treturn errTestRender\n}\n\nfunc (*TestRender) WriteContentType(http.ResponseWriter) {}\n\nfunc TestContextRenderIfErr(t *testing.T) {\n\tw := httptest.NewRecorder()\n\tc, _ := CreateTestContext(w)\n\n\tc.Render(http.StatusOK, &TestRender{})\n\n\tassert.Equal(t, errorMsgs{&Error{Err: errTestRender, Type: 1}}, c.Errors)\n}\n\n// Tests that the response is serialized as JSON\n// and Content-Type is set to application/json\n// and special HTML characters are escaped\nfunc TestContextRenderJSON(t *testing.T) {\n\tw := httptest.NewRecorder()\n\tc, _ := CreateTestContext(w)\n\n\tc.JSON(http.StatusCreated, H{\"foo\": \"bar\", \"html\": \"<b>\"})\n\n\tassert.Equal(t, http.StatusCreated, w.Code)\n\tassert.JSONEq(t, \"{\\\"foo\\\":\\\"bar\\\",\\\"html\\\":\\\"\\\\u003cb\\\\u003e\\\"}\", w.Body.String())\n\tassert.Equal(t, \"application/json; charset=utf-8\", w.Header().Get(\"Content-Type\"))\n}\n\n// Tests that the response is serialized as JSONP\n// and Content-Type is set to application/javascript\nfunc TestContextRenderJSONP(t *testing.T) {\n\tw := httptest.NewRecorder()\n\tc, _ := CreateTestContext(w)\n\tc.Request, _ = http.NewRequest(http.MethodGet, \"http://example.com/?callback=x\", nil)\n\n\tc.JSONP(http.StatusCreated, H{\"foo\": \"bar\"})\n\n\tassert.Equal(t, http.StatusCreated, w.Code)\n\tassert.Equal(t, \"x({\\\"foo\\\":\\\"bar\\\"});\", w.Body.String())\n\tassert.Equal(t, \"application/javascript; charset=utf-8\", w.Header().Get(\"Content-Type\"))\n}\n\n// Tests that the response is serialized as JSONP\n// and Content-Type is set to application/json\nfunc TestContextRenderJSONPWithoutCallback(t *testing.T) {\n\tw := httptest.NewRecorder()\n\tc, _ := CreateTestContext(w)\n\tc.Request, _ = http.NewRequest(http.MethodGet, \"http://example.com\", nil)\n\n\tc.JSONP(http.StatusCreated, H{\"foo\": \"bar\"})\n\n\tassert.Equal(t, http.StatusCreated, w.Code)\n\tassert.JSONEq(t, `{\"foo\":\"bar\"}`, w.Body.String())\n\tassert.Equal(t, \"application/json; charset=utf-8\", w.Header().Get(\"Content-Type\"))\n}\n\n// Tests that no JSON is rendered if code is 204\nfunc TestContextRenderNoContentJSON(t *testing.T) {\n\tw := httptest.NewRecorder()\n\tc, _ := CreateTestContext(w)\n\n\tc.JSON(http.StatusNoContent, H{\"foo\": \"bar\"})\n\n\tassert.Equal(t, http.StatusNoContent, w.Code)\n\tassert.Empty(t, w.Body.String())\n\tassert.Equal(t, \"application/json; charset=utf-8\", w.Header().Get(\"Content-Type\"))\n}\n\n// Tests that the response is serialized as JSON\n// we change the content-type before\nfunc TestContextRenderAPIJSON(t *testing.T) {\n\tw := httptest.NewRecorder()\n\tc, _ := CreateTestContext(w)\n\n\tc.Header(\"Content-Type\", \"application/vnd.api+json\")\n\tc.JSON(http.StatusCreated, H{\"foo\": \"bar\"})\n\n\tassert.Equal(t, http.StatusCreated, w.Code)\n\tassert.JSONEq(t, `{\"foo\":\"bar\"}`, w.Body.String())\n\tassert.Equal(t, \"application/vnd.api+json\", w.Header().Get(\"Content-Type\"))\n}\n\n// Tests that no Custom JSON is rendered if code is 204\nfunc TestContextRenderNoContentAPIJSON(t *testing.T) {\n\tw := httptest.NewRecorder()\n\tc, _ := CreateTestContext(w)\n\n\tc.Header(\"Content-Type\", \"application/vnd.api+json\")\n\tc.JSON(http.StatusNoContent, H{\"foo\": \"bar\"})\n\n\tassert.Equal(t, http.StatusNoContent, w.Code)\n\tassert.Empty(t, w.Body.String())\n\tassert.Equal(t, \"application/vnd.api+json\", w.Header().Get(\"Content-Type\"))\n}\n\n// Tests that the response is serialized as JSON\n// and Content-Type is set to application/json\nfunc TestContextRenderIndentedJSON(t *testing.T) {\n\tw := httptest.NewRecorder()\n\tc, _ := CreateTestContext(w)\n\n\tc.IndentedJSON(http.StatusCreated, H{\"foo\": \"bar\", \"bar\": \"foo\", \"nested\": H{\"foo\": \"bar\"}})\n\n\tassert.Equal(t, http.StatusCreated, w.Code)\n\tassert.JSONEq(t, \"{\\n    \\\"bar\\\": \\\"foo\\\",\\n    \\\"foo\\\": \\\"bar\\\",\\n    \\\"nested\\\": {\\n        \\\"foo\\\": \\\"bar\\\"\\n    }\\n}\", w.Body.String())\n\tassert.Equal(t, \"application/json; charset=utf-8\", w.Header().Get(\"Content-Type\"))\n}\n\n// Tests that no Custom JSON is rendered if code is 204\nfunc TestContextRenderNoContentIndentedJSON(t *testing.T) {\n\tw := httptest.NewRecorder()\n\tc, _ := CreateTestContext(w)\n\n\tc.IndentedJSON(http.StatusNoContent, H{\"foo\": \"bar\", \"bar\": \"foo\", \"nested\": H{\"foo\": \"bar\"}})\n\n\tassert.Equal(t, http.StatusNoContent, w.Code)\n\tassert.Empty(t, w.Body.String())\n\tassert.Equal(t, \"application/json; charset=utf-8\", w.Header().Get(\"Content-Type\"))\n}\n\nfunc TestContextClientIPWithMultipleHeaders(t *testing.T) {\n\tc, _ := CreateTestContext(httptest.NewRecorder())\n\tc.Request, _ = http.NewRequest(http.MethodGet, \"/test\", nil)\n\n\t// Multiple X-Forwarded-For headers\n\tc.Request.Header.Add(\"X-Forwarded-For\", \"1.2.3.4, \"+localhostIP)\n\tc.Request.Header.Add(\"X-Forwarded-For\", \"5.6.7.8\")\n\tc.Request.RemoteAddr = localhostIP + \":1234\"\n\n\tc.engine.ForwardedByClientIP = true\n\tc.engine.RemoteIPHeaders = []string{\"X-Forwarded-For\"}\n\t_ = c.engine.SetTrustedProxies([]string{localhostIP})\n\n\t// Should return 5.6.7.8 (last non-trusted IP)\n\tassert.Equal(t, \"5.6.7.8\", c.ClientIP())\n}\n\nfunc TestContextClientIPWithSingleHeader(t *testing.T) {\n\tc, _ := CreateTestContext(httptest.NewRecorder())\n\tc.Request, _ = http.NewRequest(http.MethodGet, \"/test\", nil)\n\tc.Request.Header.Set(\"X-Forwarded-For\", \"1.2.3.4, \"+localhostIP)\n\tc.Request.RemoteAddr = localhostIP + \":1234\"\n\n\tc.engine.ForwardedByClientIP = true\n\tc.engine.RemoteIPHeaders = []string{\"X-Forwarded-For\"}\n\t_ = c.engine.SetTrustedProxies([]string{localhostIP})\n\n\t// Should return 1.2.3.4\n\tassert.Equal(t, \"1.2.3.4\", c.ClientIP())\n}\n\n// Tests that the response is serialized as Secure JSON\n// and Content-Type is set to application/json\nfunc TestContextRenderSecureJSON(t *testing.T) {\n\tw := httptest.NewRecorder()\n\tc, router := CreateTestContext(w)\n\n\trouter.SecureJsonPrefix(\"&&&START&&&\")\n\tc.SecureJSON(http.StatusCreated, []string{\"foo\", \"bar\"})\n\n\tassert.Equal(t, http.StatusCreated, w.Code)\n\tassert.Equal(t, \"&&&START&&&[\\\"foo\\\",\\\"bar\\\"]\", w.Body.String())\n\tassert.Equal(t, \"application/json; charset=utf-8\", w.Header().Get(\"Content-Type\"))\n}\n\n// Tests that no Custom JSON is rendered if code is 204\nfunc TestContextRenderNoContentSecureJSON(t *testing.T) {\n\tw := httptest.NewRecorder()\n\tc, _ := CreateTestContext(w)\n\n\tc.SecureJSON(http.StatusNoContent, []string{\"foo\", \"bar\"})\n\n\tassert.Equal(t, http.StatusNoContent, w.Code)\n\tassert.Empty(t, w.Body.String())\n\tassert.Equal(t, \"application/json; charset=utf-8\", w.Header().Get(\"Content-Type\"))\n}\n\nfunc TestContextRenderNoContentAsciiJSON(t *testing.T) {\n\tw := httptest.NewRecorder()\n\tc, _ := CreateTestContext(w)\n\n\tc.AsciiJSON(http.StatusNoContent, []string{\"lang\", \"Go语言\"})\n\n\tassert.Equal(t, http.StatusNoContent, w.Code)\n\tassert.Empty(t, w.Body.String())\n\tassert.Equal(t, \"application/json\", w.Header().Get(\"Content-Type\"))\n}\n\n// Tests that the response is serialized as JSON\n// and Content-Type is set to application/json\n// and special HTML characters are preserved\nfunc TestContextRenderPureJSON(t *testing.T) {\n\tw := httptest.NewRecorder()\n\tc, _ := CreateTestContext(w)\n\tc.PureJSON(http.StatusCreated, H{\"foo\": \"bar\", \"html\": \"<b>\"})\n\tassert.Equal(t, http.StatusCreated, w.Code)\n\tassert.JSONEq(t, \"{\\\"foo\\\":\\\"bar\\\",\\\"html\\\":\\\"<b>\\\"}\\n\", w.Body.String())\n\tassert.Equal(t, \"application/json; charset=utf-8\", w.Header().Get(\"Content-Type\"))\n}\n\n// Tests that the response executes the templates\n// and responds with Content-Type set to text/html\nfunc TestContextRenderHTML(t *testing.T) {\n\tw := httptest.NewRecorder()\n\tc, router := CreateTestContext(w)\n\n\ttempl := template.Must(template.New(\"t\").Parse(`Hello {{.name}}`))\n\trouter.SetHTMLTemplate(templ)\n\n\tc.HTML(http.StatusCreated, \"t\", H{\"name\": \"alexandernyquist\"})\n\n\tassert.Equal(t, http.StatusCreated, w.Code)\n\tassert.Equal(t, \"Hello alexandernyquist\", w.Body.String())\n\tassert.Equal(t, \"text/html; charset=utf-8\", w.Header().Get(\"Content-Type\"))\n}\n\nfunc TestContextRenderHTML2(t *testing.T) {\n\tw := httptest.NewRecorder()\n\tc, router := CreateTestContext(w)\n\n\t// print debug warning log when Engine.trees > 0\n\trouter.addRoute(http.MethodGet, \"/\", HandlersChain{func(_ *Context) {}})\n\tassert.Len(t, router.trees, 1)\n\n\ttempl := template.Must(template.New(\"t\").Parse(`Hello {{.name}}`))\n\tre := captureOutput(t, func() {\n\t\tSetMode(DebugMode)\n\t\trouter.SetHTMLTemplate(templ)\n\t\tSetMode(TestMode)\n\t})\n\n\tassert.Equal(t, \"[GIN-debug] [WARNING] Since SetHTMLTemplate() is NOT thread-safe. It should only be called\\nat initialization. ie. before any route is registered or the router is listening in a socket:\\n\\n\\trouter := gin.Default()\\n\\trouter.SetHTMLTemplate(template) // << good place\\n\\n\", re)\n\n\tc.HTML(http.StatusCreated, \"t\", H{\"name\": \"alexandernyquist\"})\n\n\tassert.Equal(t, http.StatusCreated, w.Code)\n\tassert.Equal(t, \"Hello alexandernyquist\", w.Body.String())\n\tassert.Equal(t, \"text/html; charset=utf-8\", w.Header().Get(\"Content-Type\"))\n}\n\n// Tests that no HTML is rendered if code is 204\nfunc TestContextRenderNoContentHTML(t *testing.T) {\n\tw := httptest.NewRecorder()\n\tc, router := CreateTestContext(w)\n\ttempl := template.Must(template.New(\"t\").Parse(`Hello {{.name}}`))\n\trouter.SetHTMLTemplate(templ)\n\n\tc.HTML(http.StatusNoContent, \"t\", H{\"name\": \"alexandernyquist\"})\n\n\tassert.Equal(t, http.StatusNoContent, w.Code)\n\tassert.Empty(t, w.Body.String())\n\tassert.Equal(t, \"text/html; charset=utf-8\", w.Header().Get(\"Content-Type\"))\n}\n\n// TestContextRenderXML tests that the response is serialized as XML\n// and Content-Type is set to application/xml\nfunc TestContextRenderXML(t *testing.T) {\n\tw := httptest.NewRecorder()\n\tc, _ := CreateTestContext(w)\n\n\tc.XML(http.StatusCreated, H{\"foo\": \"bar\"})\n\n\tassert.Equal(t, http.StatusCreated, w.Code)\n\tassert.Equal(t, \"<map><foo>bar</foo></map>\", w.Body.String())\n\tassert.Equal(t, \"application/xml; charset=utf-8\", w.Header().Get(\"Content-Type\"))\n}\n\n// Tests that no XML is rendered if code is 204\nfunc TestContextRenderNoContentXML(t *testing.T) {\n\tw := httptest.NewRecorder()\n\tc, _ := CreateTestContext(w)\n\n\tc.XML(http.StatusNoContent, H{\"foo\": \"bar\"})\n\n\tassert.Equal(t, http.StatusNoContent, w.Code)\n\tassert.Empty(t, w.Body.String())\n\tassert.Equal(t, \"application/xml; charset=utf-8\", w.Header().Get(\"Content-Type\"))\n}\n\n// TestContextRenderPDF tests that the response is serialized as PDF\n// and Content-Type is set to application/pdf\nfunc TestContextRenderPDF(t *testing.T) {\n\tw := httptest.NewRecorder()\n\tc, _ := CreateTestContext(w)\n\n\tdata := []byte(\"%Test pdf content\")\n\tc.PDF(http.StatusCreated, data)\n\n\tassert.Equal(t, http.StatusCreated, w.Code)\n\tassert.Equal(t, data, w.Body.Bytes())\n\tassert.Equal(t, \"application/pdf\", w.Header().Get(\"Content-Type\"))\n}\n\n// Tests that no PDF is rendered if code is 204\nfunc TestContextRenderNoContentPDF(t *testing.T) {\n\tw := httptest.NewRecorder()\n\tc, _ := CreateTestContext(w)\n\n\tdata := []byte(\"%Test pdf content\")\n\tc.PDF(http.StatusNoContent, data)\n\n\tassert.Equal(t, http.StatusNoContent, w.Code)\n\tassert.Empty(t, w.Body.Bytes())\n\tassert.Equal(t, \"application/pdf\", w.Header().Get(\"Content-Type\"))\n}\n\n// TestContextRenderString tests that the response is returned\n// with Content-Type set to text/plain\nfunc TestContextRenderString(t *testing.T) {\n\tw := httptest.NewRecorder()\n\tc, _ := CreateTestContext(w)\n\n\tc.String(http.StatusCreated, \"test %s %d\", \"string\", 2)\n\n\tassert.Equal(t, http.StatusCreated, w.Code)\n\tassert.Equal(t, \"test string 2\", w.Body.String())\n\tassert.Equal(t, \"text/plain; charset=utf-8\", w.Header().Get(\"Content-Type\"))\n}\n\n// Tests that no String is rendered if code is 204\nfunc TestContextRenderNoContentString(t *testing.T) {\n\tw := httptest.NewRecorder()\n\tc, _ := CreateTestContext(w)\n\n\tc.String(http.StatusNoContent, \"test %s %d\", \"string\", 2)\n\n\tassert.Equal(t, http.StatusNoContent, w.Code)\n\tassert.Empty(t, w.Body.String())\n\tassert.Equal(t, \"text/plain; charset=utf-8\", w.Header().Get(\"Content-Type\"))\n}\n\n// TestContextRenderHTMLString tests that the response is returned\n// with Content-Type set to text/html\nfunc TestContextRenderHTMLString(t *testing.T) {\n\tw := httptest.NewRecorder()\n\tc, _ := CreateTestContext(w)\n\n\tc.Header(\"Content-Type\", \"text/html; charset=utf-8\")\n\tc.String(http.StatusCreated, \"<html>%s %d</html>\", \"string\", 3)\n\n\tassert.Equal(t, http.StatusCreated, w.Code)\n\tassert.Equal(t, \"<html>string 3</html>\", w.Body.String())\n\tassert.Equal(t, \"text/html; charset=utf-8\", w.Header().Get(\"Content-Type\"))\n}\n\n// Tests that no HTML String is rendered if code is 204\nfunc TestContextRenderNoContentHTMLString(t *testing.T) {\n\tw := httptest.NewRecorder()\n\tc, _ := CreateTestContext(w)\n\n\tc.Header(\"Content-Type\", \"text/html; charset=utf-8\")\n\tc.String(http.StatusNoContent, \"<html>%s %d</html>\", \"string\", 3)\n\n\tassert.Equal(t, http.StatusNoContent, w.Code)\n\tassert.Empty(t, w.Body.String())\n\tassert.Equal(t, \"text/html; charset=utf-8\", w.Header().Get(\"Content-Type\"))\n}\n\n// TestContextRenderData tests that the response can be written from `bytestring`\n// with specified MIME type\nfunc TestContextRenderData(t *testing.T) {\n\tw := httptest.NewRecorder()\n\tc, _ := CreateTestContext(w)\n\n\tc.Data(http.StatusCreated, \"text/csv\", []byte(`foo,bar`))\n\n\tassert.Equal(t, http.StatusCreated, w.Code)\n\tassert.Equal(t, \"foo,bar\", w.Body.String())\n\tassert.Equal(t, \"text/csv\", w.Header().Get(\"Content-Type\"))\n}\n\n// Tests that no Custom Data is rendered if code is 204\nfunc TestContextRenderNoContentData(t *testing.T) {\n\tw := httptest.NewRecorder()\n\tc, _ := CreateTestContext(w)\n\n\tc.Data(http.StatusNoContent, \"text/csv\", []byte(`foo,bar`))\n\n\tassert.Equal(t, http.StatusNoContent, w.Code)\n\tassert.Empty(t, w.Body.String())\n\tassert.Equal(t, \"text/csv\", w.Header().Get(\"Content-Type\"))\n}\n\nfunc TestContextRenderSSE(t *testing.T) {\n\tw := httptest.NewRecorder()\n\tc, _ := CreateTestContext(w)\n\n\tc.SSEvent(\"float\", 1.5)\n\tc.Render(-1, sse.Event{\n\t\tId:   \"123\",\n\t\tData: \"text\",\n\t})\n\tc.SSEvent(\"chat\", H{\n\t\t\"foo\": \"bar\",\n\t\t\"bar\": \"foo\",\n\t})\n\n\tassert.Equal(t, strings.ReplaceAll(w.Body.String(), \" \", \"\"), strings.ReplaceAll(\"event:float\\ndata:1.5\\n\\nid:123\\ndata:text\\n\\nevent:chat\\ndata:{\\\"bar\\\":\\\"foo\\\",\\\"foo\\\":\\\"bar\\\"}\\n\\n\", \" \", \"\"))\n}\n\nfunc TestContextRenderFile(t *testing.T) {\n\tw := httptest.NewRecorder()\n\tc, _ := CreateTestContext(w)\n\n\tc.Request, _ = http.NewRequest(http.MethodGet, \"/\", nil)\n\tc.File(\"./gin.go\")\n\n\tassert.Equal(t, http.StatusOK, w.Code)\n\tassert.Contains(t, w.Body.String(), \"func New(opts ...OptionFunc) *Engine {\")\n\t// Content-Type='text/plain; charset=utf-8' when go version <= 1.16,\n\t// else, Content-Type='text/x-go; charset=utf-8'\n\tassert.NotEmpty(t, w.Header().Get(\"Content-Type\"))\n}\n\nfunc TestContextRenderFileFromFS(t *testing.T) {\n\tw := httptest.NewRecorder()\n\tc, _ := CreateTestContext(w)\n\n\tc.Request, _ = http.NewRequest(http.MethodGet, \"/some/path\", nil)\n\tc.FileFromFS(\"./gin.go\", Dir(\".\", false))\n\n\tassert.Equal(t, http.StatusOK, w.Code)\n\tassert.Contains(t, w.Body.String(), \"func New(opts ...OptionFunc) *Engine {\")\n\t// Content-Type='text/plain; charset=utf-8' when go version <= 1.16,\n\t// else, Content-Type='text/x-go; charset=utf-8'\n\tassert.NotEmpty(t, w.Header().Get(\"Content-Type\"))\n\tassert.Equal(t, \"/some/path\", c.Request.URL.Path)\n}\n\nfunc TestContextRenderAttachment(t *testing.T) {\n\tw := httptest.NewRecorder()\n\tc, _ := CreateTestContext(w)\n\tnewFilename := \"new_filename.go\"\n\n\tc.Request, _ = http.NewRequest(http.MethodGet, \"/\", nil)\n\tc.FileAttachment(\"./gin.go\", newFilename)\n\n\tassert.Equal(t, 200, w.Code)\n\tassert.Contains(t, w.Body.String(), \"func New(opts ...OptionFunc) *Engine {\")\n\tassert.Equal(t, fmt.Sprintf(\"attachment; filename=\\\"%s\\\"\", newFilename), w.Header().Get(\"Content-Disposition\"))\n}\n\nfunc TestContextRenderAndEscapeAttachment(t *testing.T) {\n\tw := httptest.NewRecorder()\n\tc, _ := CreateTestContext(w)\n\tmaliciousFilename := \"tampering_field.sh\\\"; \\\\\\\"; dummy=.go\"\n\tactualEscapedResponseFilename := \"tampering_field.sh\\\\\\\"; \\\\\\\\\\\\\\\"; dummy=.go\"\n\n\tc.Request, _ = http.NewRequest(http.MethodGet, \"/\", nil)\n\tc.FileAttachment(\"./gin.go\", maliciousFilename)\n\n\tassert.Equal(t, 200, w.Code)\n\tassert.Contains(t, w.Body.String(), \"func New(opts ...OptionFunc) *Engine {\")\n\tassert.Equal(t, fmt.Sprintf(\"attachment; filename=\\\"%s\\\"\", actualEscapedResponseFilename), w.Header().Get(\"Content-Disposition\"))\n}\n\nfunc TestContextRenderUTF8Attachment(t *testing.T) {\n\tw := httptest.NewRecorder()\n\tc, _ := CreateTestContext(w)\n\tnewFilename := \"new🧡_filename.go\"\n\n\tc.Request, _ = http.NewRequest(http.MethodGet, \"/\", nil)\n\tc.FileAttachment(\"./gin.go\", newFilename)\n\n\tassert.Equal(t, 200, w.Code)\n\tassert.Contains(t, w.Body.String(), \"func New(opts ...OptionFunc) *Engine {\")\n\tassert.Equal(t, `attachment; filename*=UTF-8''`+url.QueryEscape(newFilename), w.Header().Get(\"Content-Disposition\"))\n}\n\n// TestContextRenderYAML tests that the response is serialized as YAML\n// and Content-Type is set to application/yaml\nfunc TestContextRenderYAML(t *testing.T) {\n\tw := httptest.NewRecorder()\n\tc, _ := CreateTestContext(w)\n\n\tc.YAML(http.StatusCreated, H{\"foo\": \"bar\"})\n\n\tassert.Equal(t, http.StatusCreated, w.Code)\n\tassert.Equal(t, \"foo: bar\\n\", w.Body.String())\n\tassert.Equal(t, \"application/yaml; charset=utf-8\", w.Header().Get(\"Content-Type\"))\n}\n\n// TestContextRenderTOML tests that the response is serialized as TOML\n// and Content-Type is set to application/toml\nfunc TestContextRenderTOML(t *testing.T) {\n\tw := httptest.NewRecorder()\n\tc, _ := CreateTestContext(w)\n\n\tc.TOML(http.StatusCreated, H{\"foo\": \"bar\"})\n\n\tassert.Equal(t, http.StatusCreated, w.Code)\n\tassert.Equal(t, \"foo = 'bar'\\n\", w.Body.String())\n\tassert.Equal(t, \"application/toml; charset=utf-8\", w.Header().Get(\"Content-Type\"))\n}\n\n// TestContextRenderProtoBuf tests that the response is serialized as ProtoBuf\n// and Content-Type is set to application/x-protobuf\n// and we just use the example protobuf to check if the response is correct\nfunc TestContextRenderProtoBuf(t *testing.T) {\n\tw := httptest.NewRecorder()\n\tc, _ := CreateTestContext(w)\n\n\treps := []int64{int64(1), int64(2)}\n\tlabel := \"test\"\n\tdata := &testdata.Test{\n\t\tLabel: &label,\n\t\tReps:  reps,\n\t}\n\n\tc.ProtoBuf(http.StatusCreated, data)\n\n\tprotoData, err := proto.Marshal(data)\n\trequire.NoError(t, err)\n\n\tassert.Equal(t, http.StatusCreated, w.Code)\n\tassert.Equal(t, string(protoData), w.Body.String())\n\tassert.Equal(t, \"application/x-protobuf\", w.Header().Get(\"Content-Type\"))\n}\n\nfunc TestContextHeaders(t *testing.T) {\n\tc, _ := CreateTestContext(httptest.NewRecorder())\n\tc.Header(\"Content-Type\", \"text/plain\")\n\tc.Header(\"X-Custom\", \"value\")\n\n\tassert.Equal(t, \"text/plain\", c.Writer.Header().Get(\"Content-Type\"))\n\tassert.Equal(t, \"value\", c.Writer.Header().Get(\"X-Custom\"))\n\n\tc.Header(\"Content-Type\", \"text/html\")\n\tc.Header(\"X-Custom\", \"\")\n\n\tassert.Equal(t, \"text/html\", c.Writer.Header().Get(\"Content-Type\"))\n\t_, exist := c.Writer.Header()[\"X-Custom\"]\n\tassert.False(t, exist)\n}\n\n// TODO\nfunc TestContextRenderRedirectWithRelativePath(t *testing.T) {\n\tw := httptest.NewRecorder()\n\tc, _ := CreateTestContext(w)\n\n\tc.Request, _ = http.NewRequest(http.MethodPost, \"http://example.com\", nil)\n\tassert.Panics(t, func() { c.Redirect(299, \"/new_path\") })\n\tassert.Panics(t, func() { c.Redirect(309, \"/new_path\") })\n\n\tc.Redirect(http.StatusMovedPermanently, \"/path\")\n\tc.Writer.WriteHeaderNow()\n\tassert.Equal(t, http.StatusMovedPermanently, w.Code)\n\tassert.Equal(t, \"/path\", w.Header().Get(\"Location\"))\n}\n\nfunc TestContextRenderRedirectWithAbsolutePath(t *testing.T) {\n\tw := httptest.NewRecorder()\n\tc, _ := CreateTestContext(w)\n\n\tc.Request, _ = http.NewRequest(http.MethodPost, \"http://example.com\", nil)\n\tc.Redirect(http.StatusFound, \"http://google.com\")\n\tc.Writer.WriteHeaderNow()\n\n\tassert.Equal(t, http.StatusFound, w.Code)\n\tassert.Equal(t, \"http://google.com\", w.Header().Get(\"Location\"))\n}\n\nfunc TestContextRenderRedirectWith201(t *testing.T) {\n\tw := httptest.NewRecorder()\n\tc, _ := CreateTestContext(w)\n\n\tc.Request, _ = http.NewRequest(http.MethodPost, \"http://example.com\", nil)\n\tc.Redirect(http.StatusCreated, \"/resource\")\n\tc.Writer.WriteHeaderNow()\n\n\tassert.Equal(t, http.StatusCreated, w.Code)\n\tassert.Equal(t, \"/resource\", w.Header().Get(\"Location\"))\n}\n\nfunc TestContextRenderRedirectAll(t *testing.T) {\n\tc, _ := CreateTestContext(httptest.NewRecorder())\n\tc.Request, _ = http.NewRequest(http.MethodPost, \"http://example.com\", nil)\n\tassert.Panics(t, func() { c.Redirect(http.StatusOK, \"/resource\") })\n\tassert.Panics(t, func() { c.Redirect(http.StatusAccepted, \"/resource\") })\n\tassert.Panics(t, func() { c.Redirect(299, \"/resource\") })\n\tassert.Panics(t, func() { c.Redirect(309, \"/resource\") })\n\tassert.NotPanics(t, func() { c.Redirect(http.StatusMultipleChoices, \"/resource\") })\n\tassert.NotPanics(t, func() { c.Redirect(http.StatusPermanentRedirect, \"/resource\") })\n}\n\nfunc TestContextNegotiationWithJSON(t *testing.T) {\n\tw := httptest.NewRecorder()\n\tc, _ := CreateTestContext(w)\n\tc.Request, _ = http.NewRequest(http.MethodPost, \"\", nil)\n\n\tc.Negotiate(http.StatusOK, Negotiate{\n\t\tOffered: []string{MIMEJSON, MIMEXML, MIMEYAML, MIMEYAML2},\n\t\tData:    H{\"foo\": \"bar\"},\n\t})\n\n\tassert.Equal(t, http.StatusOK, w.Code)\n\tassert.JSONEq(t, `{\"foo\":\"bar\"}`, w.Body.String())\n\tassert.Equal(t, \"application/json; charset=utf-8\", w.Header().Get(\"Content-Type\"))\n}\n\nfunc TestContextNegotiationWithXML(t *testing.T) {\n\tw := httptest.NewRecorder()\n\tc, _ := CreateTestContext(w)\n\tc.Request, _ = http.NewRequest(http.MethodPost, \"\", nil)\n\n\tc.Negotiate(http.StatusOK, Negotiate{\n\t\tOffered: []string{MIMEXML, MIMEJSON, MIMEYAML, MIMEYAML2},\n\t\tData:    H{\"foo\": \"bar\"},\n\t})\n\n\tassert.Equal(t, http.StatusOK, w.Code)\n\tassert.Equal(t, \"<map><foo>bar</foo></map>\", w.Body.String())\n\tassert.Equal(t, \"application/xml; charset=utf-8\", w.Header().Get(\"Content-Type\"))\n}\n\nfunc TestContextNegotiationWithYAML(t *testing.T) {\n\tw := httptest.NewRecorder()\n\tc, _ := CreateTestContext(w)\n\tc.Request, _ = http.NewRequest(http.MethodPost, \"\", nil)\n\n\tc.Negotiate(http.StatusOK, Negotiate{\n\t\tOffered: []string{MIMEYAML, MIMEXML, MIMEJSON, MIMETOML, MIMEYAML2},\n\t\tData:    H{\"foo\": \"bar\"},\n\t})\n\n\tassert.Equal(t, http.StatusOK, w.Code)\n\tassert.Equal(t, \"foo: bar\\n\", w.Body.String())\n\tassert.Equal(t, \"application/yaml; charset=utf-8\", w.Header().Get(\"Content-Type\"))\n}\n\nfunc TestContextNegotiationWithTOML(t *testing.T) {\n\tw := httptest.NewRecorder()\n\tc, _ := CreateTestContext(w)\n\tc.Request, _ = http.NewRequest(http.MethodPost, \"\", nil)\n\n\tc.Negotiate(http.StatusOK, Negotiate{\n\t\tOffered: []string{MIMETOML, MIMEXML, MIMEJSON, MIMEYAML, MIMEYAML2},\n\t\tData:    H{\"foo\": \"bar\"},\n\t})\n\n\tassert.Equal(t, http.StatusOK, w.Code)\n\tassert.Equal(t, \"foo = 'bar'\\n\", w.Body.String())\n\tassert.Equal(t, \"application/toml; charset=utf-8\", w.Header().Get(\"Content-Type\"))\n}\n\nfunc TestContextNegotiationWithHTML(t *testing.T) {\n\tw := httptest.NewRecorder()\n\tc, router := CreateTestContext(w)\n\tc.Request, _ = http.NewRequest(http.MethodPost, \"\", nil)\n\ttempl := template.Must(template.New(\"t\").Parse(`Hello {{.name}}`))\n\trouter.SetHTMLTemplate(templ)\n\n\tc.Negotiate(http.StatusOK, Negotiate{\n\t\tOffered:  []string{MIMEHTML},\n\t\tData:     H{\"name\": \"gin\"},\n\t\tHTMLName: \"t\",\n\t})\n\n\tassert.Equal(t, http.StatusOK, w.Code)\n\tassert.Equal(t, \"Hello gin\", w.Body.String())\n\tassert.Equal(t, \"text/html; charset=utf-8\", w.Header().Get(\"Content-Type\"))\n}\n\nfunc TestContextNegotiationWithPROTOBUF(t *testing.T) {\n\tw := httptest.NewRecorder()\n\tc, _ := CreateTestContext(w)\n\tc.Request = httptest.NewRequest(http.MethodPost, \"/\", nil)\n\n\treps := []int64{int64(1), int64(2)}\n\tlabel := \"test\"\n\tdata := &testdata.Test{\n\t\tLabel: &label,\n\t\tReps:  reps,\n\t}\n\n\tc.Negotiate(http.StatusCreated, Negotiate{\n\t\tOffered: []string{MIMEPROTOBUF, MIMEJSON, MIMEXML},\n\t\tData:    data,\n\t})\n\n\t// Marshal original data for comparison\n\tprotoData, err := proto.Marshal(data)\n\trequire.NoError(t, err)\n\n\tassert.Equal(t, http.StatusCreated, w.Code)\n\tassert.Equal(t, string(protoData), w.Body.String())\n\tassert.Equal(t, \"application/x-protobuf\", w.Header().Get(\"Content-Type\"))\n}\n\nfunc TestContextNegotiationWithBSON(t *testing.T) {\n\tw := httptest.NewRecorder()\n\tc, _ := CreateTestContext(w)\n\tc.Request, _ = http.NewRequest(http.MethodPost, \"\", nil)\n\n\tc.Negotiate(http.StatusOK, Negotiate{\n\t\tOffered: []string{MIMEBSON, MIMEXML, MIMEJSON, MIMEYAML, MIMEYAML2},\n\t\tData:    H{\"foo\": \"bar\"},\n\t})\n\n\tbData, _ := bson.Marshal(H{\"foo\": \"bar\"})\n\n\tassert.Equal(t, http.StatusOK, w.Code)\n\tassert.Equal(t, string(bData), w.Body.String())\n\tassert.Equal(t, \"application/bson\", w.Header().Get(\"Content-Type\"))\n}\n\nfunc TestContextNegotiationNotSupport(t *testing.T) {\n\tw := httptest.NewRecorder()\n\tc, _ := CreateTestContext(w)\n\tc.Request, _ = http.NewRequest(http.MethodPost, \"\", nil)\n\n\tc.Negotiate(http.StatusOK, Negotiate{\n\t\tOffered: []string{MIMEPOSTForm},\n\t})\n\n\tassert.Equal(t, http.StatusNotAcceptable, w.Code)\n\tassert.Equal(t, abortIndex, c.index)\n\tassert.True(t, c.IsAborted())\n}\n\nfunc TestContextNegotiationFormat(t *testing.T) {\n\tc, _ := CreateTestContext(httptest.NewRecorder())\n\tc.Request, _ = http.NewRequest(http.MethodPost, \"\", nil)\n\n\tassert.Panics(t, func() { c.NegotiateFormat() })\n\tassert.Equal(t, MIMEJSON, c.NegotiateFormat(MIMEJSON, MIMEXML)) //nolint:testifylint\n\tassert.Equal(t, MIMEHTML, c.NegotiateFormat(MIMEHTML, MIMEJSON))\n}\n\nfunc TestContextNegotiationFormatWithAccept(t *testing.T) {\n\tc, _ := CreateTestContext(httptest.NewRecorder())\n\tc.Request, _ = http.NewRequest(http.MethodPost, \"/\", nil)\n\tc.Request.Header.Add(\"Accept\", \"text/html,application/xhtml+xml,application/xml;q=0.9;q=0.8\")\n\n\tassert.Equal(t, MIMEXML, c.NegotiateFormat(MIMEJSON, MIMEXML))\n\tassert.Equal(t, MIMEHTML, c.NegotiateFormat(MIMEXML, MIMEHTML))\n\tassert.Empty(t, c.NegotiateFormat(MIMEJSON))\n}\n\nfunc TestContextNegotiationFormatWithWildcardAccept(t *testing.T) {\n\tc, _ := CreateTestContext(httptest.NewRecorder())\n\tc.Request, _ = http.NewRequest(http.MethodPost, \"/\", nil)\n\tc.Request.Header.Add(\"Accept\", \"*/*\")\n\n\tassert.Equal(t, \"*/*\", c.NegotiateFormat(\"*/*\"))\n\tassert.Equal(t, \"text/*\", c.NegotiateFormat(\"text/*\"))\n\tassert.Equal(t, \"application/*\", c.NegotiateFormat(\"application/*\"))\n\tassert.Equal(t, MIMEJSON, c.NegotiateFormat(MIMEJSON)) //nolint:testifylint\n\tassert.Equal(t, MIMEXML, c.NegotiateFormat(MIMEXML))\n\tassert.Equal(t, MIMEHTML, c.NegotiateFormat(MIMEHTML))\n\n\tc, _ = CreateTestContext(httptest.NewRecorder())\n\tc.Request, _ = http.NewRequest(http.MethodPost, \"/\", nil)\n\tc.Request.Header.Add(\"Accept\", \"text/*\")\n\n\tassert.Equal(t, \"*/*\", c.NegotiateFormat(\"*/*\"))\n\tassert.Equal(t, \"text/*\", c.NegotiateFormat(\"text/*\"))\n\tassert.Empty(t, c.NegotiateFormat(\"application/*\"))\n\tassert.Empty(t, c.NegotiateFormat(MIMEJSON))\n\tassert.Empty(t, c.NegotiateFormat(MIMEXML))\n\tassert.Equal(t, MIMEHTML, c.NegotiateFormat(MIMEHTML))\n}\n\nfunc TestContextNegotiationFormatCustom(t *testing.T) {\n\tc, _ := CreateTestContext(httptest.NewRecorder())\n\tc.Request, _ = http.NewRequest(http.MethodPost, \"/\", nil)\n\tc.Request.Header.Add(\"Accept\", \"text/html,application/xhtml+xml,application/xml;q=0.9;q=0.8\")\n\n\tc.Accepted = nil\n\tc.SetAccepted(MIMEJSON, MIMEXML)\n\n\tassert.Equal(t, MIMEJSON, c.NegotiateFormat(MIMEJSON, MIMEXML)) //nolint:testifylint\n\tassert.Equal(t, MIMEXML, c.NegotiateFormat(MIMEXML, MIMEHTML))\n\tassert.Equal(t, MIMEJSON, c.NegotiateFormat(MIMEJSON)) //nolint:testifylint\n}\n\nfunc TestContextNegotiationFormat2(t *testing.T) {\n\tc, _ := CreateTestContext(httptest.NewRecorder())\n\tc.Request, _ = http.NewRequest(http.MethodPost, \"/\", nil)\n\tc.Request.Header.Add(\"Accept\", \"image/tiff-fx\")\n\n\tassert.Empty(t, c.NegotiateFormat(\"image/tiff\"))\n}\n\nfunc TestContextIsAborted(t *testing.T) {\n\tc, _ := CreateTestContext(httptest.NewRecorder())\n\tassert.False(t, c.IsAborted())\n\n\tc.Abort()\n\tassert.True(t, c.IsAborted())\n\n\tc.Next()\n\tassert.True(t, c.IsAborted())\n\n\tc.index++\n\tassert.True(t, c.IsAborted())\n}\n\n// TestContextAbortWithStatus tests that the response can be written from `bytestring`\n// with specified MIME type\nfunc TestContextAbortWithStatus(t *testing.T) {\n\tw := httptest.NewRecorder()\n\tc, _ := CreateTestContext(w)\n\n\tc.index = 4\n\tc.AbortWithStatus(http.StatusUnauthorized)\n\n\tassert.Equal(t, abortIndex, c.index)\n\tassert.Equal(t, http.StatusUnauthorized, c.Writer.Status())\n\tassert.Equal(t, http.StatusUnauthorized, w.Code)\n\tassert.True(t, c.IsAborted())\n}\n\ntype testJSONAbortMsg struct {\n\tFoo string `json:\"foo\"`\n\tBar string `json:\"bar\"`\n}\n\nfunc TestContextAbortWithStatusJSON(t *testing.T) {\n\tw := httptest.NewRecorder()\n\tc, _ := CreateTestContext(w)\n\tc.index = 4\n\n\tin := new(testJSONAbortMsg)\n\tin.Bar = \"barValue\"\n\tin.Foo = \"fooValue\"\n\n\tc.AbortWithStatusJSON(http.StatusUnsupportedMediaType, in)\n\n\tassert.Equal(t, abortIndex, c.index)\n\tassert.Equal(t, http.StatusUnsupportedMediaType, c.Writer.Status())\n\tassert.Equal(t, http.StatusUnsupportedMediaType, w.Code)\n\tassert.True(t, c.IsAborted())\n\n\tcontentType := w.Header().Get(\"Content-Type\")\n\tassert.Equal(t, \"application/json; charset=utf-8\", contentType)\n\n\tbuf := new(bytes.Buffer)\n\t_, err := buf.ReadFrom(w.Body)\n\trequire.NoError(t, err)\n\tjsonStringBody := buf.String()\n\tassert.JSONEq(t, \"{\\\"foo\\\":\\\"fooValue\\\",\\\"bar\\\":\\\"barValue\\\"}\", jsonStringBody)\n}\n\nfunc TestContextAbortWithStatusPureJSON(t *testing.T) {\n\tw := httptest.NewRecorder()\n\tc, _ := CreateTestContext(w)\n\tc.index = 4\n\n\tin := new(testJSONAbortMsg)\n\tin.Bar = \"barValue\"\n\tin.Foo = \"fooValue\"\n\n\tc.AbortWithStatusPureJSON(http.StatusUnsupportedMediaType, in)\n\n\tassert.Equal(t, abortIndex, c.index)\n\tassert.Equal(t, http.StatusUnsupportedMediaType, c.Writer.Status())\n\tassert.Equal(t, http.StatusUnsupportedMediaType, w.Code)\n\tassert.True(t, c.IsAborted())\n\n\tcontentType := w.Header().Get(\"Content-Type\")\n\tassert.Equal(t, \"application/json; charset=utf-8\", contentType)\n\n\tbuf := new(bytes.Buffer)\n\t_, err := buf.ReadFrom(w.Body)\n\trequire.NoError(t, err)\n\tjsonStringBody := buf.String()\n\tassert.JSONEq(t, \"{\\\"foo\\\":\\\"fooValue\\\",\\\"bar\\\":\\\"barValue\\\"}\", jsonStringBody)\n}\n\nfunc TestContextError(t *testing.T) {\n\tc, _ := CreateTestContext(httptest.NewRecorder())\n\tassert.Empty(t, c.Errors)\n\n\tfirstErr := errors.New(\"first error\")\n\tc.Error(firstErr) //nolint: errcheck\n\tassert.Len(t, c.Errors, 1)\n\tassert.Equal(t, \"Error #01: first error\\n\", c.Errors.String())\n\n\tsecondErr := errors.New(\"second error\")\n\tc.Error(&Error{ //nolint: errcheck\n\t\tErr:  secondErr,\n\t\tMeta: \"some data 2\",\n\t\tType: ErrorTypePublic,\n\t})\n\tassert.Len(t, c.Errors, 2)\n\n\tassert.Equal(t, firstErr, c.Errors[0].Err)\n\tassert.Nil(t, c.Errors[0].Meta)\n\tassert.Equal(t, ErrorTypePrivate, c.Errors[0].Type)\n\n\tassert.Equal(t, secondErr, c.Errors[1].Err)\n\tassert.Equal(t, \"some data 2\", c.Errors[1].Meta)\n\tassert.Equal(t, ErrorTypePublic, c.Errors[1].Type)\n\n\tassert.Equal(t, c.Errors.Last(), c.Errors[1])\n\n\tdefer func() {\n\t\tif recover() == nil {\n\t\t\tt.Error(\"didn't panic\")\n\t\t}\n\t}()\n\tc.Error(nil) //nolint: errcheck\n}\n\nfunc TestContextTypedError(t *testing.T) {\n\tc, _ := CreateTestContext(httptest.NewRecorder())\n\tc.Error(errors.New(\"externo 0\")).SetType(ErrorTypePublic)  //nolint: errcheck\n\tc.Error(errors.New(\"interno 0\")).SetType(ErrorTypePrivate) //nolint: errcheck\n\n\tfor _, err := range c.Errors.ByType(ErrorTypePublic) {\n\t\tassert.Equal(t, ErrorTypePublic, err.Type)\n\t}\n\tfor _, err := range c.Errors.ByType(ErrorTypePrivate) {\n\t\tassert.Equal(t, ErrorTypePrivate, err.Type)\n\t}\n\tassert.Equal(t, []string{\"externo 0\", \"interno 0\"}, c.Errors.Errors())\n}\n\nfunc TestContextAbortWithError(t *testing.T) {\n\tw := httptest.NewRecorder()\n\tc, _ := CreateTestContext(w)\n\n\tc.AbortWithError(http.StatusUnauthorized, errors.New(\"bad input\")).SetMeta(\"some input\") //nolint: errcheck\n\n\tassert.Equal(t, http.StatusUnauthorized, w.Code)\n\tassert.Equal(t, abortIndex, c.index)\n\tassert.True(t, c.IsAborted())\n}\n\nfunc TestContextClientIP(t *testing.T) {\n\tc, _ := CreateTestContext(httptest.NewRecorder())\n\tc.Request, _ = http.NewRequest(http.MethodPost, \"/\", nil)\n\tc.engine.trustedCIDRs, _ = c.engine.prepareTrustedCIDRs()\n\tresetContextForClientIPTests(c)\n\n\t// unix address\n\taddr := &net.UnixAddr{Net: \"unix\", Name: \"@\"}\n\tc.Request = c.Request.WithContext(context.WithValue(c.Request.Context(), http.LocalAddrContextKey, addr))\n\tc.Request.RemoteAddr = addr.String()\n\tassert.Equal(t, \"20.20.20.20\", c.ClientIP())\n\n\t// reset\n\tc.Request = c.Request.WithContext(context.Background())\n\tresetContextForClientIPTests(c)\n\n\t// Legacy tests (validating that the defaults don't break the\n\t// (insecure!) old behaviour)\n\tassert.Equal(t, \"20.20.20.20\", c.ClientIP())\n\n\tc.Request.Header.Del(\"X-Forwarded-For\")\n\tassert.Equal(t, \"10.10.10.10\", c.ClientIP())\n\n\tc.Request.Header.Set(\"X-Forwarded-For\", \"30.30.30.30  \")\n\tassert.Equal(t, \"30.30.30.30\", c.ClientIP())\n\n\tc.Request.Header.Del(\"X-Forwarded-For\")\n\tc.Request.Header.Del(\"X-Real-IP\")\n\tc.engine.TrustedPlatform = PlatformGoogleAppEngine\n\tassert.Equal(t, \"50.50.50.50\", c.ClientIP())\n\n\tc.Request.Header.Del(\"X-Appengine-Remote-Addr\")\n\tassert.Equal(t, \"40.40.40.40\", c.ClientIP())\n\n\t// no port\n\tc.Request.RemoteAddr = \"50.50.50.50\"\n\tassert.Empty(t, c.ClientIP())\n\n\t// Tests exercising the TrustedProxies functionality\n\tresetContextForClientIPTests(c)\n\n\t// IPv6 support\n\tc.Request.RemoteAddr = fmt.Sprintf(\"[%s]:12345\", localhostIPv6)\n\tassert.Equal(t, \"20.20.20.20\", c.ClientIP())\n\n\tresetContextForClientIPTests(c)\n\t// No trusted proxies\n\t_ = c.engine.SetTrustedProxies([]string{})\n\tc.engine.RemoteIPHeaders = []string{\"X-Forwarded-For\"}\n\tassert.Equal(t, \"40.40.40.40\", c.ClientIP())\n\n\t// Disabled TrustedProxies feature\n\t_ = c.engine.SetTrustedProxies(nil)\n\tassert.Equal(t, \"40.40.40.40\", c.ClientIP())\n\n\t// Last proxy is trusted, but the RemoteAddr is not\n\t_ = c.engine.SetTrustedProxies([]string{\"30.30.30.30\"})\n\tassert.Equal(t, \"40.40.40.40\", c.ClientIP())\n\n\t// Only trust RemoteAddr\n\t_ = c.engine.SetTrustedProxies([]string{\"40.40.40.40\"})\n\tassert.Equal(t, \"30.30.30.30\", c.ClientIP())\n\n\t// All steps are trusted\n\t_ = c.engine.SetTrustedProxies([]string{\"40.40.40.40\", \"30.30.30.30\", \"20.20.20.20\"})\n\tassert.Equal(t, \"20.20.20.20\", c.ClientIP())\n\n\t// Use CIDR\n\t_ = c.engine.SetTrustedProxies([]string{\"40.40.25.25/16\", \"30.30.30.30\"})\n\tassert.Equal(t, \"20.20.20.20\", c.ClientIP())\n\n\t// Use hostname that resolves to all the proxies\n\t_ = c.engine.SetTrustedProxies([]string{\"foo\"})\n\tassert.Equal(t, \"40.40.40.40\", c.ClientIP())\n\n\t// Use hostname that returns an error\n\t_ = c.engine.SetTrustedProxies([]string{\"bar\"})\n\tassert.Equal(t, \"40.40.40.40\", c.ClientIP())\n\n\t// X-Forwarded-For has a non-IP element\n\t_ = c.engine.SetTrustedProxies([]string{\"40.40.40.40\"})\n\tc.Request.Header.Set(\"X-Forwarded-For\", \" blah \")\n\tassert.Equal(t, \"40.40.40.40\", c.ClientIP())\n\n\t// Result from LookupHost has non-IP element. This should never\n\t// happen, but we should test it to make sure we handle it\n\t// gracefully.\n\t_ = c.engine.SetTrustedProxies([]string{\"baz\"})\n\tc.Request.Header.Set(\"X-Forwarded-For\", \" 30.30.30.30 \")\n\tassert.Equal(t, \"40.40.40.40\", c.ClientIP())\n\n\t_ = c.engine.SetTrustedProxies([]string{\"40.40.40.40\"})\n\tc.Request.Header.Del(\"X-Forwarded-For\")\n\tc.engine.RemoteIPHeaders = []string{\"X-Forwarded-For\", \"X-Real-IP\"}\n\tassert.Equal(t, \"10.10.10.10\", c.ClientIP())\n\n\tc.engine.RemoteIPHeaders = []string{}\n\tc.engine.TrustedPlatform = PlatformGoogleAppEngine\n\tassert.Equal(t, \"50.50.50.50\", c.ClientIP())\n\n\t// Use custom TrustedPlatform header\n\tc.engine.TrustedPlatform = \"X-CDN-IP\"\n\tc.Request.Header.Set(\"X-CDN-IP\", \"80.80.80.80\")\n\tassert.Equal(t, \"80.80.80.80\", c.ClientIP())\n\t// wrong header\n\tc.engine.TrustedPlatform = \"X-Wrong-Header\"\n\tassert.Equal(t, \"40.40.40.40\", c.ClientIP())\n\n\tc.Request.Header.Del(\"X-CDN-IP\")\n\t// TrustedPlatform is empty\n\tc.engine.TrustedPlatform = \"\"\n\tassert.Equal(t, \"40.40.40.40\", c.ClientIP())\n\n\t// Test the legacy flag\n\tc.engine.AppEngine = true\n\tassert.Equal(t, \"50.50.50.50\", c.ClientIP())\n\tc.engine.AppEngine = false\n\tc.engine.TrustedPlatform = PlatformGoogleAppEngine\n\n\tc.Request.Header.Del(\"X-Appengine-Remote-Addr\")\n\tassert.Equal(t, \"40.40.40.40\", c.ClientIP())\n\n\tc.engine.TrustedPlatform = PlatformCloudflare\n\tassert.Equal(t, \"60.60.60.60\", c.ClientIP())\n\n\tc.Request.Header.Del(\"CF-Connecting-IP\")\n\tassert.Equal(t, \"40.40.40.40\", c.ClientIP())\n\n\tc.engine.TrustedPlatform = PlatformFlyIO\n\tassert.Equal(t, \"70.70.70.70\", c.ClientIP())\n\n\tc.Request.Header.Del(\"Fly-Client-IP\")\n\tassert.Equal(t, \"40.40.40.40\", c.ClientIP())\n\n\tc.engine.TrustedPlatform = \"\"\n\n\t// no port\n\tc.Request.RemoteAddr = \"50.50.50.50\"\n\tassert.Empty(t, c.ClientIP())\n}\n\nfunc resetContextForClientIPTests(c *Context) {\n\tc.Request.Header.Set(\"X-Real-IP\", \" 10.10.10.10  \")\n\tc.Request.Header.Set(\"X-Forwarded-For\", \"  20.20.20.20, 30.30.30.30\")\n\tc.Request.Header.Set(\"X-Appengine-Remote-Addr\", \"50.50.50.50\")\n\tc.Request.Header.Set(\"CF-Connecting-IP\", \"60.60.60.60\")\n\tc.Request.Header.Set(\"Fly-Client-IP\", \"70.70.70.70\")\n\tc.Request.RemoteAddr = \"  40.40.40.40:42123 \"\n\tc.engine.TrustedPlatform = \"\"\n\tc.engine.trustedCIDRs = defaultTrustedCIDRs\n\tc.engine.AppEngine = false\n}\n\nfunc TestContextContentType(t *testing.T) {\n\tc, _ := CreateTestContext(httptest.NewRecorder())\n\tc.Request, _ = http.NewRequest(http.MethodPost, \"/\", nil)\n\tc.Request.Header.Set(\"Content-Type\", \"application/json; charset=utf-8\")\n\n\tassert.Equal(t, \"application/json\", c.ContentType())\n}\n\nfunc TestContextBindRequestTooLarge(t *testing.T) {\n\t// When using go-json as JSON encoder, they do not propagate the http.MaxBytesError error\n\t// The response will fail with a generic 400 instead of 413\n\t// https://github.com/goccy/go-json/issues/485\n\tvar expectedCode int\n\tswitch json.Package {\n\tcase \"github.com/goccy/go-json\":\n\t\texpectedCode = http.StatusBadRequest\n\tdefault:\n\t\texpectedCode = http.StatusRequestEntityTooLarge\n\t}\n\n\tw := httptest.NewRecorder()\n\tc, _ := CreateTestContext(w)\n\n\tc.Request, _ = http.NewRequest(http.MethodPost, \"/\", strings.NewReader(`{\"foo\":\"bar\", \"bar\":\"foo\"}`))\n\tc.Request.Body = http.MaxBytesReader(c.Writer, c.Request.Body, 10)\n\n\tvar obj struct {\n\t\tFoo string `json:\"foo\"`\n\t\tBar string `json:\"bar\"`\n\t}\n\trequire.Error(t, c.BindJSON(&obj))\n\tc.Writer.WriteHeaderNow()\n\n\tassert.Empty(t, obj.Bar)\n\tassert.Empty(t, obj.Foo)\n\tassert.Equal(t, expectedCode, w.Code)\n\tassert.True(t, c.IsAborted())\n}\n\nfunc TestContextAutoBindJSON(t *testing.T) {\n\tc, _ := CreateTestContext(httptest.NewRecorder())\n\tc.Request, _ = http.NewRequest(http.MethodPost, \"/\", strings.NewReader(`{\"foo\":\"bar\", \"bar\":\"foo\"}`))\n\tc.Request.Header.Add(\"Content-Type\", MIMEJSON)\n\n\tvar obj struct {\n\t\tFoo string `json:\"foo\"`\n\t\tBar string `json:\"bar\"`\n\t}\n\trequire.NoError(t, c.Bind(&obj))\n\tassert.Equal(t, \"foo\", obj.Bar)\n\tassert.Equal(t, \"bar\", obj.Foo)\n\tassert.Empty(t, c.Errors)\n}\n\nfunc TestContextBindWithJSON(t *testing.T) {\n\tw := httptest.NewRecorder()\n\tc, _ := CreateTestContext(w)\n\n\tc.Request, _ = http.NewRequest(http.MethodPost, \"/\", strings.NewReader(`{\"foo\":\"bar\", \"bar\":\"foo\"}`))\n\tc.Request.Header.Add(\"Content-Type\", MIMEXML) // set fake content-type\n\n\tvar obj struct {\n\t\tFoo string `json:\"foo\"`\n\t\tBar string `json:\"bar\"`\n\t}\n\trequire.NoError(t, c.BindJSON(&obj))\n\tassert.Equal(t, \"foo\", obj.Bar)\n\tassert.Equal(t, \"bar\", obj.Foo)\n\tassert.Equal(t, 0, w.Body.Len())\n}\n\nfunc TestContextBindWithXML(t *testing.T) {\n\tw := httptest.NewRecorder()\n\tc, _ := CreateTestContext(w)\n\n\tc.Request, _ = http.NewRequest(http.MethodPost, \"/\", strings.NewReader(`<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n\t\t<root>\n\t\t\t<foo>FOO</foo>\n\t\t   \t<bar>BAR</bar>\n\t\t</root>`))\n\tc.Request.Header.Add(\"Content-Type\", MIMEXML) // set fake content-type\n\n\tvar obj struct {\n\t\tFoo string `xml:\"foo\"`\n\t\tBar string `xml:\"bar\"`\n\t}\n\trequire.NoError(t, c.BindXML(&obj))\n\tassert.Equal(t, \"FOO\", obj.Foo)\n\tassert.Equal(t, \"BAR\", obj.Bar)\n\tassert.Equal(t, 0, w.Body.Len())\n}\n\nfunc TestContextBindPlain(t *testing.T) {\n\t// string\n\tw := httptest.NewRecorder()\n\tc, _ := CreateTestContext(w)\n\tc.Request, _ = http.NewRequest(http.MethodPost, \"/\", bytes.NewBufferString(`test string`))\n\tc.Request.Header.Add(\"Content-Type\", MIMEPlain)\n\n\tvar s string\n\n\trequire.NoError(t, c.BindPlain(&s))\n\tassert.Equal(t, \"test string\", s)\n\tassert.Equal(t, 0, w.Body.Len())\n\n\t// []byte\n\tc.Request, _ = http.NewRequest(http.MethodPost, \"/\", bytes.NewBufferString(`test []byte`))\n\tc.Request.Header.Add(\"Content-Type\", MIMEPlain)\n\n\tvar bs []byte\n\n\trequire.NoError(t, c.BindPlain(&bs))\n\tassert.Equal(t, []byte(\"test []byte\"), bs)\n\tassert.Equal(t, 0, w.Body.Len())\n}\n\nfunc TestContextBindHeader(t *testing.T) {\n\tw := httptest.NewRecorder()\n\tc, _ := CreateTestContext(w)\n\n\tc.Request, _ = http.NewRequest(http.MethodPost, \"/\", nil)\n\tc.Request.Header.Add(\"rate\", \"8000\")\n\tc.Request.Header.Add(\"domain\", \"music\")\n\tc.Request.Header.Add(\"limit\", \"1000\")\n\n\tvar testHeader struct {\n\t\tRate   int    `header:\"Rate\"`\n\t\tDomain string `header:\"Domain\"`\n\t\tLimit  int    `header:\"limit\"`\n\t}\n\n\trequire.NoError(t, c.BindHeader(&testHeader))\n\tassert.Equal(t, 8000, testHeader.Rate)\n\tassert.Equal(t, \"music\", testHeader.Domain)\n\tassert.Equal(t, 1000, testHeader.Limit)\n\tassert.Equal(t, 0, w.Body.Len())\n}\n\nfunc TestContextBindWithQuery(t *testing.T) {\n\tw := httptest.NewRecorder()\n\tc, _ := CreateTestContext(w)\n\n\tc.Request, _ = http.NewRequest(http.MethodPost, \"/?foo=bar&bar=foo\", strings.NewReader(\"foo=unused\"))\n\n\tvar obj struct {\n\t\tFoo string `form:\"foo\"`\n\t\tBar string `form:\"bar\"`\n\t}\n\trequire.NoError(t, c.BindQuery(&obj))\n\tassert.Equal(t, \"foo\", obj.Bar)\n\tassert.Equal(t, \"bar\", obj.Foo)\n\tassert.Equal(t, 0, w.Body.Len())\n}\n\nfunc TestContextBindWithYAML(t *testing.T) {\n\tw := httptest.NewRecorder()\n\tc, _ := CreateTestContext(w)\n\n\tc.Request, _ = http.NewRequest(http.MethodPost, \"/\", strings.NewReader(\"foo: bar\\nbar: foo\"))\n\tc.Request.Header.Add(\"Content-Type\", MIMEXML) // set fake content-type\n\n\tvar obj struct {\n\t\tFoo string `yaml:\"foo\"`\n\t\tBar string `yaml:\"bar\"`\n\t}\n\trequire.NoError(t, c.BindYAML(&obj))\n\tassert.Equal(t, \"foo\", obj.Bar)\n\tassert.Equal(t, \"bar\", obj.Foo)\n\tassert.Equal(t, 0, w.Body.Len())\n}\n\nfunc TestContextBindWithTOML(t *testing.T) {\n\tw := httptest.NewRecorder()\n\tc, _ := CreateTestContext(w)\n\n\tc.Request, _ = http.NewRequest(http.MethodPost, \"/\", strings.NewReader(\"foo = 'bar'\\nbar = 'foo'\"))\n\tc.Request.Header.Add(\"Content-Type\", MIMEXML) // set fake content-type\n\n\tvar obj struct {\n\t\tFoo string `toml:\"foo\"`\n\t\tBar string `toml:\"bar\"`\n\t}\n\trequire.NoError(t, c.BindTOML(&obj))\n\tassert.Equal(t, \"foo\", obj.Bar)\n\tassert.Equal(t, \"bar\", obj.Foo)\n\tassert.Equal(t, 0, w.Body.Len())\n}\n\nfunc TestContextBadAutoBind(t *testing.T) {\n\tw := httptest.NewRecorder()\n\tc, _ := CreateTestContext(w)\n\n\tc.Request, _ = http.NewRequest(http.MethodPost, \"http://example.com\", strings.NewReader(\"\\\"foo\\\":\\\"bar\\\", \\\"bar\\\":\\\"foo\\\"}\"))\n\tc.Request.Header.Add(\"Content-Type\", MIMEJSON)\n\tvar obj struct {\n\t\tFoo string `json:\"foo\"`\n\t\tBar string `json:\"bar\"`\n\t}\n\n\tassert.False(t, c.IsAborted())\n\trequire.Error(t, c.Bind(&obj))\n\tc.Writer.WriteHeaderNow()\n\n\tassert.Empty(t, obj.Bar)\n\tassert.Empty(t, obj.Foo)\n\tassert.Equal(t, http.StatusBadRequest, w.Code)\n\tassert.True(t, c.IsAborted())\n}\n\nfunc TestContextAutoShouldBindJSON(t *testing.T) {\n\tc, _ := CreateTestContext(httptest.NewRecorder())\n\tc.Request, _ = http.NewRequest(http.MethodPost, \"/\", strings.NewReader(`{\"foo\":\"bar\", \"bar\":\"foo\"}`))\n\tc.Request.Header.Add(\"Content-Type\", MIMEJSON)\n\n\tvar obj struct {\n\t\tFoo string `json:\"foo\"`\n\t\tBar string `json:\"bar\"`\n\t}\n\trequire.NoError(t, c.ShouldBind(&obj))\n\tassert.Equal(t, \"foo\", obj.Bar)\n\tassert.Equal(t, \"bar\", obj.Foo)\n\tassert.Empty(t, c.Errors)\n}\n\nfunc TestContextShouldBindWithJSON(t *testing.T) {\n\tw := httptest.NewRecorder()\n\tc, _ := CreateTestContext(w)\n\n\tc.Request, _ = http.NewRequest(http.MethodPost, \"/\", strings.NewReader(`{\"foo\":\"bar\", \"bar\":\"foo\"}`))\n\tc.Request.Header.Add(\"Content-Type\", MIMEXML) // set fake content-type\n\n\tvar obj struct {\n\t\tFoo string `json:\"foo\"`\n\t\tBar string `json:\"bar\"`\n\t}\n\trequire.NoError(t, c.ShouldBindJSON(&obj))\n\tassert.Equal(t, \"foo\", obj.Bar)\n\tassert.Equal(t, \"bar\", obj.Foo)\n\tassert.Equal(t, 0, w.Body.Len())\n}\n\nfunc TestContextShouldBindWithXML(t *testing.T) {\n\tw := httptest.NewRecorder()\n\tc, _ := CreateTestContext(w)\n\n\tc.Request, _ = http.NewRequest(http.MethodPost, \"/\", strings.NewReader(`<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n\t\t<root>\n\t\t\t<foo>FOO</foo>\n\t\t\t<bar>BAR</bar>\n\t\t</root>`))\n\tc.Request.Header.Add(\"Content-Type\", MIMEXML) // set fake content-type\n\n\tvar obj struct {\n\t\tFoo string `xml:\"foo\"`\n\t\tBar string `xml:\"bar\"`\n\t}\n\trequire.NoError(t, c.ShouldBindXML(&obj))\n\tassert.Equal(t, \"FOO\", obj.Foo)\n\tassert.Equal(t, \"BAR\", obj.Bar)\n\tassert.Equal(t, 0, w.Body.Len())\n}\n\nfunc TestContextShouldBindPlain(t *testing.T) {\n\t// string\n\tw := httptest.NewRecorder()\n\tc, _ := CreateTestContext(w)\n\tc.Request, _ = http.NewRequest(http.MethodPost, \"/\", bytes.NewBufferString(`test string`))\n\tc.Request.Header.Add(\"Content-Type\", MIMEPlain)\n\n\tvar s string\n\n\trequire.NoError(t, c.ShouldBindPlain(&s))\n\tassert.Equal(t, \"test string\", s)\n\tassert.Equal(t, 0, w.Body.Len())\n\t// []byte\n\n\tc.Request, _ = http.NewRequest(http.MethodPost, \"/\", bytes.NewBufferString(`test []byte`))\n\tc.Request.Header.Add(\"Content-Type\", MIMEPlain)\n\n\tvar bs []byte\n\n\trequire.NoError(t, c.ShouldBindPlain(&bs))\n\tassert.Equal(t, []byte(\"test []byte\"), bs)\n\tassert.Equal(t, 0, w.Body.Len())\n}\n\nfunc TestContextShouldBindHeader(t *testing.T) {\n\tw := httptest.NewRecorder()\n\tc, _ := CreateTestContext(w)\n\n\tc.Request, _ = http.NewRequest(http.MethodPost, \"/\", nil)\n\tc.Request.Header.Add(\"rate\", \"8000\")\n\tc.Request.Header.Add(\"domain\", \"music\")\n\tc.Request.Header.Add(\"limit\", \"1000\")\n\n\tvar testHeader struct {\n\t\tRate   int    `header:\"Rate\"`\n\t\tDomain string `header:\"Domain\"`\n\t\tLimit  int    `header:\"limit\"`\n\t}\n\n\trequire.NoError(t, c.ShouldBindHeader(&testHeader))\n\tassert.Equal(t, 8000, testHeader.Rate)\n\tassert.Equal(t, \"music\", testHeader.Domain)\n\tassert.Equal(t, 1000, testHeader.Limit)\n\tassert.Equal(t, 0, w.Body.Len())\n}\n\nfunc TestContextShouldBindWithQuery(t *testing.T) {\n\tw := httptest.NewRecorder()\n\tc, _ := CreateTestContext(w)\n\n\tc.Request, _ = http.NewRequest(http.MethodPost, \"/?foo=bar&bar=foo&Foo=bar1&Bar=foo1\", strings.NewReader(\"foo=unused\"))\n\n\tvar obj struct {\n\t\tFoo  string `form:\"foo\"`\n\t\tBar  string `form:\"bar\"`\n\t\tFoo1 string `form:\"Foo\"`\n\t\tBar1 string `form:\"Bar\"`\n\t}\n\trequire.NoError(t, c.ShouldBindQuery(&obj))\n\tassert.Equal(t, \"foo\", obj.Bar)\n\tassert.Equal(t, \"bar\", obj.Foo)\n\tassert.Equal(t, \"foo1\", obj.Bar1)\n\tassert.Equal(t, \"bar1\", obj.Foo1)\n\tassert.Equal(t, 0, w.Body.Len())\n}\n\nfunc TestContextShouldBindWithYAML(t *testing.T) {\n\tw := httptest.NewRecorder()\n\tc, _ := CreateTestContext(w)\n\n\tc.Request, _ = http.NewRequest(http.MethodPost, \"/\", strings.NewReader(\"foo: bar\\nbar: foo\"))\n\tc.Request.Header.Add(\"Content-Type\", MIMEXML) // set fake content-type\n\n\tvar obj struct {\n\t\tFoo string `yaml:\"foo\"`\n\t\tBar string `yaml:\"bar\"`\n\t}\n\trequire.NoError(t, c.ShouldBindYAML(&obj))\n\tassert.Equal(t, \"foo\", obj.Bar)\n\tassert.Equal(t, \"bar\", obj.Foo)\n\tassert.Equal(t, 0, w.Body.Len())\n}\n\nfunc TestContextShouldBindWithTOML(t *testing.T) {\n\tw := httptest.NewRecorder()\n\tc, _ := CreateTestContext(w)\n\n\tc.Request, _ = http.NewRequest(http.MethodPost, \"/\", strings.NewReader(\"foo='bar'\\nbar= 'foo'\"))\n\tc.Request.Header.Add(\"Content-Type\", MIMETOML) // set fake content-type\n\n\tvar obj struct {\n\t\tFoo string `toml:\"foo\"`\n\t\tBar string `toml:\"bar\"`\n\t}\n\trequire.NoError(t, c.ShouldBindTOML(&obj))\n\tassert.Equal(t, \"foo\", obj.Bar)\n\tassert.Equal(t, \"bar\", obj.Foo)\n\tassert.Equal(t, 0, w.Body.Len())\n}\n\nfunc TestContextBadAutoShouldBind(t *testing.T) {\n\tw := httptest.NewRecorder()\n\tc, _ := CreateTestContext(w)\n\n\tc.Request, _ = http.NewRequest(http.MethodPost, \"http://example.com\", strings.NewReader(`\"foo\":\"bar\", \"bar\":\"foo\"}`))\n\tc.Request.Header.Add(\"Content-Type\", MIMEJSON)\n\tvar obj struct {\n\t\tFoo string `json:\"foo\"`\n\t\tBar string `json:\"bar\"`\n\t}\n\n\tassert.False(t, c.IsAborted())\n\trequire.Error(t, c.ShouldBind(&obj))\n\n\tassert.Empty(t, obj.Bar)\n\tassert.Empty(t, obj.Foo)\n\tassert.False(t, c.IsAborted())\n}\n\nfunc TestContextShouldBindBodyWith(t *testing.T) {\n\ttype typeA struct {\n\t\tFoo string `json:\"foo\" xml:\"foo\" binding:\"required\"`\n\t}\n\ttype typeB struct {\n\t\tBar string `json:\"bar\" xml:\"bar\" binding:\"required\"`\n\t}\n\tfor _, tt := range []struct {\n\t\tname               string\n\t\tbindingA, bindingB binding.BindingBody\n\t\tbodyA, bodyB       string\n\t}{\n\t\t{\n\t\t\tname:     \"JSON & JSON\",\n\t\t\tbindingA: binding.JSON,\n\t\t\tbindingB: binding.JSON,\n\t\t\tbodyA:    `{\"foo\":\"FOO\"}`,\n\t\t\tbodyB:    `{\"bar\":\"BAR\"}`,\n\t\t},\n\t\t{\n\t\t\tname:     \"JSON & XML\",\n\t\t\tbindingA: binding.JSON,\n\t\t\tbindingB: binding.XML,\n\t\t\tbodyA:    `{\"foo\":\"FOO\"}`,\n\t\t\tbodyB: `<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<root>\n   <bar>BAR</bar>\n</root>`,\n\t\t},\n\t\t{\n\t\t\tname:     \"XML & XML\",\n\t\t\tbindingA: binding.XML,\n\t\t\tbindingB: binding.XML,\n\t\t\tbodyA: `<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<root>\n   <foo>FOO</foo>\n</root>`,\n\t\t\tbodyB: `<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<root>\n   <bar>BAR</bar>\n</root>`,\n\t\t},\n\t} {\n\t\tt.Logf(\"testing: %s\", tt.name)\n\t\t// bodyA to typeA and typeB\n\t\t{\n\t\t\tw := httptest.NewRecorder()\n\t\t\tc, _ := CreateTestContext(w)\n\t\t\tc.Request, _ = http.NewRequest(\n\t\t\t\thttp.MethodPost, \"http://example.com\", strings.NewReader(tt.bodyA),\n\t\t\t)\n\t\t\t// When it binds to typeA and typeB, it finds the body is\n\t\t\t// not typeB but typeA.\n\t\t\tobjA := typeA{}\n\t\t\trequire.NoError(t, c.ShouldBindBodyWith(&objA, tt.bindingA))\n\t\t\tassert.Equal(t, typeA{\"FOO\"}, objA)\n\t\t\tobjB := typeB{}\n\t\t\trequire.Error(t, c.ShouldBindBodyWith(&objB, tt.bindingB))\n\t\t\tassert.NotEqual(t, typeB{\"BAR\"}, objB)\n\t\t}\n\t\t// bodyB to typeA and typeB\n\t\t{\n\t\t\t// When it binds to typeA and typeB, it finds the body is\n\t\t\t// not typeA but typeB.\n\t\t\tw := httptest.NewRecorder()\n\t\t\tc, _ := CreateTestContext(w)\n\t\t\tc.Request, _ = http.NewRequest(\n\t\t\t\thttp.MethodPost, \"http://example.com\", strings.NewReader(tt.bodyB),\n\t\t\t)\n\t\t\tobjA := typeA{}\n\t\t\trequire.Error(t, c.ShouldBindBodyWith(&objA, tt.bindingA))\n\t\t\tassert.NotEqual(t, typeA{\"FOO\"}, objA)\n\t\t\tobjB := typeB{}\n\t\t\trequire.NoError(t, c.ShouldBindBodyWith(&objB, tt.bindingB))\n\t\t\tassert.Equal(t, typeB{\"BAR\"}, objB)\n\t\t}\n\t}\n}\n\nfunc TestContextShouldBindBodyWithJSON(t *testing.T) {\n\tfor _, tt := range []struct {\n\t\tname        string\n\t\tbindingBody binding.BindingBody\n\t\tbody        string\n\t}{\n\t\t{\n\t\t\tname:        \" JSON & JSON-BODY \",\n\t\t\tbindingBody: binding.JSON,\n\t\t\tbody:        `{\"foo\":\"FOO\"}`,\n\t\t},\n\t\t{\n\t\t\tname:        \" JSON & XML-BODY \",\n\t\t\tbindingBody: binding.XML,\n\t\t\tbody: `<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<root>\n<foo>FOO</foo>\n</root>`,\n\t\t},\n\t\t{\n\t\t\tname:        \" JSON & YAML-BODY \",\n\t\t\tbindingBody: binding.YAML,\n\t\t\tbody:        `foo: FOO`,\n\t\t},\n\t\t{\n\t\t\tname:        \" JSON & TOM-BODY \",\n\t\t\tbindingBody: binding.TOML,\n\t\t\tbody:        `foo=FOO`,\n\t\t},\n\t} {\n\t\tt.Logf(\"testing: %s\", tt.name)\n\n\t\tw := httptest.NewRecorder()\n\t\tc, _ := CreateTestContext(w)\n\n\t\tc.Request, _ = http.NewRequest(http.MethodPost, \"/\", bytes.NewBufferString(tt.body))\n\n\t\ttype typeJSON struct {\n\t\t\tFoo string `json:\"foo\" binding:\"required\"`\n\t\t}\n\t\tobjJSON := typeJSON{}\n\n\t\tif tt.bindingBody == binding.JSON {\n\t\t\trequire.NoError(t, c.ShouldBindBodyWithJSON(&objJSON))\n\t\t\tassert.Equal(t, typeJSON{\"FOO\"}, objJSON)\n\t\t}\n\n\t\tif tt.bindingBody == binding.XML {\n\t\t\trequire.Error(t, c.ShouldBindBodyWithJSON(&objJSON))\n\t\t\tassert.Equal(t, typeJSON{}, objJSON)\n\t\t}\n\n\t\tif tt.bindingBody == binding.YAML {\n\t\t\trequire.Error(t, c.ShouldBindBodyWithJSON(&objJSON))\n\t\t\tassert.Equal(t, typeJSON{}, objJSON)\n\t\t}\n\n\t\tif tt.bindingBody == binding.TOML {\n\t\t\trequire.Error(t, c.ShouldBindBodyWithJSON(&objJSON))\n\t\t\tassert.Equal(t, typeJSON{}, objJSON)\n\t\t}\n\t}\n}\n\nfunc TestContextShouldBindBodyWithXML(t *testing.T) {\n\tfor _, tt := range []struct {\n\t\tname        string\n\t\tbindingBody binding.BindingBody\n\t\tbody        string\n\t}{\n\t\t{\n\t\t\tname:        \" XML & JSON-BODY \",\n\t\t\tbindingBody: binding.JSON,\n\t\t\tbody:        `{\"foo\":\"FOO\"}`,\n\t\t},\n\t\t{\n\t\t\tname:        \" XML & XML-BODY \",\n\t\t\tbindingBody: binding.XML,\n\t\t\tbody: `<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<root>\n<foo>FOO</foo>\n</root>`,\n\t\t},\n\t\t{\n\t\t\tname:        \" XML & YAML-BODY \",\n\t\t\tbindingBody: binding.YAML,\n\t\t\tbody:        `foo: FOO`,\n\t\t},\n\t\t{\n\t\t\tname:        \" XML & TOM-BODY \",\n\t\t\tbindingBody: binding.TOML,\n\t\t\tbody:        `foo=FOO`,\n\t\t},\n\t} {\n\t\tt.Logf(\"testing: %s\", tt.name)\n\n\t\tw := httptest.NewRecorder()\n\t\tc, _ := CreateTestContext(w)\n\n\t\tc.Request, _ = http.NewRequest(http.MethodPost, \"/\", bytes.NewBufferString(tt.body))\n\n\t\ttype typeXML struct {\n\t\t\tFoo string `xml:\"foo\" binding:\"required\"`\n\t\t}\n\t\tobjXML := typeXML{}\n\n\t\tif tt.bindingBody == binding.JSON {\n\t\t\trequire.Error(t, c.ShouldBindBodyWithXML(&objXML))\n\t\t\tassert.Equal(t, typeXML{}, objXML)\n\t\t}\n\n\t\tif tt.bindingBody == binding.XML {\n\t\t\trequire.NoError(t, c.ShouldBindBodyWithXML(&objXML))\n\t\t\tassert.Equal(t, typeXML{\"FOO\"}, objXML)\n\t\t}\n\n\t\tif tt.bindingBody == binding.YAML {\n\t\t\trequire.Error(t, c.ShouldBindBodyWithXML(&objXML))\n\t\t\tassert.Equal(t, typeXML{}, objXML)\n\t\t}\n\n\t\tif tt.bindingBody == binding.TOML {\n\t\t\trequire.Error(t, c.ShouldBindBodyWithXML(&objXML))\n\t\t\tassert.Equal(t, typeXML{}, objXML)\n\t\t}\n\t}\n}\n\nfunc TestContextShouldBindBodyWithYAML(t *testing.T) {\n\tfor _, tt := range []struct {\n\t\tname        string\n\t\tbindingBody binding.BindingBody\n\t\tbody        string\n\t}{\n\t\t{\n\t\t\tname:        \" YAML & JSON-BODY \",\n\t\t\tbindingBody: binding.JSON,\n\t\t\tbody:        `{\"foo\":\"FOO\"}`,\n\t\t},\n\t\t{\n\t\t\tname:        \" YAML & XML-BODY \",\n\t\t\tbindingBody: binding.XML,\n\t\t\tbody: `<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<root>\n<foo>FOO</foo>\n</root>`,\n\t\t},\n\t\t{\n\t\t\tname:        \" YAML & YAML-BODY \",\n\t\t\tbindingBody: binding.YAML,\n\t\t\tbody:        `foo: FOO`,\n\t\t},\n\t\t{\n\t\t\tname:        \" YAML & TOM-BODY \",\n\t\t\tbindingBody: binding.TOML,\n\t\t\tbody:        `foo=FOO`,\n\t\t},\n\t} {\n\t\tt.Logf(\"testing: %s\", tt.name)\n\n\t\tw := httptest.NewRecorder()\n\t\tc, _ := CreateTestContext(w)\n\n\t\tc.Request, _ = http.NewRequest(http.MethodPost, \"/\", bytes.NewBufferString(tt.body))\n\n\t\ttype typeYAML struct {\n\t\t\tFoo string `yaml:\"foo\" binding:\"required\"`\n\t\t}\n\t\tobjYAML := typeYAML{}\n\n\t\t// YAML belongs to a super collection of JSON, so JSON can be parsed by YAML\n\t\tif tt.bindingBody == binding.JSON {\n\t\t\trequire.NoError(t, c.ShouldBindBodyWithYAML(&objYAML))\n\t\t\tassert.Equal(t, typeYAML{\"FOO\"}, objYAML)\n\t\t}\n\n\t\tif tt.bindingBody == binding.XML {\n\t\t\trequire.Error(t, c.ShouldBindBodyWithYAML(&objYAML))\n\t\t\tassert.Equal(t, typeYAML{}, objYAML)\n\t\t}\n\n\t\tif tt.bindingBody == binding.YAML {\n\t\t\trequire.NoError(t, c.ShouldBindBodyWithYAML(&objYAML))\n\t\t\tassert.Equal(t, typeYAML{\"FOO\"}, objYAML)\n\t\t}\n\n\t\tif tt.bindingBody == binding.TOML {\n\t\t\trequire.Error(t, c.ShouldBindBodyWithYAML(&objYAML))\n\t\t\tassert.Equal(t, typeYAML{}, objYAML)\n\t\t}\n\t}\n}\n\nfunc TestContextShouldBindBodyWithTOML(t *testing.T) {\n\tfor _, tt := range []struct {\n\t\tname        string\n\t\tbindingBody binding.BindingBody\n\t\tbody        string\n\t}{\n\t\t{\n\t\t\tname:        \" TOML & JSON-BODY \",\n\t\t\tbindingBody: binding.JSON,\n\t\t\tbody:        `{\"foo\":\"FOO\"}`,\n\t\t},\n\t\t{\n\t\t\tname:        \" TOML & XML-BODY \",\n\t\t\tbindingBody: binding.XML,\n\t\t\tbody: `<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<root>\n<foo>FOO</foo>\n</root>`,\n\t\t},\n\t\t{\n\t\t\tname:        \" TOML & YAML-BODY \",\n\t\t\tbindingBody: binding.YAML,\n\t\t\tbody:        `foo: FOO`,\n\t\t},\n\t\t{\n\t\t\tname:        \" TOML & TOM-BODY \",\n\t\t\tbindingBody: binding.TOML,\n\t\t\tbody:        `foo = 'FOO'`,\n\t\t},\n\t} {\n\t\tt.Logf(\"testing: %s\", tt.name)\n\n\t\tw := httptest.NewRecorder()\n\t\tc, _ := CreateTestContext(w)\n\n\t\tc.Request, _ = http.NewRequest(http.MethodPost, \"/\", bytes.NewBufferString(tt.body))\n\n\t\ttype typeTOML struct {\n\t\t\tFoo string `toml:\"foo\" binding:\"required\"`\n\t\t}\n\t\tobjTOML := typeTOML{}\n\n\t\tif tt.bindingBody == binding.JSON {\n\t\t\trequire.Error(t, c.ShouldBindBodyWithTOML(&objTOML))\n\t\t\tassert.Equal(t, typeTOML{}, objTOML)\n\t\t}\n\n\t\tif tt.bindingBody == binding.XML {\n\t\t\trequire.Error(t, c.ShouldBindBodyWithTOML(&objTOML))\n\t\t\tassert.Equal(t, typeTOML{}, objTOML)\n\t\t}\n\n\t\tif tt.bindingBody == binding.YAML {\n\t\t\trequire.Error(t, c.ShouldBindBodyWithTOML(&objTOML))\n\t\t\tassert.Equal(t, typeTOML{}, objTOML)\n\t\t}\n\n\t\tif tt.bindingBody == binding.TOML {\n\t\t\trequire.NoError(t, c.ShouldBindBodyWithTOML(&objTOML))\n\t\t\tassert.Equal(t, typeTOML{\"FOO\"}, objTOML)\n\t\t}\n\t}\n}\n\nfunc TestContextShouldBindBodyWithPlain(t *testing.T) {\n\tfor _, tt := range []struct {\n\t\tname        string\n\t\tbindingBody binding.BindingBody\n\t\tbody        string\n\t}{\n\t\t{\n\t\t\tname:        \" JSON & JSON-BODY \",\n\t\t\tbindingBody: binding.JSON,\n\t\t\tbody:        `{\"foo\":\"FOO\"}`,\n\t\t},\n\t\t{\n\t\t\tname:        \" JSON & XML-BODY \",\n\t\t\tbindingBody: binding.XML,\n\t\t\tbody: `<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<root>\n<foo>FOO</foo>\n</root>`,\n\t\t},\n\t\t{\n\t\t\tname:        \" JSON & YAML-BODY \",\n\t\t\tbindingBody: binding.YAML,\n\t\t\tbody:        `foo: FOO`,\n\t\t},\n\t\t{\n\t\t\tname:        \" JSON & TOM-BODY \",\n\t\t\tbindingBody: binding.TOML,\n\t\t\tbody:        `foo=FOO`,\n\t\t},\n\t\t{\n\t\t\tname:        \" JSON & Plain-BODY \",\n\t\t\tbindingBody: binding.Plain,\n\t\t\tbody:        `foo=FOO`,\n\t\t},\n\t} {\n\t\tt.Logf(\"testing: %s\", tt.name)\n\n\t\tw := httptest.NewRecorder()\n\t\tc, _ := CreateTestContext(w)\n\n\t\tc.Request, _ = http.NewRequest(http.MethodPost, \"/\", bytes.NewBufferString(tt.body))\n\n\t\ttype typeJSON struct {\n\t\t\tFoo string `json:\"foo\" binding:\"required\"`\n\t\t}\n\t\tobjJSON := typeJSON{}\n\n\t\tif tt.bindingBody == binding.Plain {\n\t\t\tbody := \"\"\n\t\t\trequire.NoError(t, c.ShouldBindBodyWithPlain(&body))\n\t\t\tassert.Equal(t, \"foo=FOO\", body)\n\t\t}\n\n\t\tif tt.bindingBody == binding.JSON {\n\t\t\trequire.NoError(t, c.ShouldBindBodyWithJSON(&objJSON))\n\t\t\tassert.Equal(t, typeJSON{\"FOO\"}, objJSON)\n\t\t}\n\n\t\tif tt.bindingBody == binding.XML {\n\t\t\trequire.Error(t, c.ShouldBindBodyWithJSON(&objJSON))\n\t\t\tassert.Equal(t, typeJSON{}, objJSON)\n\t\t}\n\n\t\tif tt.bindingBody == binding.YAML {\n\t\t\trequire.Error(t, c.ShouldBindBodyWithJSON(&objJSON))\n\t\t\tassert.Equal(t, typeJSON{}, objJSON)\n\t\t}\n\n\t\tif tt.bindingBody == binding.TOML {\n\t\t\trequire.Error(t, c.ShouldBindBodyWithJSON(&objJSON))\n\t\t\tassert.Equal(t, typeJSON{}, objJSON)\n\t\t}\n\t}\n}\n\nfunc TestContextGolangContext(t *testing.T) {\n\tc, _ := CreateTestContext(httptest.NewRecorder())\n\tc.Request, _ = http.NewRequest(http.MethodPost, \"/\", strings.NewReader(`{\"foo\":\"bar\", \"bar\":\"foo\"}`))\n\trequire.NoError(t, c.Err())\n\tassert.Nil(t, c.Done())\n\tti, ok := c.Deadline()\n\tassert.Equal(t, time.Time{}, ti)\n\tassert.False(t, ok)\n\tassert.Equal(t, c.Value(ContextRequestKey), c.Request)\n\tassert.Equal(t, c.Value(ContextKey), c)\n\tassert.Nil(t, c.Value(\"foo\"))\n\n\tc.Set(\"foo\", \"bar\")\n\tassert.Equal(t, \"bar\", c.Value(\"foo\"))\n\tassert.Nil(t, c.Value(1))\n}\n\nfunc TestWebsocketsRequired(t *testing.T) {\n\t// Example request from spec: https://tools.ietf.org/html/rfc6455#section-1.2\n\tc, _ := CreateTestContext(httptest.NewRecorder())\n\tc.Request, _ = http.NewRequest(http.MethodGet, \"/chat\", nil)\n\tc.Request.Header.Set(\"Host\", \"server.example.com\")\n\tc.Request.Header.Set(\"Upgrade\", \"websocket\")\n\tc.Request.Header.Set(\"Connection\", \"Upgrade\")\n\tc.Request.Header.Set(\"Sec-WebSocket-Key\", \"dGhlIHNhbXBsZSBub25jZQ==\")\n\tc.Request.Header.Set(\"Origin\", \"http://example.com\")\n\tc.Request.Header.Set(\"Sec-WebSocket-Protocol\", \"chat, superchat\")\n\tc.Request.Header.Set(\"Sec-WebSocket-Version\", \"13\")\n\n\tassert.True(t, c.IsWebsocket())\n\n\t// Normal request, no websocket required.\n\tc, _ = CreateTestContext(httptest.NewRecorder())\n\tc.Request, _ = http.NewRequest(http.MethodGet, \"/chat\", nil)\n\tc.Request.Header.Set(\"Host\", \"server.example.com\")\n\n\tassert.False(t, c.IsWebsocket())\n}\n\nfunc TestGetRequestHeaderValue(t *testing.T) {\n\tc, _ := CreateTestContext(httptest.NewRecorder())\n\tc.Request, _ = http.NewRequest(http.MethodGet, \"/chat\", nil)\n\tc.Request.Header.Set(\"Gin-Version\", \"1.0.0\")\n\n\tassert.Equal(t, \"1.0.0\", c.GetHeader(\"Gin-Version\"))\n\tassert.Empty(t, c.GetHeader(\"Connection\"))\n}\n\nfunc TestContextGetRawData(t *testing.T) {\n\tc, _ := CreateTestContext(httptest.NewRecorder())\n\tbody := strings.NewReader(\"Fetch binary post data\")\n\tc.Request, _ = http.NewRequest(http.MethodPost, \"/\", body)\n\tc.Request.Header.Add(\"Content-Type\", MIMEPOSTForm)\n\n\tdata, err := c.GetRawData()\n\trequire.NoError(t, err)\n\tassert.Equal(t, \"Fetch binary post data\", string(data))\n}\n\nfunc TestContextGetRawDataNilBody(t *testing.T) {\n\tc, _ := CreateTestContext(httptest.NewRecorder())\n\tc.Request, _ = http.NewRequest(http.MethodPost, \"/\", nil)\n\n\tdata, err := c.GetRawData()\n\tassert.Nil(t, data)\n\trequire.Error(t, err)\n\tassert.Equal(t, \"cannot read nil body\", err.Error())\n}\n\nfunc TestContextRenderDataFromReader(t *testing.T) {\n\tw := httptest.NewRecorder()\n\tc, _ := CreateTestContext(w)\n\n\tbody := \"#!PNG some raw data\"\n\treader := strings.NewReader(body)\n\tcontentLength := int64(len(body))\n\tcontentType := \"image/png\"\n\textraHeaders := map[string]string{\"Content-Disposition\": `attachment; filename=\"gopher.png\"`}\n\n\tc.DataFromReader(http.StatusOK, contentLength, contentType, reader, extraHeaders)\n\n\tassert.Equal(t, http.StatusOK, w.Code)\n\tassert.Equal(t, body, w.Body.String())\n\tassert.Equal(t, contentType, w.Header().Get(\"Content-Type\"))\n\tassert.Equal(t, strconv.FormatInt(contentLength, 10), w.Header().Get(\"Content-Length\"))\n\tassert.Equal(t, extraHeaders[\"Content-Disposition\"], w.Header().Get(\"Content-Disposition\"))\n}\n\nfunc TestContextRenderDataFromReaderNoHeaders(t *testing.T) {\n\tw := httptest.NewRecorder()\n\tc, _ := CreateTestContext(w)\n\n\tbody := \"#!PNG some raw data\"\n\treader := strings.NewReader(body)\n\tcontentLength := int64(len(body))\n\tcontentType := \"image/png\"\n\n\tc.DataFromReader(http.StatusOK, contentLength, contentType, reader, nil)\n\n\tassert.Equal(t, http.StatusOK, w.Code)\n\tassert.Equal(t, body, w.Body.String())\n\tassert.Equal(t, contentType, w.Header().Get(\"Content-Type\"))\n\tassert.Equal(t, strconv.FormatInt(contentLength, 10), w.Header().Get(\"Content-Length\"))\n}\n\ntype TestResponseRecorder struct {\n\t*httptest.ResponseRecorder\n\tcloseChannel chan bool\n}\n\nfunc (r *TestResponseRecorder) CloseNotify() <-chan bool {\n\treturn r.closeChannel\n}\n\nfunc (r *TestResponseRecorder) closeClient() {\n\tr.closeChannel <- true\n}\n\nfunc CreateTestResponseRecorder() *TestResponseRecorder {\n\treturn &TestResponseRecorder{\n\t\thttptest.NewRecorder(),\n\t\tmake(chan bool, 1),\n\t}\n}\n\nfunc TestContextStream(t *testing.T) {\n\tw := CreateTestResponseRecorder()\n\tc, _ := CreateTestContext(w)\n\n\tstopStream := true\n\tc.Stream(func(w io.Writer) bool {\n\t\tdefer func() {\n\t\t\tstopStream = false\n\t\t}()\n\n\t\t_, err := w.Write([]byte(\"test\"))\n\t\trequire.NoError(t, err)\n\n\t\treturn stopStream\n\t})\n\n\tassert.Equal(t, \"testtest\", w.Body.String())\n}\n\nfunc TestContextStreamWithClientGone(t *testing.T) {\n\tw := CreateTestResponseRecorder()\n\tc, _ := CreateTestContext(w)\n\n\tc.Stream(func(writer io.Writer) bool {\n\t\tdefer func() {\n\t\t\tw.closeClient()\n\t\t}()\n\n\t\t_, err := writer.Write([]byte(\"test\"))\n\t\trequire.NoError(t, err)\n\n\t\treturn true\n\t})\n\n\tassert.Equal(t, \"test\", w.Body.String())\n}\n\nfunc TestContextResetInHandler(t *testing.T) {\n\tw := CreateTestResponseRecorder()\n\tc, _ := CreateTestContext(w)\n\n\tc.handlers = []HandlerFunc{\n\t\tfunc(c *Context) { c.reset() },\n\t}\n\tassert.NotPanics(t, func() {\n\t\tc.Next()\n\t})\n}\n\nfunc TestRaceParamsContextCopy(t *testing.T) {\n\tDefaultWriter = os.Stdout\n\trouter := Default()\n\tnameGroup := router.Group(\"/:name\")\n\tvar wg sync.WaitGroup\n\twg.Add(2)\n\t{\n\t\tnameGroup.GET(\"/api\", func(c *Context) {\n\t\t\tgo func(c *Context, param string) {\n\t\t\t\tdefer wg.Done()\n\t\t\t\t// First assert must be executed after the second request\n\t\t\t\ttime.Sleep(50 * time.Millisecond)\n\t\t\t\tassert.Equal(t, c.Param(\"name\"), param)\n\t\t\t}(c.Copy(), c.Param(\"name\"))\n\t\t})\n\t}\n\tPerformRequest(router, http.MethodGet, \"/name1/api\")\n\tPerformRequest(router, http.MethodGet, \"/name2/api\")\n\twg.Wait()\n}\n\nfunc TestContextWithKeysMutex(t *testing.T) {\n\tc := &Context{}\n\tc.Set(\"foo\", \"bar\")\n\n\tvalue, err := c.Get(\"foo\")\n\tassert.Equal(t, \"bar\", value)\n\tassert.True(t, err)\n\n\tvalue, err = c.Get(\"foo2\")\n\tassert.Nil(t, value)\n\tassert.False(t, err)\n}\n\nfunc TestRemoteIPFail(t *testing.T) {\n\tc, _ := CreateTestContext(httptest.NewRecorder())\n\tc.Request, _ = http.NewRequest(http.MethodPost, \"/\", nil)\n\tc.Request.RemoteAddr = \"[:::]:80\"\n\tip := net.ParseIP(c.RemoteIP())\n\ttrust := c.engine.isTrustedProxy(ip)\n\tassert.Nil(t, ip)\n\tassert.False(t, trust)\n}\n\nfunc TestHasRequestContext(t *testing.T) {\n\tc, _ := CreateTestContext(httptest.NewRecorder())\n\tassert.False(t, c.hasRequestContext(), \"no request, no fallback\")\n\tc.engine.ContextWithFallback = true\n\tassert.False(t, c.hasRequestContext(), \"no request, has fallback\")\n\tc.Request, _ = http.NewRequest(http.MethodGet, \"/\", nil)\n\tassert.True(t, c.hasRequestContext(), \"has request, has fallback\")\n\tc.Request, _ = http.NewRequestWithContext(nil, \"\", \"\", nil) //nolint:staticcheck\n\tassert.False(t, c.hasRequestContext(), \"has request with nil ctx, has fallback\")\n\tc.engine.ContextWithFallback = false\n\tassert.False(t, c.hasRequestContext(), \"has request, no fallback\")\n\n\tc = &Context{}\n\tassert.False(t, c.hasRequestContext(), \"no request, no engine\")\n\tc.Request, _ = http.NewRequest(http.MethodGet, \"/\", nil)\n\tassert.False(t, c.hasRequestContext(), \"has request, no engine\")\n}\n\nfunc TestContextWithFallbackDeadlineFromRequestContext(t *testing.T) {\n\tc, _ := CreateTestContext(httptest.NewRecorder())\n\t// enable ContextWithFallback feature flag\n\tc.engine.ContextWithFallback = true\n\n\tdeadline, ok := c.Deadline()\n\tassert.Zero(t, deadline)\n\tassert.False(t, ok)\n\n\tc2, _ := CreateTestContext(httptest.NewRecorder())\n\t// enable ContextWithFallback feature flag\n\tc2.engine.ContextWithFallback = true\n\n\tc2.Request, _ = http.NewRequest(http.MethodGet, \"/\", nil)\n\td := time.Now().Add(time.Second)\n\tctx, cancel := context.WithDeadline(context.Background(), d)\n\tdefer cancel()\n\tc2.Request = c2.Request.WithContext(ctx)\n\tdeadline, ok = c2.Deadline()\n\tassert.Equal(t, d, deadline)\n\tassert.True(t, ok)\n}\n\nfunc TestContextWithFallbackDoneFromRequestContext(t *testing.T) {\n\tc, _ := CreateTestContext(httptest.NewRecorder())\n\t// enable ContextWithFallback feature flag\n\tc.engine.ContextWithFallback = true\n\n\tassert.Nil(t, c.Done())\n\n\tc2, _ := CreateTestContext(httptest.NewRecorder())\n\t// enable ContextWithFallback feature flag\n\tc2.engine.ContextWithFallback = true\n\n\tc2.Request, _ = http.NewRequest(http.MethodGet, \"/\", nil)\n\tctx, cancel := context.WithCancel(context.Background())\n\tc2.Request = c2.Request.WithContext(ctx)\n\tcancel()\n\tassert.NotNil(t, <-c2.Done())\n}\n\nfunc TestContextWithFallbackErrFromRequestContext(t *testing.T) {\n\tc, _ := CreateTestContext(httptest.NewRecorder())\n\t// enable ContextWithFallback feature flag\n\tc.engine.ContextWithFallback = true\n\n\trequire.NoError(t, c.Err())\n\n\tc2, _ := CreateTestContext(httptest.NewRecorder())\n\t// enable ContextWithFallback feature flag\n\tc2.engine.ContextWithFallback = true\n\n\tc2.Request, _ = http.NewRequest(http.MethodGet, \"/\", nil)\n\tctx, cancel := context.WithCancel(context.Background())\n\tc2.Request = c2.Request.WithContext(ctx)\n\tcancel()\n\n\tassert.EqualError(t, c2.Err(), context.Canceled.Error())\n}\n\nfunc TestContextWithFallbackValueFromRequestContext(t *testing.T) {\n\ttype contextKey string\n\n\ttests := []struct {\n\t\tname             string\n\t\tgetContextAndKey func() (*Context, any)\n\t\tvalue            any\n\t}{\n\t\t{\n\t\t\tname: \"c with struct context key\",\n\t\t\tgetContextAndKey: func() (*Context, any) {\n\t\t\t\ttype KeyStruct struct{} // https://staticcheck.dev/docs/checks/#SA1029\n\t\t\t\tvar key KeyStruct\n\t\t\t\tc, _ := CreateTestContext(httptest.NewRecorder())\n\t\t\t\t// enable ContextWithFallback feature flag\n\t\t\t\tc.engine.ContextWithFallback = true\n\t\t\t\tc.Request, _ = http.NewRequest(http.MethodPost, \"/\", nil)\n\t\t\t\tc.Request = c.Request.WithContext(context.WithValue(context.TODO(), key, \"value\"))\n\t\t\t\treturn c, key\n\t\t\t},\n\t\t\tvalue: \"value\",\n\t\t},\n\t\t{\n\t\t\tname: \"c with string context key\",\n\t\t\tgetContextAndKey: func() (*Context, any) {\n\t\t\t\tc, _ := CreateTestContext(httptest.NewRecorder())\n\t\t\t\t// enable ContextWithFallback feature flag\n\t\t\t\tc.engine.ContextWithFallback = true\n\t\t\t\tc.Request, _ = http.NewRequest(http.MethodPost, \"/\", nil)\n\t\t\t\tc.Request = c.Request.WithContext(context.WithValue(context.TODO(), contextKey(\"key\"), \"value\"))\n\t\t\t\treturn c, contextKey(\"key\")\n\t\t\t},\n\t\t\tvalue: \"value\",\n\t\t},\n\t\t{\n\t\t\tname: \"c with nil http.Request\",\n\t\t\tgetContextAndKey: func() (*Context, any) {\n\t\t\t\tc, _ := CreateTestContext(httptest.NewRecorder())\n\t\t\t\t// enable ContextWithFallback feature flag\n\t\t\t\tc.engine.ContextWithFallback = true\n\t\t\t\tc.Request = nil\n\t\t\t\treturn c, \"key\"\n\t\t\t},\n\t\t\tvalue: nil,\n\t\t},\n\t\t{\n\t\t\tname: \"c with nil http.Request.Context()\",\n\t\t\tgetContextAndKey: func() (*Context, any) {\n\t\t\t\tc, _ := CreateTestContext(httptest.NewRecorder())\n\t\t\t\t// enable ContextWithFallback feature flag\n\t\t\t\tc.engine.ContextWithFallback = true\n\t\t\t\tc.Request, _ = http.NewRequest(http.MethodPost, \"/\", nil)\n\t\t\t\treturn c, \"key\"\n\t\t\t},\n\t\t\tvalue: nil,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tc, key := tt.getContextAndKey()\n\t\t\tassert.Equal(t, tt.value, c.Value(key))\n\t\t})\n\t}\n}\n\nfunc TestContextCopyShouldNotCancel(t *testing.T) {\n\tsrv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {\n\t\tw.WriteHeader(http.StatusOK)\n\t}))\n\tdefer srv.Close()\n\n\tensureRequestIsOver := make(chan struct{})\n\n\twg := &sync.WaitGroup{}\n\n\tr := New()\n\tr.GET(\"/\", func(ginctx *Context) {\n\t\twg.Add(1)\n\n\t\tginctx = ginctx.Copy()\n\n\t\t// start async goroutine for calling srv\n\t\tgo func() {\n\t\t\tdefer wg.Done()\n\n\t\t\t<-ensureRequestIsOver // ensure request is done\n\n\t\t\treq, err := http.NewRequestWithContext(ginctx, http.MethodGet, srv.URL, nil)\n\t\t\tmust(err)\n\n\t\t\tres, err := http.DefaultClient.Do(req)\n\t\t\tif err != nil {\n\t\t\t\tt.Error(fmt.Errorf(\"request error: %w\", err))\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif res.StatusCode != http.StatusOK {\n\t\t\t\tt.Error(fmt.Errorf(\"unexpected status code: %s\", res.Status))\n\t\t\t}\n\t\t}()\n\t})\n\n\tl, err := net.Listen(\"tcp\", \":0\")\n\tmust(err)\n\tgo func() {\n\t\ts := &http.Server{\n\t\t\tHandler: r,\n\t\t}\n\n\t\tmust(s.Serve(l))\n\t}()\n\n\taddr := strings.Split(l.Addr().String(), \":\")\n\tres, err := http.Get(fmt.Sprintf(\"http://%s:%s/\", localhostIP, addr[len(addr)-1]))\n\tif err != nil {\n\t\tt.Error(fmt.Errorf(\"request error: %w\", err))\n\t\treturn\n\t}\n\n\tclose(ensureRequestIsOver)\n\n\tif res.StatusCode != http.StatusOK {\n\t\tt.Error(fmt.Errorf(\"unexpected status code: %s\", res.Status))\n\t\treturn\n\t}\n\n\twg.Wait()\n}\n\nfunc TestContextAddParam(t *testing.T) {\n\tc := &Context{}\n\tid := \"id\"\n\tvalue := \"1\"\n\tc.AddParam(id, value)\n\n\tv, ok := c.Params.Get(id)\n\tassert.True(t, ok)\n\tassert.Equal(t, value, v)\n}\n\nfunc TestCreateTestContextWithRouteParams(t *testing.T) {\n\tw := httptest.NewRecorder()\n\tengine := New()\n\tengine.GET(\"/:action/:name\", func(ctx *Context) {\n\t\tctx.String(http.StatusOK, \"%s %s\", ctx.Param(\"action\"), ctx.Param(\"name\"))\n\t})\n\tc := CreateTestContextOnly(w, engine)\n\tc.Request, _ = http.NewRequest(http.MethodGet, \"/hello/gin\", nil)\n\tengine.HandleContext(c)\n\n\tassert.Equal(t, http.StatusOK, w.Code)\n\tassert.Equal(t, \"hello gin\", w.Body.String())\n}\n\ntype interceptedWriter struct {\n\tResponseWriter\n\tb *bytes.Buffer\n}\n\nfunc (i interceptedWriter) WriteHeader(code int) {\n\ti.Header().Del(\"X-Test\")\n\ti.ResponseWriter.WriteHeader(code)\n}\n\nfunc TestInterceptedHeader(t *testing.T) {\n\tw := httptest.NewRecorder()\n\tc, r := CreateTestContext(w)\n\n\tr.Use(func(c *Context) {\n\t\ti := interceptedWriter{\n\t\t\tResponseWriter: c.Writer,\n\t\t\tb:              bytes.NewBuffer(nil),\n\t\t}\n\t\tc.Writer = i\n\t\tc.Next()\n\t\tc.Header(\"X-Test\", \"overridden\")\n\t\tc.Writer = i.ResponseWriter\n\t})\n\tr.GET(\"/\", func(c *Context) {\n\t\tc.Header(\"X-Test\", \"original\")\n\t\tc.Header(\"X-Test-2\", \"present\")\n\t\tc.String(http.StatusOK, \"hello world\")\n\t})\n\tc.Request = httptest.NewRequest(http.MethodGet, \"/\", nil)\n\tr.HandleContext(c)\n\t// Result() has headers frozen when WriteHeaderNow() has been called\n\t// Compared to this time, this is when the response headers will be flushed\n\t// As response is flushed on c.String, the Header cannot be set by the first\n\t// middleware. Assert this\n\tassert.Empty(t, w.Result().Header.Get(\"X-Test\"))\n\tassert.Equal(t, \"present\", w.Result().Header.Get(\"X-Test-2\"))\n}\n\nfunc TestContextNext(t *testing.T) {\n\tc, _ := CreateTestContext(httptest.NewRecorder())\n\n\t// Test with no handlers\n\tc.Next()\n\tassert.Equal(t, int8(0), c.index)\n\n\t// Test with one handler\n\tc.index = -1\n\tc.handlers = HandlersChain{func(c *Context) {\n\t\tc.Set(\"key\", \"value\")\n\t}}\n\tc.Next()\n\tassert.Equal(t, int8(1), c.index)\n\tvalue, exists := c.Get(\"key\")\n\tassert.True(t, exists)\n\tassert.Equal(t, \"value\", value)\n\n\t// Test with multiple handlers\n\tc.handlers = HandlersChain{\n\t\tfunc(c *Context) {\n\t\t\tc.Set(\"key1\", \"value1\")\n\t\t\tc.Next()\n\t\t\tc.Set(\"key2\", \"value2\")\n\t\t},\n\t\tnil,\n\t\tfunc(c *Context) {\n\t\t\tc.Set(\"key3\", \"value3\")\n\t\t},\n\t}\n\tc.index = -1\n\tc.Next()\n\tassert.Equal(t, int8(4), c.index)\n\tvalue, exists = c.Get(\"key1\")\n\tassert.True(t, exists)\n\tassert.Equal(t, \"value1\", value)\n\tvalue, exists = c.Get(\"key2\")\n\tassert.True(t, exists)\n\tassert.Equal(t, \"value2\", value)\n\tvalue, exists = c.Get(\"key3\")\n\tassert.True(t, exists)\n\tassert.Equal(t, \"value3\", value)\n}\n\nfunc TestContextSetCookieData(t *testing.T) {\n\tc, _ := CreateTestContext(httptest.NewRecorder())\n\tc.SetSameSite(http.SameSiteLaxMode)\n\tvar setCookie string\n\n\t// Basic cookie settings\n\tcookie := &http.Cookie{\n\t\tName:     \"user\",\n\t\tValue:    \"gin\",\n\t\tMaxAge:   1,\n\t\tPath:     \"/\",\n\t\tDomain:   \"localhost\",\n\t\tSecure:   true,\n\t\tHttpOnly: true,\n\t}\n\tc.SetCookieData(cookie)\n\tsetCookie = c.Writer.Header().Get(\"Set-Cookie\")\n\tassert.Contains(t, setCookie, \"user=gin\")\n\tassert.Contains(t, setCookie, \"Path=/\")\n\tassert.Contains(t, setCookie, \"Domain=localhost\")\n\tassert.Contains(t, setCookie, \"Max-Age=1\")\n\tassert.Contains(t, setCookie, \"HttpOnly\")\n\tassert.Contains(t, setCookie, \"Secure\")\n\t// SameSite=Lax might be omitted in Go 1.24+ as it's the default\n\t// assert.Contains(t, setCookie, \"SameSite=Lax\")\n\n\t// Test that when Path is empty, \"/\" is automatically set\n\tcookie = &http.Cookie{\n\t\tName:     \"user\",\n\t\tValue:    \"gin\",\n\t\tMaxAge:   1,\n\t\tPath:     \"\",\n\t\tDomain:   \"localhost\",\n\t\tSecure:   true,\n\t\tHttpOnly: true,\n\t}\n\tc.SetCookieData(cookie)\n\tsetCookie = c.Writer.Header().Get(\"Set-Cookie\")\n\tassert.Contains(t, setCookie, \"user=gin\")\n\tassert.Contains(t, setCookie, \"Path=/\")\n\tassert.Contains(t, setCookie, \"Domain=localhost\")\n\tassert.Contains(t, setCookie, \"Max-Age=1\")\n\tassert.Contains(t, setCookie, \"HttpOnly\")\n\tassert.Contains(t, setCookie, \"Secure\")\n\t// SameSite=Lax might be omitted in Go 1.24+ as it's the default\n\t// assert.Contains(t, setCookie, \"SameSite=Lax\")\n\n\t// Test additional cookie attributes (Expires)\n\texpireTime := time.Now().Add(24 * time.Hour)\n\tcookie = &http.Cookie{\n\t\tName:     \"user\",\n\t\tValue:    \"gin\",\n\t\tPath:     \"/\",\n\t\tDomain:   \"localhost\",\n\t\tExpires:  expireTime,\n\t\tSecure:   true,\n\t\tHttpOnly: true,\n\t}\n\tc.SetCookieData(cookie)\n\n\t// Since the Expires value varies by time, partially verify with Contains\n\tsetCookie = c.Writer.Header().Get(\"Set-Cookie\")\n\tassert.Contains(t, setCookie, \"user=gin\")\n\tassert.Contains(t, setCookie, \"Path=/\")\n\tassert.Contains(t, setCookie, \"Domain=localhost\")\n\tassert.Contains(t, setCookie, \"HttpOnly\")\n\tassert.Contains(t, setCookie, \"Secure\")\n\t// SameSite=Lax might be omitted in Go 1.24+ as it's the default\n\t// assert.Contains(t, setCookie, \"SameSite=Lax\")\n\n\t// Test for Partitioned attribute (Go 1.18+)\n\tcookie = &http.Cookie{\n\t\tName:        \"user\",\n\t\tValue:       \"gin\",\n\t\tPath:        \"/\",\n\t\tDomain:      \"localhost\",\n\t\tSecure:      true,\n\t\tHttpOnly:    true,\n\t\tPartitioned: true,\n\t}\n\tc.SetCookieData(cookie)\n\tsetCookie = c.Writer.Header().Get(\"Set-Cookie\")\n\tassert.Contains(t, setCookie, \"user=gin\")\n\tassert.Contains(t, setCookie, \"Path=/\")\n\tassert.Contains(t, setCookie, \"Domain=localhost\")\n\tassert.Contains(t, setCookie, \"HttpOnly\")\n\tassert.Contains(t, setCookie, \"Secure\")\n\t// SameSite=Lax might be omitted in Go 1.24+ as it's the default\n\t// assert.Contains(t, setCookie, \"SameSite=Lax\")\n\t// Not testing for Partitioned attribute as it may not be supported in all Go versions\n\n\t// Test that SameSiteStrictMode is explicitly included in the header\n\tt.Run(\"SameSite=Strict is included\", func(t *testing.T) {\n\t\tc, _ := CreateTestContext(httptest.NewRecorder())\n\t\tcookie := &http.Cookie{\n\t\t\tName:     \"user\",\n\t\t\tValue:    \"gin\",\n\t\t\tPath:     \"/\",\n\t\t\tDomain:   \"localhost\",\n\t\t\tSecure:   true,\n\t\t\tHttpOnly: true,\n\t\t\tSameSite: http.SameSiteStrictMode,\n\t\t}\n\t\tc.SetCookieData(cookie)\n\t\tsetCookie := c.Writer.Header().Get(\"Set-Cookie\")\n\t\tassert.Contains(t, setCookie, \"SameSite=Strict\")\n\t})\n\n\t// Test that SameSiteNoneMode is explicitly included in the header\n\tt.Run(\"SameSite=None is included\", func(t *testing.T) {\n\t\tc, _ := CreateTestContext(httptest.NewRecorder())\n\t\tcookie := &http.Cookie{\n\t\t\tName:     \"user\",\n\t\t\tValue:    \"gin\",\n\t\t\tPath:     \"/\",\n\t\t\tDomain:   \"localhost\",\n\t\t\tSecure:   true,\n\t\t\tHttpOnly: true,\n\t\t\tSameSite: http.SameSiteNoneMode,\n\t\t}\n\t\tc.SetCookieData(cookie)\n\t\tsetCookie := c.Writer.Header().Get(\"Set-Cookie\")\n\t\tassert.Contains(t, setCookie, \"SameSite=None\")\n\t})\n\n\t// Test that SameSiteDefaultMode inherits from context's sameSite\n\tt.Run(\"SameSiteDefaultMode inherits context sameSite\", func(t *testing.T) {\n\t\tc, _ := CreateTestContext(httptest.NewRecorder())\n\t\tc.SetSameSite(http.SameSiteStrictMode)\n\t\tcookie := &http.Cookie{\n\t\t\tName:     \"user\",\n\t\t\tValue:    \"gin\",\n\t\t\tPath:     \"/\",\n\t\t\tDomain:   \"localhost\",\n\t\t\tSecure:   true,\n\t\t\tHttpOnly: true,\n\t\t\tSameSite: http.SameSiteDefaultMode,\n\t\t}\n\t\tc.SetCookieData(cookie)\n\t\tsetCookie := c.Writer.Header().Get(\"Set-Cookie\")\n\t\tassert.Contains(t, setCookie, \"SameSite=Strict\")\n\t})\n}\n\nfunc TestGetMapFromFormData(t *testing.T) {\n\ttestCases := []struct {\n\t\tname     string\n\t\tdata     map[string][]string\n\t\tkey      string\n\t\texpected map[string]string\n\t\tfound    bool\n\t}{\n\t\t{\n\t\t\tname: \"Basic bracket notation\",\n\t\t\tdata: map[string][]string{\n\t\t\t\t\"ids[a]\": {\"hi\"},\n\t\t\t\t\"ids[b]\": {\"3.14\"},\n\t\t\t},\n\t\t\tkey: \"ids\",\n\t\t\texpected: map[string]string{\n\t\t\t\t\"a\": \"hi\",\n\t\t\t\t\"b\": \"3.14\",\n\t\t\t},\n\t\t\tfound: true,\n\t\t},\n\t\t{\n\t\t\tname: \"Mixed data with bracket notation\",\n\t\t\tdata: map[string][]string{\n\t\t\t\t\"ids[a]\":     {\"hi\"},\n\t\t\t\t\"ids[b]\":     {\"3.14\"},\n\t\t\t\t\"names[a]\":   {\"mike\"},\n\t\t\t\t\"names[b]\":   {\"maria\"},\n\t\t\t\t\"other[key]\": {\"value\"},\n\t\t\t\t\"simple\":     {\"data\"},\n\t\t\t},\n\t\t\tkey: \"ids\",\n\t\t\texpected: map[string]string{\n\t\t\t\t\"a\": \"hi\",\n\t\t\t\t\"b\": \"3.14\",\n\t\t\t},\n\t\t\tfound: true,\n\t\t},\n\t\t{\n\t\t\tname: \"Names key\",\n\t\t\tdata: map[string][]string{\n\t\t\t\t\"ids[a]\":     {\"hi\"},\n\t\t\t\t\"ids[b]\":     {\"3.14\"},\n\t\t\t\t\"names[a]\":   {\"mike\"},\n\t\t\t\t\"names[b]\":   {\"maria\"},\n\t\t\t\t\"other[key]\": {\"value\"},\n\t\t\t},\n\t\t\tkey: \"names\",\n\t\t\texpected: map[string]string{\n\t\t\t\t\"a\": \"mike\",\n\t\t\t\t\"b\": \"maria\",\n\t\t\t},\n\t\t\tfound: true,\n\t\t},\n\t\t{\n\t\t\tname: \"Key not found\",\n\t\t\tdata: map[string][]string{\n\t\t\t\t\"ids[a]\":   {\"hi\"},\n\t\t\t\t\"names[b]\": {\"maria\"},\n\t\t\t},\n\t\t\tkey:      \"notfound\",\n\t\t\texpected: map[string]string{},\n\t\t\tfound:    false,\n\t\t},\n\t\t{\n\t\t\tname:     \"Empty data\",\n\t\t\tdata:     map[string][]string{},\n\t\t\tkey:      \"ids\",\n\t\t\texpected: map[string]string{},\n\t\t\tfound:    false,\n\t\t},\n\t\t{\n\t\t\tname: \"Malformed bracket notation\",\n\t\t\tdata: map[string][]string{\n\t\t\t\t\"ids[a\": {\"hi\"},    // Missing closing bracket\n\t\t\t\t\"ids]b\": {\"3.14\"},  // Missing opening bracket\n\t\t\t\t\"idsab\": {\"value\"}, // No brackets\n\t\t\t},\n\t\t\tkey:      \"ids\",\n\t\t\texpected: map[string]string{},\n\t\t\tfound:    false,\n\t\t},\n\t\t{\n\t\t\tname: \"Nested bracket notation\",\n\t\t\tdata: map[string][]string{\n\t\t\t\t\"ids[a][b]\": {\"nested\"},\n\t\t\t\t\"ids[c]\":    {\"simple\"},\n\t\t\t},\n\t\t\tkey: \"ids\",\n\t\t\texpected: map[string]string{\n\t\t\t\t\"a\": \"nested\",\n\t\t\t\t\"c\": \"simple\",\n\t\t\t},\n\t\t\tfound: true,\n\t\t},\n\t\t{\n\t\t\tname: \"Simple key without brackets\",\n\t\t\tdata: map[string][]string{\n\t\t\t\t\"simple\": {\"data\"},\n\t\t\t\t\"ids[a]\": {\"hi\"},\n\t\t\t},\n\t\t\tkey:      \"simple\",\n\t\t\texpected: map[string]string{},\n\t\t\tfound:    false,\n\t\t},\n\t\t{\n\t\t\tname: \"Mixed simple and bracket keys\",\n\t\t\tdata: map[string][]string{\n\t\t\t\t\"simple\": {\"data\"},\n\t\t\t\t\"ids[a]\": {\"hi\"},\n\t\t\t\t\"ids[b]\": {\"3.14\"},\n\t\t\t\t\"other\":  {\"value\"},\n\t\t\t},\n\t\t\tkey: \"ids\",\n\t\t\texpected: map[string]string{\n\t\t\t\t\"a\": \"hi\",\n\t\t\t\t\"b\": \"3.14\",\n\t\t\t},\n\t\t\tfound: true,\n\t\t},\n\t}\n\n\tfor _, tc := range testCases {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\tresult, found := getMapFromFormData(tc.data, tc.key)\n\t\t\tassert.Equal(t, tc.expected, result, \"result mismatch\")\n\t\t\tassert.Equal(t, tc.found, found, \"found mismatch\")\n\t\t})\n\t}\n}\n\nfunc BenchmarkGetMapFromFormData(b *testing.B) {\n\t// Test case 1: Small dataset with bracket notation\n\tsmallData := map[string][]string{\n\t\t\"ids[a]\":   {\"hi\"},\n\t\t\"ids[b]\":   {\"3.14\"},\n\t\t\"names[a]\": {\"mike\"},\n\t\t\"names[b]\": {\"maria\"},\n\t}\n\n\t// Test case 2: Medium dataset with mixed data\n\tmediumData := map[string][]string{\n\t\t\"ids[a]\":      {\"hi\"},\n\t\t\"ids[b]\":      {\"3.14\"},\n\t\t\"ids[c]\":      {\"test\"},\n\t\t\"ids[d]\":      {\"value\"},\n\t\t\"names[a]\":    {\"mike\"},\n\t\t\"names[b]\":    {\"maria\"},\n\t\t\"names[c]\":    {\"john\"},\n\t\t\"names[d]\":    {\"jane\"},\n\t\t\"other[key1]\": {\"value1\"},\n\t\t\"other[key2]\": {\"value2\"},\n\t\t\"simple\":      {\"data\"},\n\t\t\"another\":     {\"info\"},\n\t}\n\n\t// Test case 3: Large dataset with many bracket keys\n\tlargeData := make(map[string][]string)\n\tfor i := range 100 {\n\t\tkey := fmt.Sprintf(\"ids[%d]\", i)\n\t\tlargeData[key] = []string{fmt.Sprintf(\"value%d\", i)}\n\t}\n\tfor i := range 50 {\n\t\tkey := fmt.Sprintf(\"names[%d]\", i)\n\t\tlargeData[key] = []string{fmt.Sprintf(\"name%d\", i)}\n\t}\n\tfor i := range 25 {\n\t\tkey := fmt.Sprintf(\"other[key%d]\", i)\n\t\tlargeData[key] = []string{fmt.Sprintf(\"other%d\", i)}\n\t}\n\n\t// Test case 4: Dataset with many non-matching keys (worst case)\n\tworstCaseData := make(map[string][]string)\n\tfor i := range 100 {\n\t\tkey := fmt.Sprintf(\"nonmatching%d\", i)\n\t\tworstCaseData[key] = []string{fmt.Sprintf(\"value%d\", i)}\n\t}\n\tworstCaseData[\"ids[a]\"] = []string{\"hi\"}\n\tworstCaseData[\"ids[b]\"] = []string{\"3.14\"}\n\n\t// Test case 5: Dataset with short keys (best case for early exit)\n\tshortKeysData := map[string][]string{\n\t\t\"a\":      {\"value1\"},\n\t\t\"b\":      {\"value2\"},\n\t\t\"ids[a]\": {\"hi\"},\n\t\t\"ids[b]\": {\"3.14\"},\n\t}\n\n\tbenchmarks := []struct {\n\t\tname string\n\t\tdata map[string][]string\n\t\tkey  string\n\t}{\n\t\t{\"Small_Bracket\", smallData, \"ids\"},\n\t\t{\"Small_Names\", smallData, \"names\"},\n\t\t{\"Medium_Bracket\", mediumData, \"ids\"},\n\t\t{\"Medium_Names\", mediumData, \"names\"},\n\t\t{\"Medium_Other\", mediumData, \"other\"},\n\t\t{\"Large_Bracket\", largeData, \"ids\"},\n\t\t{\"Large_Names\", largeData, \"names\"},\n\t\t{\"Large_Other\", largeData, \"other\"},\n\t\t{\"WorstCase_Bracket\", worstCaseData, \"ids\"},\n\t\t{\"ShortKeys_Bracket\", shortKeysData, \"ids\"},\n\t\t{\"Empty_Key\", smallData, \"notfound\"},\n\t}\n\n\tfor _, bm := range benchmarks {\n\t\tb.Run(bm.name, func(b *testing.B) {\n\t\t\tb.ReportAllocs()\n\t\t\tfor b.Loop() {\n\t\t\t\t_, _ = getMapFromFormData(bm.data, bm.key)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "debug.go",
    "content": "// Copyright 2014 Manu Martinez-Almeida. All rights reserved.\n// Use of this source code is governed by a MIT style\n// license that can be found in the LICENSE file.\n\npackage gin\n\nimport (\n\t\"fmt\"\n\t\"html/template\"\n\t\"runtime\"\n\t\"strconv\"\n\t\"strings\"\n\t\"sync/atomic\"\n)\n\nconst ginSupportMinGoVer = 25\n\nvar runtimeVersion = runtime.Version()\n\n// IsDebugging returns true if the framework is running in debug mode.\n// Use SetMode(gin.ReleaseMode) to disable debug mode.\nfunc IsDebugging() bool {\n\treturn atomic.LoadInt32(&ginMode) == debugCode\n}\n\n// DebugPrintRouteFunc indicates debug log output format.\nvar DebugPrintRouteFunc func(httpMethod, absolutePath, handlerName string, nuHandlers int)\n\n// DebugPrintFunc indicates debug log output format.\nvar DebugPrintFunc func(format string, values ...any)\n\nfunc debugPrintRoute(httpMethod, absolutePath string, handlers HandlersChain) {\n\tif IsDebugging() {\n\t\tnuHandlers := len(handlers)\n\t\thandlerName := nameOfFunction(handlers.Last())\n\t\tif DebugPrintRouteFunc == nil {\n\t\t\tdebugPrint(\"%-6s %-25s --> %s (%d handlers)\\n\", httpMethod, absolutePath, handlerName, nuHandlers)\n\t\t} else {\n\t\t\tDebugPrintRouteFunc(httpMethod, absolutePath, handlerName, nuHandlers)\n\t\t}\n\t}\n}\n\nfunc debugPrintLoadTemplate(tmpl *template.Template) {\n\tif IsDebugging() {\n\t\tvar buf strings.Builder\n\t\tfor _, tmpl := range tmpl.Templates() {\n\t\t\tbuf.WriteString(\"\\t- \")\n\t\t\tbuf.WriteString(tmpl.Name())\n\t\t\tbuf.WriteString(\"\\n\")\n\t\t}\n\t\tdebugPrint(\"Loaded HTML Templates (%d): \\n%s\\n\", len(tmpl.Templates()), buf.String())\n\t}\n}\n\nfunc debugPrint(format string, values ...any) {\n\tif !IsDebugging() {\n\t\treturn\n\t}\n\n\tif DebugPrintFunc != nil {\n\t\tDebugPrintFunc(format, values...)\n\t\treturn\n\t}\n\n\tif !strings.HasSuffix(format, \"\\n\") {\n\t\tformat += \"\\n\"\n\t}\n\tfmt.Fprintf(DefaultWriter, \"[GIN-debug] \"+format, values...)\n}\n\nfunc getMinVer(v string) (uint64, error) {\n\tfirst := strings.IndexByte(v, '.')\n\tlast := strings.LastIndexByte(v, '.')\n\tif first == last {\n\t\treturn strconv.ParseUint(v[first+1:], 10, 64)\n\t}\n\treturn strconv.ParseUint(v[first+1:last], 10, 64)\n}\n\nfunc debugPrintWARNINGDefault() {\n\tif v, e := getMinVer(runtimeVersion); e == nil && v < ginSupportMinGoVer {\n\t\tdebugPrint(`[WARNING] Now Gin requires Go 1.25+.\n\n`)\n\t}\n\tdebugPrint(`[WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached.\n\n`)\n}\n\nfunc debugPrintWARNINGNew() {\n\tdebugPrint(`[WARNING] Running in \"debug\" mode. Switch to \"release\" mode in production.\n - using env:\texport GIN_MODE=release\n - using code:\tgin.SetMode(gin.ReleaseMode)\n\n`)\n}\n\nfunc debugPrintWARNINGSetHTMLTemplate() {\n\tdebugPrint(`[WARNING] Since SetHTMLTemplate() is NOT thread-safe. It should only be called\nat initialization. ie. before any route is registered or the router is listening in a socket:\n\n\trouter := gin.Default()\n\trouter.SetHTMLTemplate(template) // << good place\n\n`)\n}\n\nfunc debugPrintError(err error) {\n\tif err != nil && IsDebugging() {\n\t\tfmt.Fprintf(DefaultErrorWriter, \"[GIN-debug] [ERROR] %v\\n\", err)\n\t}\n}\n"
  },
  {
    "path": "debug_test.go",
    "content": "// Copyright 2014 Manu Martinez-Almeida. All rights reserved.\n// Use of this source code is governed by a MIT style\n// license that can be found in the LICENSE file.\n\npackage gin\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"html/template\"\n\t\"io\"\n\t\"log\"\n\t\"net/http\"\n\t\"os\"\n\t\"strings\"\n\t\"sync\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestIsDebugging(t *testing.T) {\n\tSetMode(DebugMode)\n\tassert.True(t, IsDebugging())\n\tSetMode(ReleaseMode)\n\tassert.False(t, IsDebugging())\n\tSetMode(TestMode)\n\tassert.False(t, IsDebugging())\n}\n\nfunc TestDebugPrint(t *testing.T) {\n\tre := captureOutput(t, func() {\n\t\tSetMode(DebugMode)\n\t\tSetMode(ReleaseMode)\n\t\tdebugPrint(\"DEBUG this!\")\n\t\tSetMode(TestMode)\n\t\tdebugPrint(\"DEBUG this!\")\n\t\tSetMode(DebugMode)\n\t\tdebugPrint(\"these are %d %s\", 2, \"error messages\")\n\t\tSetMode(TestMode)\n\t})\n\tassert.Equal(t, \"[GIN-debug] these are 2 error messages\\n\", re)\n}\n\nfunc TestDebugPrintFunc(t *testing.T) {\n\tDebugPrintFunc = func(format string, values ...any) {\n\t\tfmt.Fprintf(DefaultWriter, \"[GIN-debug] \"+format, values...)\n\t}\n\tre := captureOutput(t, func() {\n\t\tSetMode(DebugMode)\n\t\tdebugPrint(\"debug print func test: %d\", 123)\n\t\tSetMode(TestMode)\n\t})\n\tassert.Regexp(t, `^\\[GIN-debug\\] debug print func test: 123`, re)\n}\n\nfunc TestDebugPrintError(t *testing.T) {\n\tre := captureOutput(t, func() {\n\t\tSetMode(DebugMode)\n\t\tdebugPrintError(nil)\n\t\tdebugPrintError(errors.New(\"this is an error\"))\n\t\tSetMode(TestMode)\n\t})\n\tassert.Equal(t, \"[GIN-debug] [ERROR] this is an error\\n\", re)\n}\n\nfunc TestDebugPrintRoutes(t *testing.T) {\n\tre := captureOutput(t, func() {\n\t\tSetMode(DebugMode)\n\t\tdebugPrintRoute(http.MethodGet, \"/path/to/route/:param\", HandlersChain{func(c *Context) {}, handlerNameTest})\n\t\tSetMode(TestMode)\n\t})\n\tassert.Regexp(t, `^\\[GIN-debug\\] GET    /path/to/route/:param     --> (.*/vendor/)?github.com/gin-gonic/gin.handlerNameTest \\(2 handlers\\)\\n$`, re)\n}\n\nfunc TestDebugPrintRouteFunc(t *testing.T) {\n\tDebugPrintRouteFunc = func(httpMethod, absolutePath, handlerName string, nuHandlers int) {\n\t\tfmt.Fprintf(DefaultWriter, \"[GIN-debug] %-6s %-40s --> %s (%d handlers)\\n\", httpMethod, absolutePath, handlerName, nuHandlers)\n\t}\n\tre := captureOutput(t, func() {\n\t\tSetMode(DebugMode)\n\t\tdebugPrintRoute(http.MethodGet, \"/path/to/route/:param1/:param2\", HandlersChain{func(c *Context) {}, handlerNameTest})\n\t\tSetMode(TestMode)\n\t})\n\tassert.Regexp(t, `^\\[GIN-debug\\] GET    /path/to/route/:param1/:param2           --> (.*/vendor/)?github.com/gin-gonic/gin.handlerNameTest \\(2 handlers\\)\\n$`, re)\n}\n\nfunc TestDebugPrintLoadTemplate(t *testing.T) {\n\tre := captureOutput(t, func() {\n\t\tSetMode(DebugMode)\n\t\ttempl := template.Must(template.New(\"\").Delims(\"{[{\", \"}]}\").ParseGlob(\"./testdata/template/hello.tmpl\"))\n\t\tdebugPrintLoadTemplate(templ)\n\t\tSetMode(TestMode)\n\t})\n\tassert.Regexp(t, `^\\[GIN-debug\\] Loaded HTML Templates \\(2\\): \\n(\\t- \\n|\\t- hello\\.tmpl\\n){2}\\n`, re)\n}\n\nfunc TestDebugPrintWARNINGSetHTMLTemplate(t *testing.T) {\n\tre := captureOutput(t, func() {\n\t\tSetMode(DebugMode)\n\t\tdebugPrintWARNINGSetHTMLTemplate()\n\t\tSetMode(TestMode)\n\t})\n\tassert.Equal(t, \"[GIN-debug] [WARNING] Since SetHTMLTemplate() is NOT thread-safe. It should only be called\\nat initialization. ie. before any route is registered or the router is listening in a socket:\\n\\n\\trouter := gin.Default()\\n\\trouter.SetHTMLTemplate(template) // << good place\\n\\n\", re)\n}\n\nfunc TestDebugPrintWARNINGDefault(t *testing.T) {\n\tre := captureOutput(t, func() {\n\t\tSetMode(DebugMode)\n\t\tdebugPrintWARNINGDefault()\n\t\tSetMode(TestMode)\n\t})\n\tassert.Equal(t, \"[GIN-debug] [WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached.\\n\\n\", re)\n}\n\nfunc TestDebugPrintWARNINGDefaultWithUnsupportedVersion(t *testing.T) {\n\truntimeVersion = \"go1.23.12\"\n\tre := captureOutput(t, func() {\n\t\tSetMode(DebugMode)\n\t\tdebugPrintWARNINGDefault()\n\t\tSetMode(TestMode)\n\t})\n\tassert.Equal(t, \"[GIN-debug] [WARNING] Now Gin requires Go 1.25+.\\n\\n[GIN-debug] [WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached.\\n\\n\", re)\n}\n\nfunc TestDebugPrintWARNINGNew(t *testing.T) {\n\tre := captureOutput(t, func() {\n\t\tSetMode(DebugMode)\n\t\tdebugPrintWARNINGNew()\n\t\tSetMode(TestMode)\n\t})\n\tassert.Equal(t, \"[GIN-debug] [WARNING] Running in \\\"debug\\\" mode. Switch to \\\"release\\\" mode in production.\\n - using env:\\texport GIN_MODE=release\\n - using code:\\tgin.SetMode(gin.ReleaseMode)\\n\\n\", re)\n}\n\nfunc captureOutput(t *testing.T, f func()) string {\n\treader, writer, err := os.Pipe()\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\tdefaultWriter := DefaultWriter\n\tdefaultErrorWriter := DefaultErrorWriter\n\tdefer func() {\n\t\tDefaultWriter = defaultWriter\n\t\tDefaultErrorWriter = defaultErrorWriter\n\t\tlog.SetOutput(os.Stderr)\n\t}()\n\tDefaultWriter = writer\n\tDefaultErrorWriter = writer\n\tlog.SetOutput(writer)\n\tout := make(chan string)\n\twg := new(sync.WaitGroup)\n\twg.Add(1)\n\tgo func() {\n\t\tvar buf strings.Builder\n\t\twg.Done()\n\t\t_, err := io.Copy(&buf, reader)\n\t\tassert.NoError(t, err)\n\t\tout <- buf.String()\n\t}()\n\twg.Wait()\n\tf()\n\twriter.Close()\n\treturn <-out\n}\n\nfunc TestGetMinVer(t *testing.T) {\n\tvar m uint64\n\tvar e error\n\t_, e = getMinVer(\"go1\")\n\trequire.Error(t, e)\n\tm, e = getMinVer(\"go1.1\")\n\tassert.Equal(t, uint64(1), m)\n\trequire.NoError(t, e)\n\tm, e = getMinVer(\"go1.1.1\")\n\trequire.NoError(t, e)\n\tassert.Equal(t, uint64(1), m)\n\t_, e = getMinVer(\"go1.1.1.1\")\n\trequire.Error(t, e)\n}\n"
  },
  {
    "path": "deprecated.go",
    "content": "// Copyright 2014 Manu Martinez-Almeida. All rights reserved.\n// Use of this source code is governed by a MIT style\n// license that can be found in the LICENSE file.\n\npackage gin\n\nimport (\n\t\"log\"\n\n\t\"github.com/gin-gonic/gin/binding\"\n)\n\n// BindWith binds the passed struct pointer using the specified binding engine.\n// See the binding package.\n//\n// Deprecated: Use MustBindWith or ShouldBindWith.\nfunc (c *Context) BindWith(obj any, b binding.Binding) error {\n\tlog.Println(`BindWith(\\\"any, binding.Binding\\\") error is going to\n\tbe deprecated, please check issue #662 and either use MustBindWith() if you\n\twant HTTP 400 to be automatically returned if any error occur, or use\n\tShouldBindWith() if you need to manage the error.`)\n\treturn c.MustBindWith(obj, b)\n}\n"
  },
  {
    "path": "deprecated_test.go",
    "content": "// Copyright 2014 Manu Martinez-Almeida. All rights reserved.\n// Use of this source code is governed by a MIT style\n// license that can be found in the LICENSE file.\n\npackage gin\n\nimport (\n\t\"bytes\"\n\t\"net/http\"\n\t\"net/http/httptest\"\n\t\"testing\"\n\n\t\"github.com/gin-gonic/gin/binding\"\n\t\"github.com/stretchr/testify/assert\"\n)\n\nfunc TestBindWith(t *testing.T) {\n\tw := httptest.NewRecorder()\n\tc, _ := CreateTestContext(w)\n\n\tc.Request, _ = http.NewRequest(http.MethodPost, \"/?foo=bar&bar=foo\", bytes.NewBufferString(\"foo=unused\"))\n\n\tvar obj struct {\n\t\tFoo string `form:\"foo\"`\n\t\tBar string `form:\"bar\"`\n\t}\n\tcaptureOutput(t, func() {\n\t\tassert.NoError(t, c.BindWith(&obj, binding.Form))\n\t})\n\tassert.Equal(t, \"foo\", obj.Bar)\n\tassert.Equal(t, \"bar\", obj.Foo)\n\tassert.Equal(t, 0, w.Body.Len())\n}\n"
  },
  {
    "path": "doc.go",
    "content": "/*\nPackage gin implements a HTTP web framework called gin.\n\nSee https://gin-gonic.com/ for more information about gin.\n\nExample:\n\n\tpackage main\n\n\timport \"github.com/gin-gonic/gin\"\n\n\tfunc main() {\n\t\tr := gin.Default()\n\t\tr.GET(\"/ping\", func(c *gin.Context) {\n\t\t\tc.JSON(200, gin.H{\n\t\t\t\t\"message\": \"pong\",\n\t\t\t})\n\t\t})\n\t\tr.Run() // listen and serve on 0.0.0.0:8080\n\t}\n*/\npackage gin // import \"github.com/gin-gonic/gin\"\n"
  },
  {
    "path": "docs/doc.md",
    "content": "# Gin Quick Start\n\n## Contents\n\n- [Build Tags](#build-tags)\n  - [Build with json replacement](#build-with-json-replacement)\n  - [Build without MsgPack rendering feature](#build-without-msgpack-rendering-feature)\n- [Routing](#routing)\n  - [Using GET, POST, PUT, PATCH, DELETE and OPTIONS](#using-get-post-put-patch-delete-and-options)\n  - [Parameters in path](#parameters-in-path)\n  - [Querystring parameters](#querystring-parameters)\n  - [Multipart/Urlencoded Form](#multiparturlencoded-form)\n  - [Another example: query + post form](#another-example-query--post-form)\n  - [Map as querystring or postform parameters](#map-as-querystring-or-postform-parameters)\n  - [Upload files](#upload-files)\n    - [Single file](#single-file)\n    - [Multiple files](#multiple-files)\n  - [Grouping routes](#grouping-routes)\n  - [Redirects](#redirects)\n- [Middleware](#middleware)\n  - [Blank Gin without middleware by default](#blank-gin-without-middleware-by-default)\n  - [Using middleware](#using-middleware)\n  - [Custom Middleware](#custom-middleware)\n  - [Custom Recovery behavior](#custom-recovery-behavior)\n  - [Using BasicAuth() middleware](#using-basicauth-middleware)\n  - [Goroutines inside a middleware](#goroutines-inside-a-middleware)\n- [Logging](#logging)\n  - [How to write log file](#how-to-write-log-file)\n  - [Custom Log Format](#custom-log-format)\n  - [Skip logging](#skip-logging)\n  - [Controlling Log output coloring](#controlling-log-output-coloring)\n  - [Avoid logging query strings](#avoid-logging-query-strings)\n  - [Define format for the log of routes](#define-format-for-the-log-of-routes)\n- [Request Binding & Validation](#request-binding--validation)\n  - [Model binding and validation](#model-binding-and-validation)\n  - [Custom Validators](#custom-validators)\n  - [Only Bind Query String](#only-bind-query-string)\n  - [Bind Query String or Post Data](#bind-query-string-or-post-data)\n  - [Bind default value if none provided](#bind-default-value-if-none-provided)\n    - [Collection format for arrays](#collection-format-for-arrays)\n  - [Bind Uri](#bind-uri)\n  - [Bind custom unmarshaler](#bind-custom-unmarshaler)\n  - [Bind Header](#bind-header)\n  - [Bind HTML checkboxes](#bind-html-checkboxes)\n  - [Multipart/Urlencoded binding](#multiparturlencoded-binding)\n  - [Bind form-data request with custom struct](#bind-form-data-request-with-custom-struct)\n  - [Try to bind body into different structs](#try-to-bind-body-into-different-structs)\n  - [Bind form-data request with custom struct and custom tag](#bind-form-data-request-with-custom-struct-and-custom-tag)\n- [Response Rendering](#response-rendering)\n  - [XML, JSON, YAML, TOML and ProtoBuf rendering](#xml-json-yaml-toml-and-protobuf-rendering)\n    - [SecureJSON](#securejson)\n    - [JSONP](#jsonp)\n    - [AsciiJSON](#asciijson)\n    - [PureJSON](#purejson)\n  - [Serving static files](#serving-static-files)\n  - [Serving data from file](#serving-data-from-file)\n  - [Serving data from reader](#serving-data-from-reader)\n  - [HTML rendering](#html-rendering)\n    - [Custom Template renderer](#custom-template-renderer)\n    - [Custom Delimiters](#custom-delimiters)\n    - [Custom Template Funcs](#custom-template-funcs)\n  - [Multitemplate](#multitemplate)\n  - [Build a single binary with templates](#build-a-single-binary-with-templates)\n- [Server Configuration](#server-configuration)\n  - [Custom HTTP configuration](#custom-http-configuration)\n  - [Custom json codec at runtime](#custom-json-codec-at-runtime)\n  - [Support Let's Encrypt](#support-lets-encrypt)\n  - [Run multiple service using Gin](#run-multiple-service-using-gin)\n  - [Graceful shutdown or restart](#graceful-shutdown-or-restart)\n    - [Third-party packages](#third-party-packages)\n    - [Manually](#manually)\n  - [http2 server push](#http2-server-push)\n  - [Set and get a cookie](#set-and-get-a-cookie)\n  - [Don't trust all proxies](#dont-trust-all-proxies)\n- [Testing](#testing)\n\nYou can find a number of ready-to-run examples at [Gin examples repository](https://github.com/gin-gonic/examples).\n\n## Build tags\n\n### Build with json replacement\n\nGin uses `encoding/json` as the default JSON package but you can change it by building from other tags.\n\n[jsoniter](https://github.com/json-iterator/go)\n\n```sh\ngo build -tags=jsoniter .\n```\n\n[go-json](https://github.com/goccy/go-json)\n\n```sh\ngo build -tags=go_json .\n```\n\n[sonic](https://github.com/bytedance/sonic)\n\n```sh\ngo build -tags=sonic .\n```\n\n### Build without `MsgPack` rendering feature\n\nGin enables `MsgPack` rendering feature by default. But you can disable this feature by specifying `nomsgpack` build tag.\n\n```sh\ngo build -tags=nomsgpack .\n```\n\nThis is useful to reduce the binary size of executable files. See the [detail information](https://github.com/gin-gonic/gin/pull/1852).\n\n## Routing\n\n> Learn how to define routes, handle parameters, and organize endpoints.\n\n### Using GET, POST, PUT, PATCH, DELETE and OPTIONS\n\n```go\nfunc main() {\n  // Creates a gin router with default middleware:\n  // logger and recovery (crash-free) middleware\n  router := gin.Default()\n\n  router.GET(\"/someGet\", getting)\n  router.POST(\"/somePost\", posting)\n  router.PUT(\"/somePut\", putting)\n  router.DELETE(\"/someDelete\", deleting)\n  router.PATCH(\"/somePatch\", patching)\n  router.HEAD(\"/someHead\", head)\n  router.OPTIONS(\"/someOptions\", options)\n\n  // By default, it serves on :8080 unless a\n  // PORT environment variable was defined.\n  router.Run()\n  // router.Run(\":3000\") for a hard coded port\n}\n```\n\n### Parameters in path\n\n```go\nfunc main() {\n  router := gin.Default()\n\n  // This handler will match /user/john but will not match /user/ or /user\n  router.GET(\"/user/:name\", func(c *gin.Context) {\n    name := c.Param(\"name\")\n    c.String(http.StatusOK, \"Hello %s\", name)\n  })\n\n  // However, this one will match /user/john/ and also /user/john/send\n  // If no other routers match /user/john, it will redirect to /user/john/\n  router.GET(\"/user/:name/*action\", func(c *gin.Context) {\n    name := c.Param(\"name\")\n    action := c.Param(\"action\")\n    message := name + \" is \" + action\n    c.String(http.StatusOK, message)\n  })\n\n  // For each matched request Context will hold the route definition\n  router.POST(\"/user/:name/*action\", func(c *gin.Context) {\n    b := c.FullPath() == \"/user/:name/*action\" // true\n    c.String(http.StatusOK, \"%t\", b)\n  })\n\n  // This handler will add a new router for /user/groups.\n  // Exact routes are resolved before param routes, regardless of the order they were defined.\n  // Routes starting with /user/groups are never interpreted as /user/:name/... routes\n  router.GET(\"/user/groups\", func(c *gin.Context) {\n    c.String(http.StatusOK, \"The available groups are [...]\")\n  })\n\n  router.Run(\":8080\")\n}\n```\n\n### Querystring parameters\n\n```go\nfunc main() {\n  router := gin.Default()\n\n  // Query string parameters are parsed using the existing underlying request object.\n  // The request responds to a URL matching: /welcome?firstname=Jane&lastname=Doe\n  router.GET(\"/welcome\", func(c *gin.Context) {\n    firstname := c.DefaultQuery(\"firstname\", \"Guest\")\n    lastname := c.Query(\"lastname\") // shortcut for c.Request.URL.Query().Get(\"lastname\")\n\n    c.String(http.StatusOK, \"Hello %s %s\", firstname, lastname)\n  })\n  router.Run(\":8080\")\n}\n```\n\n### Multipart/Urlencoded Form\n\n```go\nfunc main() {\n  router := gin.Default()\n\n  router.POST(\"/form_post\", func(c *gin.Context) {\n    message := c.PostForm(\"message\")\n    nick := c.DefaultPostForm(\"nick\", \"anonymous\")\n\n    c.JSON(http.StatusOK, gin.H{\n      \"status\":  \"posted\",\n      \"message\": message,\n      \"nick\":    nick,\n    })\n  })\n  router.Run(\":8080\")\n}\n```\n\n### Another example: query + post form\n\n```sh\nPOST /post?id=1234&page=1 HTTP/1.1\nContent-Type: application/x-www-form-urlencoded\n\nname=manu&message=this_is_great\n```\n\n```go\nfunc main() {\n  router := gin.Default()\n\n  router.POST(\"/post\", func(c *gin.Context) {\n\n    id := c.Query(\"id\")\n    page := c.DefaultQuery(\"page\", \"0\")\n    name := c.PostForm(\"name\")\n    message := c.PostForm(\"message\")\n\n    fmt.Printf(\"id: %s; page: %s; name: %s; message: %s\", id, page, name, message)\n  })\n  router.Run(\":8080\")\n}\n```\n\n```sh\nid: 1234; page: 1; name: manu; message: this_is_great\n```\n\n### Map as querystring or postform parameters\n\n```sh\nPOST /post?ids[a]=1234&ids[b]=hello HTTP/1.1\nContent-Type: application/x-www-form-urlencoded\n\nnames[first]=thinkerou&names[second]=tianou\n```\n\n```go\nfunc main() {\n  router := gin.Default()\n\n  router.POST(\"/post\", func(c *gin.Context) {\n\n    ids := c.QueryMap(\"ids\")\n    names := c.PostFormMap(\"names\")\n\n    fmt.Printf(\"ids: %v; names: %v\", ids, names)\n  })\n  router.Run(\":8080\")\n}\n```\n\n```sh\nids: map[b:hello a:1234]; names: map[second:tianou first:thinkerou]\n```\n\n### Upload files\n\n#### Single file\n\nReferences issue [#774](https://github.com/gin-gonic/gin/issues/774) and detail [example code](https://github.com/gin-gonic/examples/tree/master/upload-file/single).\n\n`file.Filename` **SHOULD NOT** be trusted. See [`Content-Disposition` on MDN](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Disposition#Directives) and [#1693](https://github.com/gin-gonic/gin/issues/1693)\n\n> The filename is always optional and must not be used blindly by the application: path information should be stripped, and conversion to the server file system rules should be done.\n\n```go\nfunc main() {\n  router := gin.Default()\n  // Set a lower memory limit for multipart forms (default is 32 MiB)\n  router.MaxMultipartMemory = 8 << 20  // 8 MiB\n  router.POST(\"/upload\", func(c *gin.Context) {\n    // Single file\n    file, _ := c.FormFile(\"file\")\n    log.Println(file.Filename)\n\n    // Upload the file to specific dst.\n    c.SaveUploadedFile(file, dst)\n\n    c.String(http.StatusOK, fmt.Sprintf(\"'%s' uploaded!\", file.Filename))\n  })\n  router.Run(\":8080\")\n}\n```\n\nHow to `curl`:\n\n```bash\ncurl -X POST http://localhost:8080/upload \\\n  -F \"file=@/Users/appleboy/test.zip\" \\\n  -H \"Content-Type: multipart/form-data\"\n```\n\n#### Multiple files\n\nSee the detailed [example code](https://github.com/gin-gonic/examples/tree/master/upload-file/multiple).\n\n```go\nfunc main() {\n  router := gin.Default()\n  // Set a lower memory limit for multipart forms (default is 32 MiB)\n  router.MaxMultipartMemory = 8 << 20  // 8 MiB\n  router.POST(\"/upload\", func(c *gin.Context) {\n    // Multipart form\n    form, _ := c.MultipartForm()\n    files := form.File[\"upload[]\"]\n\n    for _, file := range files {\n      log.Println(file.Filename)\n\n      // Upload the file to specific dst.\n      c.SaveUploadedFile(file, dst)\n    }\n    c.String(http.StatusOK, fmt.Sprintf(\"%d files uploaded!\", len(files)))\n  })\n  router.Run(\":8080\")\n}\n```\n\nHow to `curl`:\n\n```bash\ncurl -X POST http://localhost:8080/upload \\\n  -F \"upload[]=@/Users/appleboy/test1.zip\" \\\n  -F \"upload[]=@/Users/appleboy/test2.zip\" \\\n  -H \"Content-Type: multipart/form-data\"\n```\n\n### Grouping routes\n\n```go\nfunc main() {\n  router := gin.Default()\n\n  // Simple group: v1\n  {\n    v1 := router.Group(\"/v1\")\n    v1.POST(\"/login\", loginEndpoint)\n    v1.POST(\"/submit\", submitEndpoint)\n    v1.POST(\"/read\", readEndpoint)\n  }\n\n  // Simple group: v2\n  {\n    v2 := router.Group(\"/v2\")\n    v2.POST(\"/login\", loginEndpoint)\n    v2.POST(\"/submit\", submitEndpoint)\n    v2.POST(\"/read\", readEndpoint)\n  }\n\n  router.Run(\":8080\")\n}\n```\n\n### Redirects\n\nIssuing a HTTP redirect is easy. Both internal and external locations are supported.\n\n```go\nr.GET(\"/test\", func(c *gin.Context) {\n  c.Redirect(http.StatusMovedPermanently, \"http://www.google.com/\")\n})\n```\n\nIssuing a HTTP redirect from POST. Refer to issue: [#444](https://github.com/gin-gonic/gin/issues/444)\n\n```go\nr.POST(\"/test\", func(c *gin.Context) {\n  c.Redirect(http.StatusFound, \"/foo\")\n})\n```\n\nIssuing a Router redirect, use `HandleContext` like below.\n\n```go\nr.GET(\"/test\", func(c *gin.Context) {\n    c.Request.URL.Path = \"/test2\"\n    r.HandleContext(c)\n})\nr.GET(\"/test2\", func(c *gin.Context) {\n    c.JSON(http.StatusOK, gin.H{\"hello\": \"world\"})\n})\n```\n\n## Middleware\n\n> Configure middleware for request processing, authentication, and error recovery.\n\n### Blank Gin without middleware by default\n\nUse\n\n```go\nr := gin.New()\n```\n\ninstead of\n\n```go\n// Default With the Logger and Recovery middleware already attached\nr := gin.Default()\n```\n\n### Using middleware\n\n```go\nfunc main() {\n  // Creates a router without any middleware by default\n  r := gin.New()\n\n  // Global middleware\n  // Logger middleware will write the logs to gin.DefaultWriter even if you set with GIN_MODE=release.\n  // By default gin.DefaultWriter = os.Stdout\n  r.Use(gin.Logger())\n\n  // Recovery middleware recovers from any panics and writes a 500 if there was one.\n  r.Use(gin.Recovery())\n\n  // Per route middleware, you can add as many as you desire.\n  r.GET(\"/benchmark\", MyBenchLogger(), benchEndpoint)\n\n  // Authorization group\n  // authorized := r.Group(\"/\", AuthRequired())\n  // exactly the same as:\n  authorized := r.Group(\"/\")\n  // per group middleware! in this case we use the custom created\n  // AuthRequired() middleware just in the \"authorized\" group.\n  authorized.Use(AuthRequired())\n  {\n    authorized.POST(\"/login\", loginEndpoint)\n    authorized.POST(\"/submit\", submitEndpoint)\n    authorized.POST(\"/read\", readEndpoint)\n\n    // nested group\n    testing := authorized.Group(\"testing\")\n    // visit 0.0.0.0:8080/testing/analytics\n    testing.GET(\"/analytics\", analyticsEndpoint)\n  }\n\n  // Listen and serve on 0.0.0.0:8080\n  r.Run(\":8080\")\n}\n```\n\n### Custom Middleware\n\n```go\nfunc Logger() gin.HandlerFunc {\n  return func(c *gin.Context) {\n    t := time.Now()\n\n    // Set example variable\n    c.Set(\"example\", \"12345\")\n\n    // before request\n\n    c.Next()\n\n    // after request\n    latency := time.Since(t)\n    log.Print(latency)\n\n    // access the status we are sending\n    status := c.Writer.Status()\n    log.Println(status)\n  }\n}\n\nfunc main() {\n  r := gin.New()\n  r.Use(Logger())\n\n  r.GET(\"/test\", func(c *gin.Context) {\n    example := c.MustGet(\"example\").(string)\n\n    // it would print: \"12345\"\n    log.Println(example)\n  })\n\n  // Listen and serve on 0.0.0.0:8080\n  r.Run(\":8080\")\n}\n```\n\n### Custom Recovery behavior\n\n```go\nfunc main() {\n  // Creates a router without any middleware by default\n  r := gin.New()\n\n  // Global middleware\n  // Logger middleware will write the logs to gin.DefaultWriter even if you set with GIN_MODE=release.\n  // By default gin.DefaultWriter = os.Stdout\n  r.Use(gin.Logger())\n\n  // Recovery middleware recovers from any panics and writes a 500 if there was one.\n  r.Use(gin.CustomRecovery(func(c *gin.Context, recovered any) {\n    if err, ok := recovered.(string); ok {\n      c.String(http.StatusInternalServerError, fmt.Sprintf(\"error: %s\", err))\n    }\n    c.AbortWithStatus(http.StatusInternalServerError)\n  }))\n\n  r.GET(\"/panic\", func(c *gin.Context) {\n    // panic with a string -- the custom middleware could save this to a database or report it to the user\n    panic(\"foo\")\n  })\n\n  r.GET(\"/\", func(c *gin.Context) {\n    c.String(http.StatusOK, \"ohai\")\n  })\n\n  // Listen and serve on 0.0.0.0:8080\n  r.Run(\":8080\")\n}\n```\n\n### Using BasicAuth() middleware\n\n```go\n// simulate some private data\nvar secrets = gin.H{\n  \"foo\":    gin.H{\"email\": \"foo@bar.com\", \"phone\": \"123433\"},\n  \"austin\": gin.H{\"email\": \"austin@example.com\", \"phone\": \"666\"},\n  \"lena\":   gin.H{\"email\": \"lena@guapa.com\", \"phone\": \"523443\"},\n}\n\nfunc main() {\n  r := gin.Default()\n\n  // Group using gin.BasicAuth() middleware\n  // gin.Accounts is a shortcut for map[string]string\n  authorized := r.Group(\"/admin\", gin.BasicAuth(gin.Accounts{\n    \"foo\":    \"bar\",\n    \"austin\": \"1234\",\n    \"lena\":   \"hello2\",\n    \"manu\":   \"4321\",\n  }))\n\n  // /admin/secrets endpoint\n  // hit \"localhost:8080/admin/secrets\n  authorized.GET(\"/secrets\", func(c *gin.Context) {\n    // get user, it was set by the BasicAuth middleware\n    user := c.MustGet(gin.AuthUserKey).(string)\n    if secret, ok := secrets[user]; ok {\n      c.JSON(http.StatusOK, gin.H{\"user\": user, \"secret\": secret})\n    } else {\n      c.JSON(http.StatusOK, gin.H{\"user\": user, \"secret\": \"NO SECRET :(\"})\n    }\n  })\n\n  // Listen and serve on 0.0.0.0:8080\n  r.Run(\":8080\")\n}\n```\n\n### Goroutines inside a middleware\n\nWhen starting new Goroutines inside a middleware or handler, you **SHOULD NOT** use the original context inside it, you have to use a read-only copy.\n\n```go\nfunc main() {\n  r := gin.Default()\n\n  r.GET(\"/long_async\", func(c *gin.Context) {\n    // create copy to be used inside the goroutine\n    cCp := c.Copy()\n    go func() {\n      // simulate a long task with time.Sleep(). 5 seconds\n      time.Sleep(5 * time.Second)\n\n      // note that you are using the copied context \"cCp\", IMPORTANT\n      log.Println(\"Done! in path \" + cCp.Request.URL.Path)\n    }()\n  })\n\n  r.GET(\"/long_sync\", func(c *gin.Context) {\n    // simulate a long task with time.Sleep(). 5 seconds\n    time.Sleep(5 * time.Second)\n\n    // since we are NOT using a goroutine, we do not have to copy the context\n    log.Println(\"Done! in path \" + c.Request.URL.Path)\n  })\n\n  // Listen and serve on 0.0.0.0:8080\n  r.Run(\":8080\")\n}\n```\n\n## Logging\n\n> Control log output, formatting, and filtering.\n\n### How to write log file\n\n```go\nfunc main() {\n  // Disable Console Color, you don't need console color when writing the logs to file.\n  gin.DisableConsoleColor()\n\n  // Logging to a file.\n  f, _ := os.Create(\"gin.log\")\n  gin.DefaultWriter = io.MultiWriter(f)\n\n  // Use the following code if you need to write the logs to file and console at the same time.\n  // gin.DefaultWriter = io.MultiWriter(f, os.Stdout)\n\n  router := gin.Default()\n  router.GET(\"/ping\", func(c *gin.Context) {\n      c.String(http.StatusOK, \"pong\")\n  })\n\n   router.Run(\":8080\")\n}\n```\n\n### Custom Log Format\n\n```go\nfunc main() {\n  router := gin.New()\n\n  // LoggerWithFormatter middleware will write the logs to gin.DefaultWriter\n  // By default gin.DefaultWriter = os.Stdout\n  router.Use(gin.LoggerWithFormatter(func(param gin.LogFormatterParams) string {\n\n    // your custom format\n    return fmt.Sprintf(\"%s - [%s] \\\"%s %s %s %d %s \\\"%s\\\" %s\\\"\\n\",\n        param.ClientIP,\n        param.TimeStamp.Format(time.RFC1123),\n        param.Method,\n        param.Path,\n        param.Request.Proto,\n        param.StatusCode,\n        param.Latency,\n        param.Request.UserAgent(),\n        param.ErrorMessage,\n    )\n  }))\n  router.Use(gin.Recovery())\n\n  router.GET(\"/ping\", func(c *gin.Context) {\n    c.String(http.StatusOK, \"pong\")\n  })\n\n  router.Run(\":8080\")\n}\n```\n\nSample Output\n\n```sh\n::1 - [Fri, 07 Dec 2018 17:04:38 JST] \"GET /ping HTTP/1.1 200 122.767µs \"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3578.80 Safari/537.36\" \"\n```\n\n### Skip logging\n\n```go\nfunc main() {\n  router := gin.New()\n\n  // skip logging for desired paths by setting SkipPaths in LoggerConfig\n  loggerConfig := gin.LoggerConfig{SkipPaths: []string{\"/metrics\"}}\n\n  // skip logging based on your logic by setting Skip func in LoggerConfig\n  loggerConfig.Skip = func(c *gin.Context) bool {\n      // as an example skip non server side errors\n      return c.Writer.Status() < http.StatusInternalServerError\n  }\n\n  router.Use(gin.LoggerWithConfig(loggerConfig))\n  router.Use(gin.Recovery())\n\n  // skipped\n  router.GET(\"/metrics\", func(c *gin.Context) {\n      c.Status(http.StatusNotImplemented)\n  })\n\n  // skipped\n  router.GET(\"/ping\", func(c *gin.Context) {\n      c.String(http.StatusOK, \"pong\")\n  })\n\n  // not skipped\n  router.GET(\"/data\", func(c *gin.Context) {\n    c.Status(http.StatusNotImplemented)\n  })\n\n  router.Run(\":8080\")\n}\n\n```\n\n### Controlling Log output coloring\n\nBy default, logs output on console should be colorized depending on the detected TTY.\n\nNever colorize logs:\n\n```go\nfunc main() {\n  // Disable log's color\n  gin.DisableConsoleColor()\n\n  // Creates a gin router with default middleware:\n  // logger and recovery (crash-free) middleware\n  router := gin.Default()\n\n  router.GET(\"/ping\", func(c *gin.Context) {\n      c.String(http.StatusOK, \"pong\")\n  })\n\n  router.Run(\":8080\")\n}\n```\n\nAlways colorize logs:\n\n```go\nfunc main() {\n  // Force log's color\n  gin.ForceConsoleColor()\n\n  // Creates a gin router with default middleware:\n  // logger and recovery (crash-free) middleware\n  router := gin.Default()\n\n  router.GET(\"/ping\", func(c *gin.Context) {\n      c.String(http.StatusOK, \"pong\")\n  })\n\n  router.Run(\":8080\")\n}\n```\n\n### Avoid logging query strings\n\n```go\nfunc main() {\n  router := gin.New()\n\n  // SkipQueryString indicates that the logger should not log the query string.\n  // For example, /path?q=1 will be logged as /path\n  loggerConfig := gin.LoggerConfig{SkipQueryString: true}\n\n  router.Use(gin.LoggerWithConfig(loggerConfig))\n}\n```\n\n### Define format for the log of routes\n\nThe default log of routes is:\n\n```sh\n[GIN-debug] POST   /foo                      --> main.main.func1 (3 handlers)\n[GIN-debug] GET    /bar                      --> main.main.func2 (3 handlers)\n[GIN-debug] GET    /status                   --> main.main.func3 (3 handlers)\n```\n\nIf you want to log this information in given format (e.g. JSON, key values or something else), then you can define this format with `gin.DebugPrintRouteFunc`.\nIn the example below, we log all routes with standard log package but you can use another log tools that suits of your needs.\n\n```go\nimport (\n  \"log\"\n  \"net/http\"\n\n  \"github.com/gin-gonic/gin\"\n)\n\nfunc main() {\n  r := gin.Default()\n  gin.DebugPrintRouteFunc = func(httpMethod, absolutePath, handlerName string, nuHandlers int) {\n    log.Printf(\"endpoint %v %v %v %v\\n\", httpMethod, absolutePath, handlerName, nuHandlers)\n  }\n\n  r.POST(\"/foo\", func(c *gin.Context) {\n    c.JSON(http.StatusOK, \"foo\")\n  })\n\n  r.GET(\"/bar\", func(c *gin.Context) {\n    c.JSON(http.StatusOK, \"bar\")\n  })\n\n  r.GET(\"/status\", func(c *gin.Context) {\n    c.JSON(http.StatusOK, \"ok\")\n  })\n\n  // Listen and Server in http://0.0.0.0:8080\n  r.Run()\n}\n```\n\n## Request Binding & Validation\n\n> Bind and validate request data from query strings, forms, JSON, and more.\n\n### Model binding and validation\n\nTo bind a request body into a type, use model binding. We currently support binding of JSON, XML, YAML, TOML and standard form values (foo=bar&boo=baz).\n\nGin uses [**go-playground/validator/v10**](https://github.com/go-playground/validator) for validation. Check the full docs on tags usage [here](https://pkg.go.dev/github.com/go-playground/validator#hdr-Baked_In_Validators_and_Tags).\n\nNote that you need to set the corresponding binding tag on all fields you want to bind. For example, when binding from JSON, set `json:\"fieldname\"`.\n\nAlso, Gin provides two sets of methods for binding:\n\n- **Type** - Must bind\n  - **Methods** - `Bind`, `BindJSON`, `BindXML`, `BindQuery`, `BindYAML`, `BindHeader`, `BindTOML`\n  - **Behavior** - These methods use `MustBindWith` under the hood. If there is a binding error, the request is aborted with `c.AbortWithError(400, err).SetType(ErrorTypeBind)`. This sets the response status code to 400 and the `Content-Type` header is set to `text/plain; charset=utf-8`. Note that if you try to set the response code after this, it will result in a warning `[GIN-debug] [WARNING] Headers were already written. Wanted to override status code 400 with 422`. If you wish to have greater control over the behavior, consider using the `ShouldBind` equivalent method.\n- **Type** - Should bind\n  - **Methods** - `ShouldBind`, `ShouldBindJSON`, `ShouldBindXML`, `ShouldBindQuery`, `ShouldBindYAML`, `ShouldBindHeader`, `ShouldBindTOML`,\n  - **Behavior** - These methods use `ShouldBindWith` under the hood. If there is a binding error, the error is returned and it is the developer's responsibility to handle the request and error appropriately.\n\nWhen using the Bind-method, Gin tries to infer the binder depending on the Content-Type header. If you are sure what you are binding, you can use `MustBindWith` or `ShouldBindWith`.\n\nYou can also specify that specific fields are required. If a field is decorated with `binding:\"required\"` and has an empty value when binding, an error will be returned.\n\n```go\n// Binding from JSON\ntype Login struct {\n  User     string `form:\"user\" json:\"user\" xml:\"user\" binding:\"required\"`\n  Password string `form:\"password\" json:\"password\" xml:\"password\" binding:\"required\"`\n}\n\nfunc main() {\n  router := gin.Default()\n\n  // Example for binding JSON ({\"user\": \"manu\", \"password\": \"123\"})\n  router.POST(\"/loginJSON\", func(c *gin.Context) {\n    var json Login\n    if err := c.ShouldBindJSON(&json); err != nil {\n      c.JSON(http.StatusBadRequest, gin.H{\"error\": err.Error()})\n      return\n    }\n\n    if json.User != \"manu\" || json.Password != \"123\" {\n      c.JSON(http.StatusUnauthorized, gin.H{\"status\": \"unauthorized\"})\n      return\n    }\n\n    c.JSON(http.StatusOK, gin.H{\"status\": \"you are logged in\"})\n  })\n\n  // Example for binding XML (\n  //  <?xml version=\"1.0\" encoding=\"UTF-8\"?>\n  //  <root>\n  //    <user>manu</user>\n  //    <password>123</password>\n  //  </root>)\n  router.POST(\"/loginXML\", func(c *gin.Context) {\n    var xml Login\n    if err := c.ShouldBindXML(&xml); err != nil {\n      c.JSON(http.StatusBadRequest, gin.H{\"error\": err.Error()})\n      return\n    }\n\n    if xml.User != \"manu\" || xml.Password != \"123\" {\n      c.JSON(http.StatusUnauthorized, gin.H{\"status\": \"unauthorized\"})\n      return\n    }\n\n    c.JSON(http.StatusOK, gin.H{\"status\": \"you are logged in\"})\n  })\n\n  // Example for binding a HTML form (user=manu&password=123)\n  router.POST(\"/loginForm\", func(c *gin.Context) {\n    var form Login\n    // This will infer what binder to use depending on the content-type header.\n    if err := c.ShouldBind(&form); err != nil {\n      c.JSON(http.StatusBadRequest, gin.H{\"error\": err.Error()})\n      return\n    }\n\n    if form.User != \"manu\" || form.Password != \"123\" {\n      c.JSON(http.StatusUnauthorized, gin.H{\"status\": \"unauthorized\"})\n      return\n    }\n\n    c.JSON(http.StatusOK, gin.H{\"status\": \"you are logged in\"})\n  })\n\n  // Listen and serve on 0.0.0.0:8080\n  router.Run(\":8080\")\n}\n```\n\nSample request\n\n```sh\n$ curl -v -X POST \\\n  http://localhost:8080/loginJSON \\\n  -H 'content-type: application/json' \\\n  -d '{ \"user\": \"manu\" }'\n> POST /loginJSON HTTP/1.1\n> Host: localhost:8080\n> User-Agent: curl/7.51.0\n> Accept: */*\n> content-type: application/json\n> Content-Length: 18\n>\n* upload completely sent off: 18 out of 18 bytes\n< HTTP/1.1 400 Bad Request\n< Content-Type: application/json; charset=utf-8\n< Date: Fri, 04 Aug 2017 03:51:31 GMT\n< Content-Length: 100\n<\n{\"error\":\"Key: 'Login.Password' Error:Field validation for 'Password' failed on the 'required' tag\"}\n```\n\nSkip-validation: Running the example above using the `curl` command returns an error. This is because the example uses `binding:\"required\"` for `Password`. If instead, you use `binding:\"-\"` for `Password`, then it will not return an error when you run the example again.\n\n### Custom Validators\n\nIt is also possible to register custom validators. See the [example code](https://github.com/gin-gonic/examples/tree/master/custom-validation/server.go).\n\n```go\npackage main\n\nimport (\n  \"net/http\"\n  \"time\"\n\n  \"github.com/gin-gonic/gin\"\n  \"github.com/gin-gonic/gin/binding\"\n  \"github.com/go-playground/validator/v10\"\n)\n\n// Booking contains binded and validated data.\ntype Booking struct {\n  CheckIn  time.Time `form:\"check_in\" binding:\"required,bookabledate\" time_format:\"2006-01-02\"`\n  CheckOut time.Time `form:\"check_out\" binding:\"required,gtfield=CheckIn\" time_format:\"2006-01-02\"`\n}\n\nvar bookableDate validator.Func = func(fl validator.FieldLevel) bool {\n  date, ok := fl.Field().Interface().(time.Time)\n  if ok {\n    today := time.Now()\n    if today.After(date) {\n      return false\n    }\n  }\n  return true\n}\n\nfunc main() {\n  route := gin.Default()\n\n  if v, ok := binding.Validator.Engine().(*validator.Validate); ok {\n    v.RegisterValidation(\"bookabledate\", bookableDate)\n  }\n\n  route.GET(\"/bookable\", getBookable)\n  route.Run(\":8085\")\n}\n\nfunc getBookable(c *gin.Context) {\n  var b Booking\n  if err := c.ShouldBindWith(&b, binding.Query); err == nil {\n    c.JSON(http.StatusOK, gin.H{\"message\": \"Booking dates are valid!\"})\n  } else {\n    c.JSON(http.StatusBadRequest, gin.H{\"error\": err.Error()})\n  }\n}\n```\n\n```console\n$ curl \"localhost:8085/bookable?check_in=2030-04-16&check_out=2030-04-17\"\n{\"message\":\"Booking dates are valid!\"}\n\n$ curl \"localhost:8085/bookable?check_in=2030-03-10&check_out=2030-03-09\"\n{\"error\":\"Key: 'Booking.CheckOut' Error:Field validation for 'CheckOut' failed on the 'gtfield' tag\"}\n\n$ curl \"localhost:8085/bookable?check_in=2000-03-09&check_out=2000-03-10\"\n{\"error\":\"Key: 'Booking.CheckIn' Error:Field validation for 'CheckIn' failed on the 'bookabledate' tag\"}%\n```\n\n[Struct level validations](https://github.com/go-playground/validator/releases/tag/v8.7) can also be registered this way.\nSee the [struct-lvl-validation example](https://github.com/gin-gonic/examples/tree/master/struct-lvl-validations) to learn more.\n\n### Only Bind Query String\n\n`ShouldBindQuery` function only binds the query params and not the post data. See the [detail information](https://github.com/gin-gonic/gin/issues/742#issuecomment-315953017).\n\n```go\npackage main\n\nimport (\n  \"log\"\n  \"net/http\"\n\n  \"github.com/gin-gonic/gin\"\n)\n\ntype Person struct {\n  Name    string `form:\"name\"`\n  Address string `form:\"address\"`\n}\n\nfunc main() {\n  route := gin.Default()\n  route.Any(\"/testing\", startPage)\n  route.Run(\":8085\")\n}\n\nfunc startPage(c *gin.Context) {\n  var person Person\n  if c.ShouldBindQuery(&person) == nil {\n    log.Println(\"====== Only Bind By Query String ======\")\n    log.Println(person.Name)\n    log.Println(person.Address)\n  }\n  c.String(http.StatusOK, \"Success\")\n}\n\n```\n\n### Bind Query String or Post Data\n\nSee the [detail information](https://github.com/gin-gonic/gin/issues/742#issuecomment-264681292).\n\n```go\npackage main\n\nimport (\n  \"log\"\n  \"net/http\"\n  \"time\"\n\n  \"github.com/gin-gonic/gin\"\n)\n\ntype Person struct {\n  Name       string    `form:\"name\"`\n  Address    string    `form:\"address\"`\n  Birthday   time.Time `form:\"birthday\" time_format:\"2006-01-02\" time_utc:\"1\"`\n  CreateTime time.Time `form:\"createTime\" time_format:\"unixNano\"`\n  UnixTime   time.Time `form:\"unixTime\" time_format:\"unix\"`\n  UnixMilliTime   time.Time `form:\"unixMilliTime\" time_format:\"unixmilli\"`\n  UnixMicroTime   time.Time `form:\"unixMicroTime\" time_format:\"uNiXmIcRo\"` // case does not matter for \"unix*\" time formats\n}\n\nfunc main() {\n  route := gin.Default()\n  route.GET(\"/testing\", startPage)\n  route.Run(\":8085\")\n}\n\nfunc startPage(c *gin.Context) {\n  var person Person\n  // If `GET`, only `Form` binding engine (`query`) used.\n  // If `POST`, first checks the `content-type` for `JSON` or `XML`, then uses `Form` (`form-data`).\n  // See more at https://github.com/gin-gonic/gin/blob/master/binding/binding.go#L88\n  if c.ShouldBind(&person) == nil {\n    log.Println(person.Name)\n    log.Println(person.Address)\n    log.Println(person.Birthday)\n    log.Println(person.CreateTime)\n    log.Println(person.UnixTime)\n    log.Println(person.UnixMilliTime)\n    log.Println(person.UnixMicroTime)\n  }\n\n  c.String(http.StatusOK, \"Success\")\n}\n```\n\nTest it with:\n\n```sh\ncurl -X GET \"localhost:8085/testing?name=appleboy&address=xyz&birthday=1992-03-15&createTime=1562400033000000123&unixTime=1562400033&unixMilliTime=1562400033001&unixMicroTime=1562400033000012\"\n```\n\n### Bind default value if none provided\n\nIf the server should bind a default value to a field when the client does not provide one, specify the default value using the `default` key within the `form` tag:\n\n```go\npackage main\n\nimport (\n  \"net/http\"\n\n  \"github.com/gin-gonic/gin\"\n)\n\ntype Person struct {\n  Name      string    `form:\"name,default=William\"`\n  Age       int       `form:\"age,default=10\"`\n  Friends   []string  `form:\"friends,default=Will;Bill\"`\n  Addresses [2]string `form:\"addresses,default=foo bar\" collection_format:\"ssv\"`\n  LapTimes  []int     `form:\"lap_times,default=1;2;3\" collection_format:\"csv\"`\n}\n\nfunc main() {\n  g := gin.Default()\n  g.POST(\"/person\", func(c *gin.Context) {\n    var req Person\n    if err := c.ShouldBindQuery(&req); err != nil {\n      c.JSON(http.StatusBadRequest, err)\n      return\n    }\n    c.JSON(http.StatusOK, req)\n  })\n  _ = g.Run(\"localhost:8080\")\n}\n```\n\n```sh\ncurl -X POST http://localhost:8080/person\n{\"Name\":\"William\",\"Age\":10,\"Friends\":[\"Will\",\"Bill\"],\"Colors\":[\"red\",\"blue\"],\"LapTimes\":[1,2,3]}\n```\n\nNOTE: For default [collection values](#collection-format-for-arrays), the following rules apply:\n\n- Since commas are used to delimit tag options, they are not supported within a default value and will result in undefined behavior\n- For the collection formats \"multi\" and \"csv\", a semicolon should be used in place of a comma to delimit default values\n- Since semicolons are used to delimit default values for \"multi\" and \"csv\", they are not supported within a default value for \"multi\" and \"csv\"\n\n#### Collection format for arrays\n\n| Format          | Description                                               | Example                 |\n| --------------- | --------------------------------------------------------- | ----------------------- |\n| multi (default) | Multiple parameter instances rather than multiple values. | key=foo&key=bar&key=baz |\n| csv             | Comma-separated values.                                   | foo,bar,baz             |\n| ssv             | Space-separated values.                                   | foo bar baz             |\n| tsv             | Tab-separated values.                                     | \"foo\\tbar\\tbaz\"         |\n| pipes           | Pipe-separated values.                                    | foo\\|bar\\|baz           |\n\n```go\npackage main\n\nimport (\n  \"log\"\n  \"time\"\n  \"github.com/gin-gonic/gin\"\n)\n\ntype Person struct {\n  Name       string    `form:\"name\"`\n  Addresses  []string  `form:\"addresses\" collection_format:\"csv\"`\n  Birthday   time.Time `form:\"birthday\" time_format:\"2006-01-02\" time_utc:\"1\"`\n  CreateTime time.Time `form:\"createTime\" time_format:\"unixNano\"`\n  UnixTime   time.Time `form:\"unixTime\" time_format:\"unix\"`\n}\n\nfunc main() {\n  route := gin.Default()\n  route.GET(\"/testing\", startPage)\n  route.Run(\":8085\")\n}\nfunc startPage(c *gin.Context) {\n  var person Person\n  // If `GET`, only `Form` binding engine (`query`) used.\n  // If `POST`, first checks the `content-type` for `JSON` or `XML`, then uses `Form` (`form-data`).\n  // See more at https://github.com/gin-gonic/gin/blob/master/binding/binding.go#L48\n        if c.ShouldBind(&person) == nil {\n                log.Println(person.Name)\n                log.Println(person.Addresses)\n                log.Println(person.Birthday)\n                log.Println(person.CreateTime)\n                log.Println(person.UnixTime)\n        }\n  c.String(200, \"Success\")\n}\n```\n\nTest it with:\n\n```sh\ncurl -X GET \"localhost:8085/testing?name=appleboy&addresses=foo,bar&birthday=1992-03-15&createTime=1562400033000000123&unixTime=1562400033\"\n```\n\n### Bind Uri\n\nSee the [detail information](https://github.com/gin-gonic/gin/issues/846).\n\n```go\npackage main\n\nimport (\n  \"net/http\"\n\n  \"github.com/gin-gonic/gin\"\n)\n\ntype Person struct {\n  ID string `uri:\"id\" binding:\"required,uuid\"`\n  Name string `uri:\"name\" binding:\"required\"`\n}\n\nfunc main() {\n  route := gin.Default()\n  route.GET(\"/:name/:id\", func(c *gin.Context) {\n    var person Person\n    if err := c.ShouldBindUri(&person); err != nil {\n      c.JSON(http.StatusBadRequest, gin.H{\"msg\": err.Error()})\n      return\n    }\n    c.JSON(http.StatusOK, gin.H{\"name\": person.Name, \"uuid\": person.ID})\n  })\n  route.Run(\":8088\")\n}\n```\n\nTest it with:\n\n```sh\ncurl -v localhost:8088/thinkerou/987fbc97-4bed-5078-9f07-9141ba07c9f3\ncurl -v localhost:8088/thinkerou/not-uuid\n```\n\n### Bind custom unmarshaler\n\nTo override gin's default binding logic, define a function on your type that satisfies the `encoding.TextUnmarshaler` interface from the Golang standard library. Then specify `parser=encoding.TextUnmarshaler` in the `uri`/`form` tag of the field being bound.\n\n```go\npackage main\n\nimport (\n  \"encoding\"\n  \"strings\"\n\n  \"github.com/gin-gonic/gin\"\n)\n\ntype Birthday string\n\nfunc (b *Birthday) UnmarshalText(text []byte) error {\n  *b = Birthday(strings.Replace(string(text), \"-\", \"/\", -1))\n  return nil\n}\n\nvar _ encoding.TextUnmarshaler = (*Birthday)(nil) //assert Birthday implements encoding.TextUnmarshaler\n\nfunc main() {\n  route := gin.Default()\n  var request struct {\n    Birthday         Birthday   `form:\"birthday,parser=encoding.TextUnmarshaler\"`\n    Birthdays        []Birthday `form:\"birthdays,parser=encoding.TextUnmarshaler\" collection_format:\"csv\"`\n    BirthdaysDefault []Birthday `form:\"birthdaysDef,default=2020-09-01;2020-09-02,parser=encoding.TextUnmarshaler\" collection_format:\"csv\"`\n  }\n  route.GET(\"/test\", func(ctx *gin.Context) {\n    _ = ctx.BindQuery(&request)\n    ctx.JSON(200, request)\n  })\n  _ = route.Run(\":8088\")\n}\n```\n\nTest it with:\n\n```sh\ncurl 'localhost:8088/test?birthday=2000-01-01&birthdays=2000-01-01,2000-01-02'\n```\n\nResult\n\n```sh\n{\"Birthday\":\"2000/01/01\",\"Birthdays\":[\"2000/01/01\",\"2000/01/02\"],\"BirthdaysDefault\":[\"2020/09/01\",\"2020/09/02\"]}\n```\n\nNote:\n\n- If `parser=encoding.TextUnmarshaler` is specified for a type that does **not** implement `encoding.TextUnmarshaler`, gin will ignore it and proceed with its default binding logic.\n- If `parser=encoding.TextUnmarshaler` is specified for a type and that type's implementation of `encoding.TextUnmarshaler` returns an error, gin will stop binding and return the error to the client.\n\n---\n\nIf a type already implements `encoding.TextUnmarshaler` but you want to customize how gin binds the type differently (eg to change what error message is returned), you can implement the dedicated `BindUnmarshaler` interface provided by gin instead.\n\n```go\npackage main\n\nimport (\n  \"strings\"\n\n  \"github.com/gin-gonic/gin\"\n  \"github.com/gin-gonic/gin/binding\"\n)\n\ntype Birthday string\n\nfunc (b *Birthday) UnmarshalParam(param string) error {\n  *b = Birthday(strings.Replace(param, \"-\", \"/\", -1))\n  return nil\n}\n\nvar _ binding.BindUnmarshaler = (*Birthday)(nil) //assert Birthday implements binding.BindUnmarshaler\n\nfunc main() {\n  route := gin.Default()\n  var request struct {\n    Birthday         Birthday   `form:\"birthday\"`\n    Birthdays        []Birthday `form:\"birthdays\" collection_format:\"csv\"`\n    BirthdaysDefault []Birthday `form:\"birthdaysDef,default=2020-09-01;2020-09-02\" collection_format:\"csv\"`\n  }\n  route.GET(\"/test\", func(ctx *gin.Context) {\n    _ = ctx.BindQuery(&request)\n    ctx.JSON(200, request)\n  })\n  _ = route.Run(\":8088\")\n}\n```\n\nTest it with:\n\n```sh\ncurl 'localhost:8088/test?birthday=2000-01-01&birthdays=2000-01-01,2000-01-02'\n```\n\nResult\n\n```sh\n{\"Birthday\":\"2000/01/01\",\"Birthdays\":[\"2000/01/01\",\"2000/01/02\"],\"BirthdaysDefault\":[\"2020/09/01\",\"2020/09/02\"]}\n```\n\nNote:\n\n- If a type implements both `encoding.TextUnmarshaler` and `BindUnmarshaler`, gin will use `BindUnmarshaler` by default unless you specify `parser=encoding.TextUnmarshaler` in the binding tag.\n- If a type returns an error from its implementation of `BindUnmarshaler`, gin will stop binding and return the error to the client.\n\n### Bind Header\n\n```go\npackage main\n\nimport (\n  \"fmt\"\n  \"net/http\"\n\n  \"github.com/gin-gonic/gin\"\n)\n\ntype testHeader struct {\n  Rate   int    `header:\"Rate\"`\n  Domain string `header:\"Domain\"`\n}\n\nfunc main() {\n  r := gin.Default()\n  r.GET(\"/\", func(c *gin.Context) {\n    h := testHeader{}\n\n    if err := c.ShouldBindHeader(&h); err != nil {\n      c.JSON(http.StatusOK, err)\n    }\n\n    fmt.Printf(\"%#v\\n\", h)\n    c.JSON(http.StatusOK, gin.H{\"Rate\": h.Rate, \"Domain\": h.Domain})\n  })\n\n  r.Run()\n\n// client\n// curl -H \"rate:300\" -H \"domain:music\" 127.0.0.1:8080/\n// output\n// {\"Domain\":\"music\",\"Rate\":300}\n}\n```\n\n### Bind HTML checkboxes\n\nSee the [detail information](https://github.com/gin-gonic/gin/issues/129#issuecomment-124260092)\n\nmain.go\n\n```go\n...\n\ntype myForm struct {\n    Colors []string `form:\"colors[]\"`\n}\n\n...\n\nfunc formHandler(c *gin.Context) {\n    var fakeForm myForm\n    c.ShouldBind(&fakeForm)\n    c.JSON(http.StatusOK, gin.H{\"color\": fakeForm.Colors})\n}\n\n...\n\n```\n\nform.html\n\n```html\n<form action=\"/\" method=\"POST\">\n  <p>Check some colors</p>\n  <label for=\"red\">Red</label>\n  <input type=\"checkbox\" name=\"colors[]\" value=\"red\" id=\"red\" />\n  <label for=\"green\">Green</label>\n  <input type=\"checkbox\" name=\"colors[]\" value=\"green\" id=\"green\" />\n  <label for=\"blue\">Blue</label>\n  <input type=\"checkbox\" name=\"colors[]\" value=\"blue\" id=\"blue\" />\n  <input type=\"submit\" />\n</form>\n```\n\nresult:\n\n```json\n{ \"color\": [\"red\", \"green\", \"blue\"] }\n```\n\n### Multipart/Urlencoded binding\n\n```go\ntype ProfileForm struct {\n  Name   string                `form:\"name\" binding:\"required\"`\n  Avatar *multipart.FileHeader `form:\"avatar\" binding:\"required\"`\n\n  // or for multiple files\n  // Avatars []*multipart.FileHeader `form:\"avatar\" binding:\"required\"`\n}\n\nfunc main() {\n  router := gin.Default()\n  router.POST(\"/profile\", func(c *gin.Context) {\n    // you can bind multipart form with explicit binding declaration:\n    // c.ShouldBindWith(&form, binding.Form)\n    // or you can simply use autobinding with ShouldBind method:\n    var form ProfileForm\n    // in this case proper binding will be automatically selected\n    if err := c.ShouldBind(&form); err != nil {\n      c.String(http.StatusBadRequest, \"bad request\")\n      return\n    }\n\n    err := c.SaveUploadedFile(form.Avatar, form.Avatar.Filename)\n    if err != nil {\n      c.String(http.StatusInternalServerError, \"unknown error\")\n      return\n    }\n\n    // db.Save(&form)\n\n    c.String(http.StatusOK, \"ok\")\n  })\n  router.Run(\":8080\")\n}\n```\n\nTest it with:\n\n```sh\ncurl -X POST -v --form name=user --form \"avatar=@./avatar.png\" http://localhost:8080/profile\n```\n\n### Bind form-data request with custom struct\n\nThe follow example using custom struct:\n\n```go\ntype StructA struct {\n    FieldA string `form:\"field_a\"`\n}\n\ntype StructB struct {\n    NestedStruct StructA\n    FieldB string `form:\"field_b\"`\n}\n\ntype StructC struct {\n    NestedStructPointer *StructA\n    FieldC string `form:\"field_c\"`\n}\n\ntype StructD struct {\n    NestedAnonyStruct struct {\n        FieldX string `form:\"field_x\"`\n    }\n    FieldD string `form:\"field_d\"`\n}\n\nfunc GetDataB(c *gin.Context) {\n    var b StructB\n    c.Bind(&b)\n    c.JSON(http.StatusOK, gin.H{\n        \"a\": b.NestedStruct,\n        \"b\": b.FieldB,\n    })\n}\n\nfunc GetDataC(c *gin.Context) {\n    var b StructC\n    c.Bind(&b)\n    c.JSON(http.StatusOK, gin.H{\n        \"a\": b.NestedStructPointer,\n        \"c\": b.FieldC,\n    })\n}\n\nfunc GetDataD(c *gin.Context) {\n    var b StructD\n    c.Bind(&b)\n    c.JSON(http.StatusOK, gin.H{\n        \"x\": b.NestedAnonyStruct,\n        \"d\": b.FieldD,\n    })\n}\n\nfunc main() {\n    r := gin.Default()\n    r.GET(\"/getb\", GetDataB)\n    r.GET(\"/getc\", GetDataC)\n    r.GET(\"/getd\", GetDataD)\n\n    r.Run()\n}\n```\n\nUsing the command `curl` command result:\n\n```sh\n$ curl \"http://localhost:8080/getb?field_a=hello&field_b=world\"\n{\"a\":{\"FieldA\":\"hello\"},\"b\":\"world\"}\n$ curl \"http://localhost:8080/getc?field_a=hello&field_c=world\"\n{\"a\":{\"FieldA\":\"hello\"},\"c\":\"world\"}\n$ curl \"http://localhost:8080/getd?field_x=hello&field_d=world\"\n{\"d\":\"world\",\"x\":{\"FieldX\":\"hello\"}}\n```\n\n### Try to bind body into different structs\n\nThe normal methods for binding request body consumes `c.Request.Body` and they\ncannot be called multiple times.\n\n```go\ntype formA struct {\n  Foo string `json:\"foo\" xml:\"foo\" binding:\"required\"`\n}\n\ntype formB struct {\n  Bar string `json:\"bar\" xml:\"bar\" binding:\"required\"`\n}\n\nfunc SomeHandler(c *gin.Context) {\n  objA := formA{}\n  objB := formB{}\n  // Calling c.ShouldBind consumes c.Request.Body and it cannot be reused.\n  if errA := c.ShouldBind(&objA); errA == nil {\n    c.String(http.StatusOK, `the body should be formA`)\n  // Always an error is occurred by this because c.Request.Body is EOF now.\n  } else if errB := c.ShouldBind(&objB); errB == nil {\n    c.String(http.StatusOK, `the body should be formB`)\n  } else {\n    ...\n  }\n}\n```\n\nFor this, you can use `c.ShouldBindBodyWith` or shortcuts.\n\n- `c.ShouldBindBodyWithJSON` is a shortcut for c.ShouldBindBodyWith(obj, binding.JSON).\n- `c.ShouldBindBodyWithXML` is a shortcut for c.ShouldBindBodyWith(obj, binding.XML).\n- `c.ShouldBindBodyWithYAML` is a shortcut for c.ShouldBindBodyWith(obj, binding.YAML).\n- `c.ShouldBindBodyWithTOML` is a shortcut for c.ShouldBindBodyWith(obj, binding.TOML).\n\n```go\nfunc SomeHandler(c *gin.Context) {\n  objA := formA{}\n  objB := formB{}\n  // This reads c.Request.Body and stores the result into the context.\n  if errA := c.ShouldBindBodyWith(&objA, binding.Form); errA == nil {\n    c.String(http.StatusOK, `the body should be formA`)\n  // At this time, it reuses body stored in the context.\n  } else if errB := c.ShouldBindBodyWith(&objB, binding.JSON); errB == nil {\n    c.String(http.StatusOK, `the body should be formB JSON`)\n  // And it can accepts other formats\n  } else if errB2 := c.ShouldBindBodyWithXML(&objB); errB2 == nil {\n    c.String(http.StatusOK, `the body should be formB XML`)\n  } else {\n    ...\n  }\n}\n```\n\n1. `c.ShouldBindBodyWith` stores body into the context before binding. This has\n   a slight impact to performance, so you should not use this method if you are\n   enough to call binding at once.\n2. This feature is only needed for some formats -- `JSON`, `XML`, `MsgPack`,\n   `ProtoBuf`. For other formats, `Query`, `Form`, `FormPost`, `FormMultipart`,\n   can be called by `c.ShouldBind()` multiple times without any damage to\n   performance (See [#1341](https://github.com/gin-gonic/gin/pull/1341)).\n\n### Bind form-data request with custom struct and custom tag\n\n```go\nconst (\n  customerTag = \"url\"\n  defaultMemory = 32 << 20\n)\n\ntype customerBinding struct {}\n\nfunc (customerBinding) Name() string {\n  return \"form\"\n}\n\nfunc (customerBinding) Bind(req *http.Request, obj any) error {\n  if err := req.ParseForm(); err != nil {\n    return err\n  }\n  if err := req.ParseMultipartForm(defaultMemory); err != nil {\n    if err != http.ErrNotMultipart {\n      return err\n    }\n  }\n  if err := binding.MapFormWithTag(obj, req.Form, customerTag); err != nil {\n    return err\n  }\n  return validate(obj)\n}\n\nfunc validate(obj any) error {\n  if binding.Validator == nil {\n    return nil\n  }\n  return binding.Validator.ValidateStruct(obj)\n}\n\n// Now we can do this!!!\n// FormA is an external type that we can't modify it's tag\ntype FormA struct {\n  FieldA string `url:\"field_a\"`\n}\n\nfunc ListHandler(s *Service) func(ctx *gin.Context) {\n  return func(ctx *gin.Context) {\n    var urlBinding = customerBinding{}\n    var opt FormA\n    err := ctx.MustBindWith(&opt, urlBinding)\n    if err != nil {\n      ...\n    }\n    ...\n  }\n}\n```\n\n## Response Rendering\n\n> Render responses in various formats including JSON, XML, HTML, and more.\n\n### XML, JSON, YAML, TOML and ProtoBuf rendering\n\n```go\nfunc main() {\n  r := gin.Default()\n\n  // gin.H is a shortcut for map[string]any\n  r.GET(\"/someJSON\", func(c *gin.Context) {\n    c.JSON(http.StatusOK, gin.H{\"message\": \"hey\", \"status\": http.StatusOK})\n  })\n\n  r.GET(\"/moreJSON\", func(c *gin.Context) {\n    // You can also use a struct\n    var msg struct {\n      Name    string `json:\"user\"`\n      Message string\n      Number  int\n    }\n    msg.Name = \"Lena\"\n    msg.Message = \"hey\"\n    msg.Number = 123\n    // Note that msg.Name becomes \"user\" in the JSON\n    // Will output  :   {\"user\": \"Lena\", \"Message\": \"hey\", \"Number\": 123}\n    c.JSON(http.StatusOK, msg)\n  })\n\n  r.GET(\"/someXML\", func(c *gin.Context) {\n    c.XML(http.StatusOK, gin.H{\"message\": \"hey\", \"status\": http.StatusOK})\n  })\n\n  r.GET(\"/someYAML\", func(c *gin.Context) {\n    c.YAML(http.StatusOK, gin.H{\"message\": \"hey\", \"status\": http.StatusOK})\n  })\n\n  r.GET(\"/someTOML\", func(c *gin.Context) {\n    c.TOML(http.StatusOK, gin.H{\"message\": \"hey\", \"status\": http.StatusOK})\n  })\n\n  r.GET(\"/someProtoBuf\", func(c *gin.Context) {\n    reps := []int64{int64(1), int64(2)}\n    label := \"test\"\n    // The specific definition of protobuf is written in the testdata/protoexample file.\n    data := &protoexample.Test{\n      Label: &label,\n      Reps:  reps,\n    }\n    // Note that data becomes binary data in the response\n    // Will output protoexample.Test protobuf serialized data\n    c.ProtoBuf(http.StatusOK, data)\n  })\n\n  // Listen and serve on 0.0.0.0:8080\n  r.Run(\":8080\")\n}\n```\n\n#### SecureJSON\n\nUsing SecureJSON to prevent json hijacking. Default prepends `\"while(1),\"` to response body if the given struct is array values.\n\n```go\nfunc main() {\n  r := gin.Default()\n\n  // You can also use your own secure json prefix\n  // r.SecureJsonPrefix(\")]}',\\n\")\n\n  r.GET(\"/someJSON\", func(c *gin.Context) {\n    names := []string{\"lena\", \"austin\", \"foo\"}\n\n    // Will output  :   while(1);[\"lena\",\"austin\",\"foo\"]\n    c.SecureJSON(http.StatusOK, names)\n  })\n\n  // Listen and serve on 0.0.0.0:8080\n  r.Run(\":8080\")\n}\n```\n\n#### JSONP\n\nUsing JSONP to request data from a server in a different domain. Add callback to response body if the query parameter callback exists.\n\n```go\nfunc main() {\n  r := gin.Default()\n\n  r.GET(\"/JSONP\", func(c *gin.Context) {\n    data := gin.H{\n      \"foo\": \"bar\",\n    }\n\n    //callback is x\n    // Will output  :   x({\\\"foo\\\":\\\"bar\\\"})\n    c.JSONP(http.StatusOK, data)\n  })\n\n  // Listen and serve on 0.0.0.0:8080\n  r.Run(\":8080\")\n\n        // client\n        // curl http://127.0.0.1:8080/JSONP?callback=x\n}\n```\n\n#### AsciiJSON\n\nUsing AsciiJSON to Generates ASCII-only JSON with escaped non-ASCII characters.\n\n```go\nfunc main() {\n  r := gin.Default()\n\n  r.GET(\"/someJSON\", func(c *gin.Context) {\n    data := gin.H{\n      \"lang\": \"GO语言\",\n      \"tag\":  \"<br>\",\n    }\n\n    // will output : {\"lang\":\"GO\\u8bed\\u8a00\",\"tag\":\"\\u003cbr\\u003e\"}\n    c.AsciiJSON(http.StatusOK, data)\n  })\n\n  // Listen and serve on 0.0.0.0:8080\n  r.Run(\":8080\")\n}\n```\n\n#### PureJSON\n\nNormally, JSON replaces special HTML characters with their unicode entities, e.g. `<` becomes `\\u003c`. If you want to encode such characters literally, you can use PureJSON instead.\nThis feature is unavailable in Go 1.6 and lower.\n\n```go\nfunc main() {\n  r := gin.Default()\n\n  // Serves unicode entities\n  r.GET(\"/json\", func(c *gin.Context) {\n    c.JSON(http.StatusOK, gin.H{\n      \"html\": \"<b>Hello, world!</b>\",\n    })\n  })\n\n  // Serves literal characters\n  r.GET(\"/purejson\", func(c *gin.Context) {\n    c.PureJSON(http.StatusOK, gin.H{\n      \"html\": \"<b>Hello, world!</b>\",\n    })\n  })\n\n  // listen and serve on 0.0.0.0:8080\n  r.Run(\":8080\")\n}\n```\n\n### Serving static files\n\n```go\nfunc main() {\n  router := gin.Default()\n  router.Static(\"/assets\", \"./assets\")\n  router.StaticFS(\"/more_static\", http.Dir(\"my_file_system\"))\n  router.StaticFile(\"/favicon.ico\", \"./resources/favicon.ico\")\n  router.StaticFileFS(\"/more_favicon.ico\", \"more_favicon.ico\", http.Dir(\"my_file_system\"))\n\n  // Listen and serve on 0.0.0.0:8080\n  router.Run(\":8080\")\n}\n```\n\n### Serving data from file\n\n```go\nfunc main() {\n  router := gin.Default()\n\n  router.GET(\"/local/file\", func(c *gin.Context) {\n    c.File(\"local/file.go\")\n  })\n\n  var fs http.FileSystem = // ...\n  router.GET(\"/fs/file\", func(c *gin.Context) {\n    c.FileFromFS(\"fs/file.go\", fs)\n  })\n}\n\n```\n\n### Serving data from reader\n\n```go\nfunc main() {\n  router := gin.Default()\n  router.GET(\"/someDataFromReader\", func(c *gin.Context) {\n    response, err := http.Get(\"https://raw.githubusercontent.com/gin-gonic/logo/master/color.png\")\n    if err != nil || response.StatusCode != http.StatusOK {\n      c.Status(http.StatusServiceUnavailable)\n      return\n    }\n\n    reader := response.Body\n     defer reader.Close()\n    contentLength := response.ContentLength\n    contentType := response.Header.Get(\"Content-Type\")\n\n    extraHeaders := map[string]string{\n      \"Content-Disposition\": `attachment; filename=\"gopher.png\"`,\n    }\n\n    c.DataFromReader(http.StatusOK, contentLength, contentType, reader, extraHeaders)\n  })\n  router.Run(\":8080\")\n}\n```\n\n### HTML rendering\n\nUsing LoadHTMLGlob() or LoadHTMLFiles() or LoadHTMLFS()\n\n```go\n//go:embed templates/*\nvar templates embed.FS\n\nfunc main() {\n  router := gin.Default()\n  router.LoadHTMLGlob(\"templates/*\")\n  //router.LoadHTMLFiles(\"templates/template1.html\", \"templates/template2.html\")\n  //router.LoadHTMLFS(http.Dir(\"templates\"), \"template1.html\", \"template2.html\")\n  //or\n  //router.LoadHTMLFS(http.FS(templates), \"templates/template1.html\", \"templates/template2.html\")\n  router.GET(\"/index\", func(c *gin.Context) {\n    c.HTML(http.StatusOK, \"index.tmpl\", gin.H{\n      \"title\": \"Main website\",\n    })\n  })\n  router.Run(\":8080\")\n}\n```\n\ntemplates/index.tmpl\n\n```html\n<html>\n  <h1>{{ .title }}</h1>\n</html>\n```\n\nUsing templates with same name in different directories\n\n```go\nfunc main() {\n  router := gin.Default()\n  router.LoadHTMLGlob(\"templates/**/*\")\n  router.GET(\"/posts/index\", func(c *gin.Context) {\n    c.HTML(http.StatusOK, \"posts/index.tmpl\", gin.H{\n      \"title\": \"Posts\",\n    })\n  })\n  router.GET(\"/users/index\", func(c *gin.Context) {\n    c.HTML(http.StatusOK, \"users/index.tmpl\", gin.H{\n      \"title\": \"Users\",\n    })\n  })\n  router.Run(\":8080\")\n}\n```\n\ntemplates/posts/index.tmpl\n\n```html\n{{ define \"posts/index.tmpl\" }}\n<html>\n  <h1>{{ .title }}</h1>\n  <p>Using posts/index.tmpl</p>\n</html>\n{{ end }}\n```\n\ntemplates/users/index.tmpl\n\n```html\n{{ define \"users/index.tmpl\" }}\n<html>\n  <h1>{{ .title }}</h1>\n  <p>Using users/index.tmpl</p>\n</html>\n{{ end }}\n```\n\n#### Custom Template renderer\n\nYou can also use your own html template render\n\n```go\nimport \"html/template\"\n\nfunc main() {\n  router := gin.Default()\n  html := template.Must(template.ParseFiles(\"file1\", \"file2\"))\n  router.SetHTMLTemplate(html)\n  router.Run(\":8080\")\n}\n```\n\n#### Custom Delimiters\n\nYou may use custom delims\n\n```go\n  r := gin.Default()\n  r.Delims(\"{[{\", \"}]}\")\n  r.LoadHTMLGlob(\"/path/to/templates\")\n```\n\n#### Custom Template Funcs\n\nSee the detailed [example code](https://github.com/gin-gonic/examples/tree/master/template).\n\nmain.go\n\n```go\nimport (\n  \"fmt\"\n  \"html/template\"\n  \"net/http\"\n  \"time\"\n\n  \"github.com/gin-gonic/gin\"\n)\n\nfunc formatAsDate(t time.Time) string {\n  year, month, day := t.Date()\n  return fmt.Sprintf(\"%d/%02d/%02d\", year, month, day)\n}\n\nfunc main() {\n  router := gin.Default()\n  router.Delims(\"{[{\", \"}]}\")\n  router.SetFuncMap(template.FuncMap{\n      \"formatAsDate\": formatAsDate,\n  })\n  router.LoadHTMLFiles(\"./testdata/template/raw.tmpl\")\n\n  router.GET(\"/raw\", func(c *gin.Context) {\n      c.HTML(http.StatusOK, \"raw.tmpl\", gin.H{\n          \"now\": time.Date(2017, 07, 01, 0, 0, 0, 0, time.UTC),\n      })\n  })\n\n  router.Run(\":8080\")\n}\n\n```\n\nraw.tmpl\n\n```html\nDate: {[{.now | formatAsDate}]}\n```\n\nResult:\n\n```sh\nDate: 2017/07/01\n```\n\n### Multitemplate\n\nGin allows only one html.Template by default. Check [a multitemplate render](https://github.com/gin-contrib/multitemplate) for using features like go 1.6 `block template`.\n\n### Build a single binary with templates\n\nYou can build a server into a single binary containing templates by using the [embed](https://pkg.go.dev/embed) package.\n\n```go\npackage main\n\nimport (\n  \"embed\"\n  \"html/template\"\n  \"net/http\"\n\n  \"github.com/gin-gonic/gin\"\n)\n\n//go:embed assets/* templates/*\nvar f embed.FS\n\nfunc main() {\n  router := gin.Default()\n  templ := template.Must(template.New(\"\").ParseFS(f, \"templates/*.tmpl\", \"templates/foo/*.tmpl\"))\n  router.SetHTMLTemplate(templ)\n\n  // example: /public/assets/images/example.png\n  router.StaticFS(\"/public\", http.FS(f))\n\n  router.GET(\"/\", func(c *gin.Context) {\n    c.HTML(http.StatusOK, \"index.tmpl\", gin.H{\n      \"title\": \"Main website\",\n    })\n  })\n\n  router.GET(\"/foo\", func(c *gin.Context) {\n    c.HTML(http.StatusOK, \"bar.tmpl\", gin.H{\n      \"title\": \"Foo website\",\n    })\n  })\n\n  router.GET(\"favicon.ico\", func(c *gin.Context) {\n    file, _ := f.ReadFile(\"assets/favicon.ico\")\n    c.Data(\n      http.StatusOK,\n      \"image/x-icon\",\n      file,\n    )\n  })\n\n  router.Run(\":8080\")\n}\n```\n\nSee a complete example in the `https://github.com/gin-gonic/examples/tree/master/assets-in-binary/example02` directory.\n\n## Server Configuration\n\n> Configure HTTP servers, TLS, proxies, and runtime settings.\n\n### Custom HTTP configuration\n\nUse `http.ListenAndServe()` directly, like this:\n\n```go\nfunc main() {\n  router := gin.Default()\n  http.ListenAndServe(\":8080\", router)\n}\n```\n\nor\n\n```go\nfunc main() {\n  router := gin.Default()\n\n  s := &http.Server{\n    Addr:           \":8080\",\n    Handler:        router,\n    ReadTimeout:    10 * time.Second,\n    WriteTimeout:   10 * time.Second,\n    MaxHeaderBytes: 1 << 20,\n  }\n  s.ListenAndServe()\n}\n```\n\n### Custom json codec at runtime\n\nGin support custom json serialization and deserialization logic without using compile tags.\n\n1. Define a custom struct implements the `json.Core` interface.\n\n2. Before your engine starts, assign values to `json.API` using the custom struct.\n\n```go\npackage main\n\nimport (\n  \"io\"\n\n  \"github.com/gin-gonic/gin\"\n  \"github.com/gin-gonic/gin/codec/json\"\n  jsoniter \"github.com/json-iterator/go\"\n)\n\nvar customConfig = jsoniter.Config{\n  EscapeHTML:             true,\n  SortMapKeys:            true,\n  ValidateJsonRawMessage: true,\n}.Froze()\n\n// implement api.JsonApi\ntype customJsonApi struct {\n}\n\nfunc (j customJsonApi) Marshal(v any) ([]byte, error) {\n  return customConfig.Marshal(v)\n}\n\nfunc (j customJsonApi) Unmarshal(data []byte, v any) error {\n  return customConfig.Unmarshal(data, v)\n}\n\nfunc (j customJsonApi) MarshalIndent(v any, prefix, indent string) ([]byte, error) {\n  return customConfig.MarshalIndent(v, prefix, indent)\n}\n\nfunc (j customJsonApi) NewEncoder(writer io.Writer) json.Encoder {\n  return customConfig.NewEncoder(writer)\n}\n\nfunc (j customJsonApi) NewDecoder(reader io.Reader) json.Decoder {\n  return customConfig.NewDecoder(reader)\n}\n\nfunc main() {\n  //Replace the default json api\n  json.API = customJsonApi{}\n\n  //Start your gin engine\n  router := gin.Default()\n  router.Run(\":8080\")\n}\n```\n\n### Support Let's Encrypt\n\nexample for 1-line LetsEncrypt HTTPS servers.\n\n```go\npackage main\n\nimport (\n  \"log\"\n  \"net/http\"\n\n  \"github.com/gin-gonic/autotls\"\n  \"github.com/gin-gonic/gin\"\n)\n\nfunc main() {\n  r := gin.Default()\n\n  // Ping handler\n  r.GET(\"/ping\", func(c *gin.Context) {\n    c.String(http.StatusOK, \"pong\")\n  })\n\n  log.Fatal(autotls.Run(r, \"example1.com\", \"example2.com\"))\n}\n```\n\nexample for custom autocert manager.\n\n```go\npackage main\n\nimport (\n  \"log\"\n  \"net/http\"\n\n  \"github.com/gin-gonic/autotls\"\n  \"github.com/gin-gonic/gin\"\n  \"golang.org/x/crypto/acme/autocert\"\n)\n\nfunc main() {\n  r := gin.Default()\n\n  // Ping handler\n  r.GET(\"/ping\", func(c *gin.Context) {\n    c.String(http.StatusOK, \"pong\")\n  })\n\n  m := autocert.Manager{\n    Prompt:     autocert.AcceptTOS,\n    HostPolicy: autocert.HostWhitelist(\"example1.com\", \"example2.com\"),\n    Cache:      autocert.DirCache(\"/var/www/.cache\"),\n  }\n\n  log.Fatal(autotls.RunWithManager(r, &m))\n}\n```\n\n### Run multiple service using Gin\n\nSee the [question](https://github.com/gin-gonic/gin/issues/346) and try the following example:\n\n```go\npackage main\n\nimport (\n  \"log\"\n  \"net/http\"\n  \"time\"\n\n  \"github.com/gin-gonic/gin\"\n  \"golang.org/x/sync/errgroup\"\n)\n\nvar (\n  g errgroup.Group\n)\n\nfunc router01() http.Handler {\n  e := gin.New()\n  e.Use(gin.Recovery())\n  e.GET(\"/\", func(c *gin.Context) {\n    c.JSON(\n      http.StatusOK,\n      gin.H{\n        \"code\":  http.StatusOK,\n        \"error\": \"Welcome server 01\",\n      },\n    )\n  })\n\n  return e\n}\n\nfunc router02() http.Handler {\n  e := gin.New()\n  e.Use(gin.Recovery())\n  e.GET(\"/\", func(c *gin.Context) {\n    c.JSON(\n      http.StatusOK,\n      gin.H{\n        \"code\":  http.StatusOK,\n        \"error\": \"Welcome server 02\",\n      },\n    )\n  })\n\n  return e\n}\n\nfunc main() {\n  server01 := &http.Server{\n    Addr:         \":8080\",\n    Handler:      router01(),\n    ReadTimeout:  5 * time.Second,\n    WriteTimeout: 10 * time.Second,\n  }\n\n  server02 := &http.Server{\n    Addr:         \":8081\",\n    Handler:      router02(),\n    ReadTimeout:  5 * time.Second,\n    WriteTimeout: 10 * time.Second,\n  }\n\n  g.Go(func() error {\n    err := server01.ListenAndServe()\n    if err != nil && err != http.ErrServerClosed {\n      log.Fatal(err)\n    }\n    return err\n  })\n\n  g.Go(func() error {\n    err := server02.ListenAndServe()\n    if err != nil && err != http.ErrServerClosed {\n      log.Fatal(err)\n    }\n    return err\n  })\n\n  if err := g.Wait(); err != nil {\n    log.Fatal(err)\n  }\n}\n```\n\n### Graceful shutdown or restart\n\nThere are a few approaches you can use to perform a graceful shutdown or restart. You can make use of third-party packages specifically built for that, or you can manually do the same with the functions and methods from the built-in packages.\n\n#### Third-party packages\n\nWe can use [fvbock/endless](https://github.com/fvbock/endless) to replace the default `ListenAndServe`. Refer to issue [#296](https://github.com/gin-gonic/gin/issues/296) for more details.\n\n```go\nrouter := gin.Default()\nrouter.GET(\"/\", handler)\n// [...]\nendless.ListenAndServe(\":4242\", router)\n```\n\nAlternatives:\n\n- [grace](https://github.com/facebookgo/grace): Graceful restart & zero downtime deploy for Go servers.\n- [graceful](https://github.com/tylerb/graceful): Graceful is a Go package enabling graceful shutdown of an http.Handler server.\n- [manners](https://github.com/braintree/manners): A polite Go HTTP server that shuts down gracefully.\n\n#### Manually\n\nIn case you are using Go 1.8 or a later version, you may not need to use those libraries. Consider using `http.Server`'s built-in [Shutdown()](https://pkg.go.dev/net/http#Server.Shutdown) method for graceful shutdowns. The example below describes its usage, and we've got more examples using gin [here](https://github.com/gin-gonic/examples/tree/master/graceful-shutdown).\n\n```go\n// +build go1.8\n\npackage main\n\nimport (\n  \"context\"\n  \"log\"\n  \"net/http\"\n  \"os\"\n  \"os/signal\"\n  \"syscall\"\n  \"time\"\n\n  \"github.com/gin-gonic/gin\"\n)\n\nfunc main() {\n  router := gin.Default()\n  router.GET(\"/\", func(c *gin.Context) {\n    time.Sleep(5 * time.Second)\n    c.String(http.StatusOK, \"Welcome Gin Server\")\n  })\n\n  srv := &http.Server{\n    Addr:    \":8080\",\n    Handler: router,\n  }\n\n  // Initializing the server in a goroutine so that\n  // it won't block the graceful shutdown handling below\n  go func() {\n    if err := srv.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) {\n      log.Printf(\"listen: %s\\n\", err)\n    }\n  }()\n\n  // Wait for interrupt signal to gracefully shutdown the server with\n  // a timeout of 5 seconds.\n  quit := make(chan os.Signal)\n  // kill (no param) default send syscall.SIGTERM\n  // kill -2 is syscall.SIGINT\n  // kill -9 is syscall.SIGKILL but can't be caught, so don't need to add it\n  signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)\n  <-quit\n  log.Println(\"Shutting down server...\")\n\n  // The context is used to inform the server it has 5 seconds to finish\n  // the request it is currently handling\n  ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)\n  defer cancel()\n\n  if err := srv.Shutdown(ctx); err != nil {\n    log.Fatal(\"Server forced to shutdown:\", err)\n  }\n\n  log.Println(\"Server exiting\")\n}\n```\n\n### http2 server push\n\nhttp.Pusher is supported only **go1.8+**. See the [golang blog](https://go.dev/blog/h2push) for detail information.\n\n```go\npackage main\n\nimport (\n  \"html/template\"\n  \"log\"\n  \"net/http\"\n\n  \"github.com/gin-gonic/gin\"\n)\n\nvar html = template.Must(template.New(\"https\").Parse(`\n<html>\n<head>\n  <title>Https Test</title>\n  <script src=\"/assets/app.js\"></script>\n</head>\n<body>\n  <h1 style=\"color:red;\">Welcome, Ginner!</h1>\n</body>\n</html>\n`))\n\nfunc main() {\n  r := gin.Default()\n  r.Static(\"/assets\", \"./assets\")\n  r.SetHTMLTemplate(html)\n\n  r.GET(\"/\", func(c *gin.Context) {\n    if pusher := c.Writer.Pusher(); pusher != nil {\n      // use pusher.Push() to do server push\n      if err := pusher.Push(\"/assets/app.js\", nil); err != nil {\n        log.Printf(\"Failed to push: %v\", err)\n      }\n    }\n    c.HTML(http.StatusOK, \"https\", gin.H{\n      \"status\": \"success\",\n    })\n  })\n\n  // Listen and Server in https://127.0.0.1:8080\n  r.RunTLS(\":8080\", \"./testdata/server.pem\", \"./testdata/server.key\")\n}\n```\n\n### Set and get a cookie\n\n```go\nimport (\n  \"fmt\"\n\n  \"github.com/gin-gonic/gin\"\n)\n\nfunc main() {\n  router := gin.Default()\n\n  router.GET(\"/cookie\", func(c *gin.Context) {\n    cookie, err := c.Cookie(\"gin_cookie\")\n\n    if err != nil {\n      cookie = \"NotSet\"\n      // Using http.Cookie struct for more control\n      c.SetCookieData(&http.Cookie{\n        Name:       \"gin_cookie\",\n        Value:      \"test\",\n        Path:       \"/\",\n        Domain:     \"localhost\",\n        MaxAge:     3600,\n        Secure:     false,\n        HttpOnly:   true,\n        // Additional fields available in http.Cookie\n        Expires:    time.Now().Add(24 * time.Hour),\n        // Partitioned: true, // Available in newer Go versions\n      })\n    }\n\n    fmt.Printf(\"Cookie value: %s \\n\", cookie)\n  })\n\n  router.Run()\n}\n```\n\nYou can also use the `SetCookieData` method, which accepts a `*http.Cookie` directly for more flexibility:\n\n```go\nimport (\n  \"fmt\"\n  \"net/http\"\n  \"time\"\n\n  \"github.com/gin-gonic/gin\"\n)\n\nfunc main() {\n  router := gin.Default()\n\n  router.GET(\"/cookie\", func(c *gin.Context) {\n      cookie, err := c.Cookie(\"gin_cookie\")\n\n      if err != nil {\n          cookie = \"NotSet\"\n          // Using http.Cookie struct for more control\n          c.SetCookieData(&http.Cookie{\n              Name:       \"gin_cookie\",\n              Value:      \"test\",\n              Path:       \"/\",\n              Domain:     \"localhost\",\n              MaxAge:     3600,\n              Secure:     false,\n              HttpOnly:   true,\n              // Additional fields available in http.Cookie\n              Expires:    time.Now().Add(24 * time.Hour),\n              // Partitioned: true, // Available in newer Go versions\n          })\n      }\n\n      fmt.Printf(\"Cookie value: %s \\n\", cookie)\n  })\n\n  router.Run()\n}\n```\n\n### Don't trust all proxies\n\nGin lets you specify which headers to hold the real client IP (if any),\nas well as specifying which proxies (or direct clients) you trust to\nspecify one of these headers.\n\nUse function `SetTrustedProxies()` on your `gin.Engine` to specify network addresses\nor network CIDRs from where clients which their request headers related to client\nIP can be trusted. They can be IPv4 addresses, IPv4 CIDRs, IPv6 addresses or\nIPv6 CIDRs.\n\n**Attention:** Gin trusts all proxies by default if you don't specify a trusted\nproxy using the function above, **this is NOT safe**. At the same time, if you don't\nuse any proxy, you can disable this feature by using `Engine.SetTrustedProxies(nil)`,\nthen `Context.ClientIP()` will return the remote address directly to avoid some\nunnecessary computation.\n\n```go\nimport (\n  \"fmt\"\n\n  \"github.com/gin-gonic/gin\"\n)\n\nfunc main() {\n  router := gin.Default()\n  router.SetTrustedProxies([]string{\"192.168.1.2\"})\n\n  router.GET(\"/\", func(c *gin.Context) {\n    // If the client is 192.168.1.2, use the X-Forwarded-For\n    // header to deduce the original client IP from the trust-\n    // worthy parts of that header.\n    // Otherwise, simply return the direct client IP\n    fmt.Printf(\"ClientIP: %s\\n\", c.ClientIP())\n  })\n  router.Run()\n}\n```\n\n**Notice:** If you are using a CDN service, you can set the `Engine.TrustedPlatform`\nto skip TrustedProxies check, it has a higher priority than TrustedProxies.\nLook at the example below:\n\n```go\nimport (\n  \"fmt\"\n\n  \"github.com/gin-gonic/gin\"\n)\n\nfunc main() {\n  router := gin.Default()\n  // Use predefined header gin.PlatformXXX\n  // Google App Engine\n  router.TrustedPlatform = gin.PlatformGoogleAppEngine\n  // Cloudflare\n  router.TrustedPlatform = gin.PlatformCloudflare\n  // Fly.io\n  router.TrustedPlatform = gin.PlatformFlyIO\n  // Or, you can set your own trusted request header. But be sure your CDN\n  // prevents users from passing this header! For example, if your CDN puts\n  // the client IP in X-CDN-Client-IP:\n  router.TrustedPlatform = \"X-CDN-Client-IP\"\n\n  router.GET(\"/\", func(c *gin.Context) {\n    // If you set TrustedPlatform, ClientIP() will resolve the\n    // corresponding header and return IP directly\n    fmt.Printf(\"ClientIP: %s\\n\", c.ClientIP())\n  })\n  router.Run()\n}\n```\n\n## Testing\n\nThe `net/http/httptest` package is preferable way for HTTP testing.\n\n```go\npackage main\n\nimport (\n  \"net/http\"\n\n  \"github.com/gin-gonic/gin\"\n)\n\nfunc setupRouter() *gin.Engine {\n  r := gin.Default()\n  r.GET(\"/ping\", func(c *gin.Context) {\n    c.String(http.StatusOK, \"pong\")\n  })\n  return r\n}\n\nfunc main() {\n  r := setupRouter()\n  r.Run(\":8080\")\n}\n```\n\nTest for code example above:\n\n```go\npackage main\n\nimport (\n  \"net/http\"\n  \"net/http/httptest\"\n  \"testing\"\n\n  \"github.com/stretchr/testify/assert\"\n)\n\nfunc TestPingRoute(t *testing.T) {\n  router := setupRouter()\n\n  w := httptest.NewRecorder()\n  req, _ := http.NewRequest(http.MethodGet, \"/ping\", nil)\n  router.ServeHTTP(w, req)\n\n  assert.Equal(t, http.StatusOK, w.Code)\n  assert.Equal(t, \"pong\", w.Body.String())\n}\n```\n"
  },
  {
    "path": "errors.go",
    "content": "// Copyright 2014 Manu Martinez-Almeida. All rights reserved.\n// Use of this source code is governed by a MIT style\n// license that can be found in the LICENSE file.\n\npackage gin\n\nimport (\n\t\"fmt\"\n\t\"reflect\"\n\t\"strings\"\n\n\t\"github.com/gin-gonic/gin/codec/json\"\n)\n\n// ErrorType is an unsigned 64-bit error code as defined in the gin spec.\ntype ErrorType uint64\n\nconst (\n\t// ErrorTypeBind is used when Context.Bind() fails.\n\tErrorTypeBind ErrorType = 1 << 63\n\t// ErrorTypeRender is used when Context.Render() fails.\n\tErrorTypeRender ErrorType = 1 << 62\n\t// ErrorTypePrivate indicates a private error.\n\tErrorTypePrivate ErrorType = 1 << 0\n\t// ErrorTypePublic indicates a public error.\n\tErrorTypePublic ErrorType = 1 << 1\n\t// ErrorTypeAny indicates any other error.\n\tErrorTypeAny ErrorType = 1<<64 - 1\n)\n\n// Error represents a error's specification.\ntype Error struct {\n\tErr  error\n\tType ErrorType\n\tMeta any\n}\n\ntype errorMsgs []*Error\n\nvar _ error = (*Error)(nil)\n\n// SetType sets the error's type.\nfunc (msg *Error) SetType(flags ErrorType) *Error {\n\tmsg.Type = flags\n\treturn msg\n}\n\n// SetMeta sets the error's meta data.\nfunc (msg *Error) SetMeta(data any) *Error {\n\tmsg.Meta = data\n\treturn msg\n}\n\n// JSON creates a properly formatted JSON\nfunc (msg *Error) JSON() any {\n\tjsonData := H{}\n\tif msg.Meta != nil {\n\t\tvalue := reflect.ValueOf(msg.Meta)\n\t\tswitch value.Kind() {\n\t\tcase reflect.Struct:\n\t\t\treturn msg.Meta\n\t\tcase reflect.Map:\n\t\t\tfor _, key := range value.MapKeys() {\n\t\t\t\tjsonData[key.String()] = value.MapIndex(key).Interface()\n\t\t\t}\n\t\tdefault:\n\t\t\tjsonData[\"meta\"] = msg.Meta\n\t\t}\n\t}\n\tif _, ok := jsonData[\"error\"]; !ok {\n\t\tjsonData[\"error\"] = msg.Error()\n\t}\n\treturn jsonData\n}\n\n// MarshalJSON implements the json.Marshaller interface.\nfunc (msg *Error) MarshalJSON() ([]byte, error) {\n\treturn json.API.Marshal(msg.JSON())\n}\n\n// Error implements the error interface.\nfunc (msg Error) Error() string {\n\treturn msg.Err.Error()\n}\n\n// IsType judges one error.\nfunc (msg *Error) IsType(flags ErrorType) bool {\n\treturn (msg.Type & flags) > 0\n}\n\n// Unwrap returns the wrapped error, to allow interoperability with errors.Is(), errors.As() and errors.Unwrap()\nfunc (msg Error) Unwrap() error {\n\treturn msg.Err\n}\n\n// ByType returns a readonly copy filtered the byte.\n// ie ByType(gin.ErrorTypePublic) returns a slice of errors with type=ErrorTypePublic.\nfunc (a errorMsgs) ByType(typ ErrorType) errorMsgs {\n\tif len(a) == 0 {\n\t\treturn nil\n\t}\n\tif typ == ErrorTypeAny {\n\t\treturn a\n\t}\n\tvar result errorMsgs\n\tfor _, msg := range a {\n\t\tif msg.IsType(typ) {\n\t\t\tresult = append(result, msg)\n\t\t}\n\t}\n\treturn result\n}\n\n// Last returns the last error in the slice. It returns nil if the array is empty.\n// Shortcut for errors[len(errors)-1].\nfunc (a errorMsgs) Last() *Error {\n\tif length := len(a); length > 0 {\n\t\treturn a[length-1]\n\t}\n\treturn nil\n}\n\n// Errors returns an array with all the error messages.\n// Example:\n//\n//\tc.Error(errors.New(\"first\"))\n//\tc.Error(errors.New(\"second\"))\n//\tc.Error(errors.New(\"third\"))\n//\tc.Errors.Errors() // == []string{\"first\", \"second\", \"third\"}\nfunc (a errorMsgs) Errors() []string {\n\tif len(a) == 0 {\n\t\treturn nil\n\t}\n\terrorStrings := make([]string, len(a))\n\tfor i, err := range a {\n\t\terrorStrings[i] = err.Error()\n\t}\n\treturn errorStrings\n}\n\nfunc (a errorMsgs) JSON() any {\n\tswitch length := len(a); length {\n\tcase 0:\n\t\treturn nil\n\tcase 1:\n\t\treturn a.Last().JSON()\n\tdefault:\n\t\tjsonData := make([]any, length)\n\t\tfor i, err := range a {\n\t\t\tjsonData[i] = err.JSON()\n\t\t}\n\t\treturn jsonData\n\t}\n}\n\n// MarshalJSON implements the json.Marshaller interface.\nfunc (a errorMsgs) MarshalJSON() ([]byte, error) {\n\treturn json.API.Marshal(a.JSON())\n}\n\nfunc (a errorMsgs) String() string {\n\tif len(a) == 0 {\n\t\treturn \"\"\n\t}\n\tvar buffer strings.Builder\n\tfor i, msg := range a {\n\t\tfmt.Fprintf(&buffer, \"Error #%02d: %s\\n\", i+1, msg.Err)\n\t\tif msg.Meta != nil {\n\t\t\tfmt.Fprintf(&buffer, \"     Meta: %v\\n\", msg.Meta)\n\t\t}\n\t}\n\treturn buffer.String()\n}\n"
  },
  {
    "path": "errors_test.go",
    "content": "// Copyright 2014 Manu Martinez-Almeida. All rights reserved.\n// Use of this source code is governed by a MIT style\n// license that can be found in the LICENSE file.\n\npackage gin\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/gin-gonic/gin/codec/json\"\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestError(t *testing.T) {\n\tbaseError := errors.New(\"test error\")\n\terr := &Error{\n\t\tErr:  baseError,\n\t\tType: ErrorTypePrivate,\n\t}\n\tassert.Equal(t, err.Error(), baseError.Error())\n\tassert.Equal(t, H{\"error\": baseError.Error()}, err.JSON())\n\n\tassert.Equal(t, err.SetType(ErrorTypePublic), err)\n\tassert.Equal(t, ErrorTypePublic, err.Type)\n\n\tassert.Equal(t, err.SetMeta(\"some data\"), err)\n\tassert.Equal(t, \"some data\", err.Meta)\n\tassert.Equal(t, H{\n\t\t\"error\": baseError.Error(),\n\t\t\"meta\":  \"some data\",\n\t}, err.JSON())\n\n\tjsonBytes, _ := json.API.Marshal(err)\n\tassert.JSONEq(t, \"{\\\"error\\\":\\\"test error\\\",\\\"meta\\\":\\\"some data\\\"}\", string(jsonBytes))\n\n\terr.SetMeta(H{ //nolint: errcheck\n\t\t\"status\": \"200\",\n\t\t\"data\":   \"some data\",\n\t})\n\tassert.Equal(t, H{\n\t\t\"error\":  baseError.Error(),\n\t\t\"status\": \"200\",\n\t\t\"data\":   \"some data\",\n\t}, err.JSON())\n\n\terr.SetMeta(H{ //nolint: errcheck\n\t\t\"error\":  \"custom error\",\n\t\t\"status\": \"200\",\n\t\t\"data\":   \"some data\",\n\t})\n\tassert.Equal(t, H{\n\t\t\"error\":  \"custom error\",\n\t\t\"status\": \"200\",\n\t\t\"data\":   \"some data\",\n\t}, err.JSON())\n\n\ttype customError struct {\n\t\tstatus string\n\t\tdata   string\n\t}\n\terr.SetMeta(customError{status: \"200\", data: \"other data\"}) //nolint: errcheck\n\tassert.Equal(t, customError{status: \"200\", data: \"other data\"}, err.JSON())\n}\n\nfunc TestErrorSlice(t *testing.T) {\n\terrs := errorMsgs{\n\t\t{Err: errors.New(\"first\"), Type: ErrorTypePrivate},\n\t\t{Err: errors.New(\"second\"), Type: ErrorTypePrivate, Meta: \"some data\"},\n\t\t{Err: errors.New(\"third\"), Type: ErrorTypePublic, Meta: H{\"status\": \"400\"}},\n\t}\n\n\tassert.Equal(t, errs, errs.ByType(ErrorTypeAny))\n\tassert.Equal(t, \"third\", errs.Last().Error())\n\tassert.Equal(t, []string{\"first\", \"second\", \"third\"}, errs.Errors())\n\tassert.Equal(t, []string{\"third\"}, errs.ByType(ErrorTypePublic).Errors())\n\tassert.Equal(t, []string{\"first\", \"second\"}, errs.ByType(ErrorTypePrivate).Errors())\n\tassert.Equal(t, []string{\"first\", \"second\", \"third\"}, errs.ByType(ErrorTypePublic|ErrorTypePrivate).Errors())\n\tassert.Empty(t, errs.ByType(ErrorTypeBind))\n\tassert.Empty(t, errs.ByType(ErrorTypeBind).String())\n\n\tassert.Equal(t, `Error #01: first\nError #02: second\n     Meta: some data\nError #03: third\n     Meta: map[status:400]\n`, errs.String())\n\tassert.Equal(t, []any{\n\t\tH{\"error\": \"first\"},\n\t\tH{\"error\": \"second\", \"meta\": \"some data\"},\n\t\tH{\"error\": \"third\", \"status\": \"400\"},\n\t}, errs.JSON())\n\tjsonBytes, _ := json.API.Marshal(errs)\n\tassert.JSONEq(t, \"[{\\\"error\\\":\\\"first\\\"},{\\\"error\\\":\\\"second\\\",\\\"meta\\\":\\\"some data\\\"},{\\\"error\\\":\\\"third\\\",\\\"status\\\":\\\"400\\\"}]\", string(jsonBytes))\n\terrs = errorMsgs{\n\t\t{Err: errors.New(\"first\"), Type: ErrorTypePrivate},\n\t}\n\tassert.Equal(t, H{\"error\": \"first\"}, errs.JSON())\n\tjsonBytes, _ = json.API.Marshal(errs)\n\tassert.JSONEq(t, \"{\\\"error\\\":\\\"first\\\"}\", string(jsonBytes))\n\n\terrs = errorMsgs{}\n\tassert.Nil(t, errs.Last())\n\tassert.Nil(t, errs.JSON())\n\tassert.Empty(t, errs.String())\n}\n\ntype TestErr string\n\nfunc (e TestErr) Error() string { return string(e) }\n\n// TestErrorUnwrap tests the behavior of gin.Error with \"errors.Is()\" and \"errors.As()\".\n// \"errors.Is()\" and \"errors.As()\" have been added to the standard library in go 1.13.\nfunc TestErrorUnwrap(t *testing.T) {\n\tinnerErr := TestErr(\"some error\")\n\n\t// 2 layers of wrapping : use 'fmt.Errorf(\"%w\")' to wrap a gin.Error{}, which itself wraps innerErr\n\terr := fmt.Errorf(\"wrapped: %w\", &Error{\n\t\tErr:  innerErr,\n\t\tType: ErrorTypeAny,\n\t})\n\n\t// check that 'errors.Is()' and 'errors.As()' behave as expected :\n\trequire.ErrorIs(t, err, innerErr)\n\tvar testErr TestErr\n\trequire.ErrorAs(t, err, &testErr)\n\n\t// Test non-pointer usage of gin.Error\n\terrNonPointer := Error{\n\t\tErr:  innerErr,\n\t\tType: ErrorTypeAny,\n\t}\n\twrappedErr := fmt.Errorf(\"wrapped: %w\", errNonPointer)\n\t// Check that 'errors.Is()' and 'errors.As()' behave as expected for non-pointer usage\n\trequire.ErrorIs(t, wrappedErr, innerErr)\n\tvar testErrNonPointer TestErr\n\trequire.ErrorAs(t, wrappedErr, &testErrNonPointer)\n}\n"
  },
  {
    "path": "examples/README.md",
    "content": "# Gin Examples\n\n⚠️  **NOTICE:** All gin examples have been moved as standalone repository to [here](https://github.com/gin-gonic/examples).\n"
  },
  {
    "path": "fs.go",
    "content": "// Copyright 2017 Manu Martinez-Almeida. All rights reserved.\n// Use of this source code is governed by a MIT style\n// license that can be found in the LICENSE file.\n\npackage gin\n\nimport (\n\t\"net/http\"\n\t\"os\"\n)\n\n// OnlyFilesFS implements an http.FileSystem without `Readdir` functionality.\ntype OnlyFilesFS struct {\n\tFileSystem http.FileSystem\n}\n\n// Open passes `Open` to the upstream implementation without `Readdir` functionality.\nfunc (o OnlyFilesFS) Open(name string) (http.File, error) {\n\tf, err := o.FileSystem.Open(name)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn neutralizedReaddirFile{f}, nil\n}\n\n// neutralizedReaddirFile wraps http.File with a specific implementation of `Readdir`.\ntype neutralizedReaddirFile struct {\n\thttp.File\n}\n\n// Readdir overrides the http.File default implementation and always returns nil.\nfunc (n neutralizedReaddirFile) Readdir(_ int) ([]os.FileInfo, error) {\n\t// this disables directory listing\n\treturn nil, nil\n}\n\n// Dir returns an http.FileSystem that can be used by http.FileServer().\n// It is used internally in router.Static().\n// if listDirectory == true, then it works the same as http.Dir(),\n// otherwise it returns a filesystem that prevents http.FileServer() to list the directory files.\nfunc Dir(root string, listDirectory bool) http.FileSystem {\n\tfs := http.Dir(root)\n\n\tif listDirectory {\n\t\treturn fs\n\t}\n\n\treturn &OnlyFilesFS{FileSystem: fs}\n}\n"
  },
  {
    "path": "fs_test.go",
    "content": "package gin\n\nimport (\n\t\"errors\"\n\t\"net/http\"\n\t\"os\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n)\n\ntype mockFileSystem struct {\n\topen func(name string) (http.File, error)\n}\n\nfunc (m *mockFileSystem) Open(name string) (http.File, error) {\n\treturn m.open(name)\n}\n\nfunc TestOnlyFilesFS_Open(t *testing.T) {\n\tvar testFile *os.File\n\tmockFS := &mockFileSystem{\n\t\topen: func(name string) (http.File, error) {\n\t\t\treturn testFile, nil\n\t\t},\n\t}\n\tfs := &OnlyFilesFS{FileSystem: mockFS}\n\n\tfile, err := fs.Open(\"foo\")\n\n\trequire.NoError(t, err)\n\tassert.Equal(t, testFile, file.(neutralizedReaddirFile).File)\n}\n\nfunc TestOnlyFilesFS_Open_err(t *testing.T) {\n\ttestError := errors.New(\"mock\")\n\tmockFS := &mockFileSystem{\n\t\topen: func(_ string) (http.File, error) {\n\t\t\treturn nil, testError\n\t\t},\n\t}\n\tfs := &OnlyFilesFS{FileSystem: mockFS}\n\n\tfile, err := fs.Open(\"foo\")\n\n\trequire.ErrorIs(t, err, testError)\n\tassert.Nil(t, file)\n}\n\nfunc Test_neuteredReaddirFile_Readdir(t *testing.T) {\n\tn := neutralizedReaddirFile{}\n\n\tres, err := n.Readdir(0)\n\n\trequire.NoError(t, err)\n\tassert.Nil(t, res)\n}\n\nfunc TestDir_listDirectory(t *testing.T) {\n\ttestRoot := \"foo\"\n\tfs := Dir(testRoot, true)\n\n\tassert.Equal(t, http.Dir(testRoot), fs)\n}\n\nfunc TestDir(t *testing.T) {\n\ttestRoot := \"foo\"\n\tfs := Dir(testRoot, false)\n\n\tassert.Equal(t, &OnlyFilesFS{FileSystem: http.Dir(testRoot)}, fs)\n}\n"
  },
  {
    "path": "gin.go",
    "content": "// Copyright 2014 Manu Martinez-Almeida. All rights reserved.\n// Use of this source code is governed by a MIT style\n// license that can be found in the LICENSE file.\n\npackage gin\n\nimport (\n\t\"fmt\"\n\t\"html/template\"\n\t\"net\"\n\t\"net/http\"\n\t\"os\"\n\t\"path\"\n\t\"strings\"\n\t\"sync\"\n\n\t\"github.com/gin-gonic/gin/internal/bytesconv\"\n\tfilesystem \"github.com/gin-gonic/gin/internal/fs\"\n\t\"github.com/gin-gonic/gin/render\"\n\t\"github.com/quic-go/quic-go/http3\"\n\t\"golang.org/x/net/http2\"\n\t\"golang.org/x/net/http2/h2c\"\n)\n\nconst (\n\tdefaultMultipartMemory = 32 << 20 // 32 MB\n\tescapedColon           = \"\\\\:\"\n\tcolon                  = \":\"\n\tbackslash              = \"\\\\\"\n)\n\nvar (\n\tdefault404Body = []byte(\"404 page not found\")\n\tdefault405Body = []byte(\"405 method not allowed\")\n)\n\nvar defaultPlatform string\n\nvar defaultTrustedCIDRs = []*net.IPNet{\n\t{ // 0.0.0.0/0 (IPv4)\n\t\tIP:   net.IP{0x0, 0x0, 0x0, 0x0},\n\t\tMask: net.IPMask{0x0, 0x0, 0x0, 0x0},\n\t},\n\t{ // ::/0 (IPv6)\n\t\tIP:   net.IP{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0},\n\t\tMask: net.IPMask{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0},\n\t},\n}\n\n// HandlerFunc defines the handler used by gin middleware as return value.\ntype HandlerFunc func(*Context)\n\n// OptionFunc defines the function to change the default configuration\ntype OptionFunc func(*Engine)\n\n// HandlersChain defines a HandlerFunc slice.\ntype HandlersChain []HandlerFunc\n\n// Last returns the last handler in the chain. i.e. the last handler is the main one.\nfunc (c HandlersChain) Last() HandlerFunc {\n\tif length := len(c); length > 0 {\n\t\treturn c[length-1]\n\t}\n\treturn nil\n}\n\n// RouteInfo represents a request route's specification which contains method and path and its handler.\ntype RouteInfo struct {\n\tMethod      string\n\tPath        string\n\tHandler     string\n\tHandlerFunc HandlerFunc\n}\n\n// RoutesInfo defines a RouteInfo slice.\ntype RoutesInfo []RouteInfo\n\n// Trusted platforms\nconst (\n\t// PlatformGoogleAppEngine when running on Google App Engine. Trust X-Appengine-Remote-Addr\n\t// for determining the client's IP\n\tPlatformGoogleAppEngine = \"X-Appengine-Remote-Addr\"\n\t// PlatformCloudflare when using Cloudflare's CDN. Trust CF-Connecting-IP for determining\n\t// the client's IP\n\tPlatformCloudflare = \"CF-Connecting-IP\"\n\t// PlatformFlyIO when running on Fly.io. Trust Fly-Client-IP for determining the client's IP\n\tPlatformFlyIO = \"Fly-Client-IP\"\n)\n\n// Engine is the framework's instance, it contains the muxer, middleware and configuration settings.\n// Create an instance of Engine, by using New() or Default()\ntype Engine struct {\n\tRouterGroup\n\n\t// routeTreesUpdated ensures that the initialization or update of the route trees\n\t// (used for routing HTTP requests) happens only once, even if called multiple times concurrently.\n\trouteTreesUpdated sync.Once\n\n\t// RedirectTrailingSlash enables automatic redirection if the current route can't be matched but a\n\t// handler for the path with (without) the trailing slash exists.\n\t// For example if /foo/ is requested but a route only exists for /foo, the\n\t// client is redirected to /foo with http status code 301 for GET requests\n\t// and 307 for all other request methods.\n\tRedirectTrailingSlash bool\n\n\t// RedirectFixedPath if enabled, the router tries to fix the current request path, if no\n\t// handle is registered for it.\n\t// First superfluous path elements like ../ or // are removed.\n\t// Afterwards the router does a case-insensitive lookup of the cleaned path.\n\t// If a handle can be found for this route, the router makes a redirection\n\t// to the corrected path with status code 301 for GET requests and 307 for\n\t// all other request methods.\n\t// For example /FOO and /..//Foo could be redirected to /foo.\n\t// RedirectTrailingSlash is independent of this option.\n\tRedirectFixedPath bool\n\n\t// HandleMethodNotAllowed if enabled, the router checks if another method is allowed for the\n\t// current route, if the current request can not be routed.\n\t// If this is the case, the request is answered with 'Method Not Allowed'\n\t// and HTTP status code 405.\n\t// If no other Method is allowed, the request is delegated to the NotFound\n\t// handler.\n\tHandleMethodNotAllowed bool\n\n\t// ForwardedByClientIP if enabled, client IP will be parsed from the request's headers that\n\t// match those stored at `(*gin.Engine).RemoteIPHeaders`. If no IP was\n\t// fetched, it falls back to the IP obtained from\n\t// `(*gin.Context).Request.RemoteAddr`.\n\tForwardedByClientIP bool\n\n\t// AppEngine was deprecated.\n\t// Deprecated: USE `TrustedPlatform` WITH VALUE `gin.PlatformGoogleAppEngine` INSTEAD\n\t// #726 #755 If enabled, it will trust some headers starting with\n\t// 'X-AppEngine...' for better integration with that PaaS.\n\tAppEngine bool\n\n\t// UseRawPath if enabled, the url.RawPath will be used to find parameters.\n\t// The RawPath is only a hint, EscapedPath() should be use instead. (https://pkg.go.dev/net/url@master#URL)\n\t// Only use RawPath if you know what you are doing.\n\tUseRawPath bool\n\n\t// UseEscapedPath if enable, the url.EscapedPath() will be used to find parameters\n\t// It overrides UseRawPath\n\tUseEscapedPath bool\n\n\t// UnescapePathValues if true, the path value will be unescaped.\n\t// If UseRawPath and UseEscapedPath are false (by default), the UnescapePathValues effectively is true,\n\t// as url.Path gonna be used, which is already unescaped.\n\tUnescapePathValues bool\n\n\t// RemoveExtraSlash a parameter can be parsed from the URL even with extra slashes.\n\t// See the PR #1817 and issue #1644\n\tRemoveExtraSlash bool\n\n\t// RemoteIPHeaders list of headers used to obtain the client IP when\n\t// `(*gin.Engine).ForwardedByClientIP` is `true` and\n\t// `(*gin.Context).Request.RemoteAddr` is matched by at least one of the\n\t// network origins of list defined by `(*gin.Engine).SetTrustedProxies()`.\n\tRemoteIPHeaders []string\n\n\t// TrustedPlatform if set to a constant of value gin.Platform*, trusts the headers set by\n\t// that platform, for example to determine the client IP\n\tTrustedPlatform string\n\n\t// MaxMultipartMemory value of 'maxMemory' param that is given to http.Request's ParseMultipartForm\n\t// method call.\n\tMaxMultipartMemory int64\n\n\t// UseH2C enable h2c support.\n\tUseH2C bool\n\n\t// ContextWithFallback enable fallback Context.Deadline(), Context.Done(), Context.Err() and Context.Value() when Context.Request.Context() is not nil.\n\tContextWithFallback bool\n\n\tdelims           render.Delims\n\tsecureJSONPrefix string\n\tHTMLRender       render.HTMLRender\n\tFuncMap          template.FuncMap\n\tallNoRoute       HandlersChain\n\tallNoMethod      HandlersChain\n\tnoRoute          HandlersChain\n\tnoMethod         HandlersChain\n\tpool             sync.Pool\n\ttrees            methodTrees\n\tmaxParams        uint16\n\tmaxSections      uint16\n\ttrustedProxies   []string\n\ttrustedCIDRs     []*net.IPNet\n}\n\nvar _ IRouter = (*Engine)(nil)\n\n// New returns a new blank Engine instance without any middleware attached.\n// By default, the configuration is:\n// - RedirectTrailingSlash:  true\n// - RedirectFixedPath:      false\n// - HandleMethodNotAllowed: false\n// - ForwardedByClientIP:    true\n// - UseRawPath:             false\n// - UseEscapedPath: \t\t false\n// - UnescapePathValues:     true\nfunc New(opts ...OptionFunc) *Engine {\n\tdebugPrintWARNINGNew()\n\tengine := &Engine{\n\t\tRouterGroup: RouterGroup{\n\t\t\tHandlers: nil,\n\t\t\tbasePath: \"/\",\n\t\t\troot:     true,\n\t\t},\n\t\tFuncMap:                template.FuncMap{},\n\t\tRedirectTrailingSlash:  true,\n\t\tRedirectFixedPath:      false,\n\t\tHandleMethodNotAllowed: false,\n\t\tForwardedByClientIP:    true,\n\t\tRemoteIPHeaders:        []string{\"X-Forwarded-For\", \"X-Real-IP\"},\n\t\tTrustedPlatform:        defaultPlatform,\n\t\tUseRawPath:             false,\n\t\tUseEscapedPath:         false,\n\t\tRemoveExtraSlash:       false,\n\t\tUnescapePathValues:     true,\n\t\tMaxMultipartMemory:     defaultMultipartMemory,\n\t\ttrees:                  make(methodTrees, 0, 9),\n\t\tdelims:                 render.Delims{Left: \"{{\", Right: \"}}\"},\n\t\tsecureJSONPrefix:       \"while(1);\",\n\t\ttrustedProxies:         []string{\"0.0.0.0/0\", \"::/0\"},\n\t\ttrustedCIDRs:           defaultTrustedCIDRs,\n\t}\n\tengine.engine = engine\n\tengine.pool.New = func() any {\n\t\treturn engine.allocateContext(engine.maxParams)\n\t}\n\treturn engine.With(opts...)\n}\n\n// Default returns an Engine instance with the Logger and Recovery middleware already attached.\nfunc Default(opts ...OptionFunc) *Engine {\n\tdebugPrintWARNINGDefault()\n\tengine := New()\n\tengine.Use(Logger(), Recovery())\n\treturn engine.With(opts...)\n}\n\nfunc (engine *Engine) Handler() http.Handler {\n\tif !engine.UseH2C {\n\t\treturn engine\n\t}\n\n\th2s := &http2.Server{}\n\treturn h2c.NewHandler(engine, h2s)\n}\n\nfunc (engine *Engine) allocateContext(maxParams uint16) *Context {\n\tv := make(Params, 0, maxParams)\n\tskippedNodes := make([]skippedNode, 0, engine.maxSections)\n\treturn &Context{engine: engine, params: &v, skippedNodes: &skippedNodes}\n}\n\n// Delims sets template left and right delims and returns an Engine instance.\nfunc (engine *Engine) Delims(left, right string) *Engine {\n\tengine.delims = render.Delims{Left: left, Right: right}\n\treturn engine\n}\n\n// SecureJsonPrefix sets the secureJSONPrefix used in Context.SecureJSON.\nfunc (engine *Engine) SecureJsonPrefix(prefix string) *Engine {\n\tengine.secureJSONPrefix = prefix\n\treturn engine\n}\n\n// LoadHTMLGlob loads HTML files identified by glob pattern\n// and associates the result with HTML renderer.\nfunc (engine *Engine) LoadHTMLGlob(pattern string) {\n\tleft := engine.delims.Left\n\tright := engine.delims.Right\n\ttempl := template.Must(template.New(\"\").Delims(left, right).Funcs(engine.FuncMap).ParseGlob(pattern))\n\n\tif IsDebugging() {\n\t\tdebugPrintLoadTemplate(templ)\n\t\tengine.HTMLRender = render.HTMLDebug{Glob: pattern, FuncMap: engine.FuncMap, Delims: engine.delims}\n\t\treturn\n\t}\n\n\tengine.SetHTMLTemplate(templ)\n}\n\n// LoadHTMLFiles loads a slice of HTML files\n// and associates the result with HTML renderer.\nfunc (engine *Engine) LoadHTMLFiles(files ...string) {\n\tif IsDebugging() {\n\t\tengine.HTMLRender = render.HTMLDebug{Files: files, FuncMap: engine.FuncMap, Delims: engine.delims}\n\t\treturn\n\t}\n\n\ttempl := template.Must(template.New(\"\").Delims(engine.delims.Left, engine.delims.Right).Funcs(engine.FuncMap).ParseFiles(files...))\n\tengine.SetHTMLTemplate(templ)\n}\n\n// LoadHTMLFS loads an http.FileSystem and a slice of patterns\n// and associates the result with HTML renderer.\nfunc (engine *Engine) LoadHTMLFS(fs http.FileSystem, patterns ...string) {\n\tif IsDebugging() {\n\t\tengine.HTMLRender = render.HTMLDebug{FileSystem: fs, Patterns: patterns, FuncMap: engine.FuncMap, Delims: engine.delims}\n\t\treturn\n\t}\n\n\ttempl := template.Must(template.New(\"\").Delims(engine.delims.Left, engine.delims.Right).Funcs(engine.FuncMap).ParseFS(\n\t\tfilesystem.FileSystem{FileSystem: fs}, patterns...))\n\tengine.SetHTMLTemplate(templ)\n}\n\n// SetHTMLTemplate associate a template with HTML renderer.\nfunc (engine *Engine) SetHTMLTemplate(templ *template.Template) {\n\tif len(engine.trees) > 0 {\n\t\tdebugPrintWARNINGSetHTMLTemplate()\n\t}\n\n\tengine.HTMLRender = render.HTMLProduction{Template: templ.Funcs(engine.FuncMap)}\n}\n\n// SetFuncMap sets the FuncMap used for template.FuncMap.\nfunc (engine *Engine) SetFuncMap(funcMap template.FuncMap) {\n\tengine.FuncMap = funcMap\n}\n\n// NoRoute adds handlers for NoRoute. It returns a 404 code by default.\nfunc (engine *Engine) NoRoute(handlers ...HandlerFunc) {\n\tengine.noRoute = handlers\n\tengine.rebuild404Handlers()\n}\n\n// NoMethod sets the handlers called when Engine.HandleMethodNotAllowed = true.\nfunc (engine *Engine) NoMethod(handlers ...HandlerFunc) {\n\tengine.noMethod = handlers\n\tengine.rebuild405Handlers()\n}\n\n// Use attaches a global middleware to the router. i.e. the middleware attached through Use() will be\n// included in the handlers chain for every single request. Even 404, 405, static files...\n// For example, this is the right place for a logger or error management middleware.\nfunc (engine *Engine) Use(middleware ...HandlerFunc) IRoutes {\n\tengine.RouterGroup.Use(middleware...)\n\tengine.rebuild404Handlers()\n\tengine.rebuild405Handlers()\n\treturn engine\n}\n\n// With returns an Engine with the configuration set in the OptionFunc.\nfunc (engine *Engine) With(opts ...OptionFunc) *Engine {\n\tfor _, opt := range opts {\n\t\topt(engine)\n\t}\n\n\treturn engine\n}\n\nfunc (engine *Engine) rebuild404Handlers() {\n\tengine.allNoRoute = engine.combineHandlers(engine.noRoute)\n}\n\nfunc (engine *Engine) rebuild405Handlers() {\n\tengine.allNoMethod = engine.combineHandlers(engine.noMethod)\n}\n\nfunc (engine *Engine) addRoute(method, path string, handlers HandlersChain) {\n\tassert1(path[0] == '/', \"path must begin with '/'\")\n\tassert1(method != \"\", \"HTTP method can not be empty\")\n\tassert1(len(handlers) > 0, \"there must be at least one handler\")\n\n\tdebugPrintRoute(method, path, handlers)\n\n\troot := engine.trees.get(method)\n\tif root == nil {\n\t\troot = new(node)\n\t\troot.fullPath = \"/\"\n\t\tengine.trees = append(engine.trees, methodTree{method: method, root: root})\n\t}\n\troot.addRoute(path, handlers)\n\n\tif paramsCount := countParams(path); paramsCount > engine.maxParams {\n\t\tengine.maxParams = paramsCount\n\t}\n\n\tif sectionsCount := countSections(path); sectionsCount > engine.maxSections {\n\t\tengine.maxSections = sectionsCount\n\t}\n}\n\n// Routes returns a slice of registered routes, including some useful information, such as:\n// the http method, path, and the handler name.\nfunc (engine *Engine) Routes() (routes RoutesInfo) {\n\tfor _, tree := range engine.trees {\n\t\troutes = iterate(\"\", tree.method, routes, tree.root)\n\t}\n\treturn routes\n}\n\nfunc iterate(path, method string, routes RoutesInfo, root *node) RoutesInfo {\n\tpath += root.path\n\tif len(root.handlers) > 0 {\n\t\thandlerFunc := root.handlers.Last()\n\t\troutes = append(routes, RouteInfo{\n\t\t\tMethod:      method,\n\t\t\tPath:        path,\n\t\t\tHandler:     nameOfFunction(handlerFunc),\n\t\t\tHandlerFunc: handlerFunc,\n\t\t})\n\t}\n\tfor _, child := range root.children {\n\t\troutes = iterate(path, method, routes, child)\n\t}\n\treturn routes\n}\n\nfunc (engine *Engine) prepareTrustedCIDRs() ([]*net.IPNet, error) {\n\tif engine.trustedProxies == nil {\n\t\treturn nil, nil\n\t}\n\n\tcidr := make([]*net.IPNet, 0, len(engine.trustedProxies))\n\tfor _, trustedProxy := range engine.trustedProxies {\n\t\tif !strings.Contains(trustedProxy, \"/\") {\n\t\t\tip := parseIP(trustedProxy)\n\t\t\tif ip == nil {\n\t\t\t\treturn cidr, &net.ParseError{Type: \"IP address\", Text: trustedProxy}\n\t\t\t}\n\n\t\t\tswitch len(ip) {\n\t\t\tcase net.IPv4len:\n\t\t\t\ttrustedProxy += \"/32\"\n\t\t\tcase net.IPv6len:\n\t\t\t\ttrustedProxy += \"/128\"\n\t\t\t}\n\t\t}\n\t\t_, cidrNet, err := net.ParseCIDR(trustedProxy)\n\t\tif err != nil {\n\t\t\treturn cidr, err\n\t\t}\n\t\tcidr = append(cidr, cidrNet)\n\t}\n\treturn cidr, nil\n}\n\n// SetTrustedProxies set a list of network origins (IPv4 addresses,\n// IPv4 CIDRs, IPv6 addresses or IPv6 CIDRs) from which to trust\n// request's headers that contain alternative client IP when\n// `(*gin.Engine).ForwardedByClientIP` is `true`. `TrustedProxies`\n// feature is enabled by default, and it also trusts all proxies\n// by default. If you want to disable this feature, use\n// Engine.SetTrustedProxies(nil), then Context.ClientIP() will\n// return the remote address directly.\nfunc (engine *Engine) SetTrustedProxies(trustedProxies []string) error {\n\tengine.trustedProxies = trustedProxies\n\treturn engine.parseTrustedProxies()\n}\n\n// isUnsafeTrustedProxies checks if Engine.trustedCIDRs contains all IPs, it's not safe if it has (returns true)\nfunc (engine *Engine) isUnsafeTrustedProxies() bool {\n\treturn engine.isTrustedProxy(net.ParseIP(\"0.0.0.0\")) || engine.isTrustedProxy(net.ParseIP(\"::\"))\n}\n\n// parseTrustedProxies parse Engine.trustedProxies to Engine.trustedCIDRs\nfunc (engine *Engine) parseTrustedProxies() error {\n\ttrustedCIDRs, err := engine.prepareTrustedCIDRs()\n\tengine.trustedCIDRs = trustedCIDRs\n\treturn err\n}\n\n// isTrustedProxy will check whether the IP address is included in the trusted list according to Engine.trustedCIDRs\nfunc (engine *Engine) isTrustedProxy(ip net.IP) bool {\n\tif engine.trustedCIDRs == nil {\n\t\treturn false\n\t}\n\tfor _, cidr := range engine.trustedCIDRs {\n\t\tif cidr.Contains(ip) {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n\n// validateHeader will parse X-Forwarded-For header and return the trusted client IP address\nfunc (engine *Engine) validateHeader(header string) (clientIP string, valid bool) {\n\tif header == \"\" {\n\t\treturn \"\", false\n\t}\n\titems := strings.Split(header, \",\")\n\tfor i := len(items) - 1; i >= 0; i-- {\n\t\tipStr := strings.TrimSpace(items[i])\n\t\tip := net.ParseIP(ipStr)\n\t\tif ip == nil {\n\t\t\tbreak\n\t\t}\n\n\t\t// X-Forwarded-For is appended by proxy\n\t\t// Check IPs in reverse order and stop when find untrusted proxy\n\t\tif (i == 0) || (!engine.isTrustedProxy(ip)) {\n\t\t\treturn ipStr, true\n\t\t}\n\t}\n\treturn \"\", false\n}\n\n// updateRouteTree do update to the route tree recursively\nfunc updateRouteTree(n *node) {\n\tn.path = strings.ReplaceAll(n.path, escapedColon, colon)\n\tn.fullPath = strings.ReplaceAll(n.fullPath, escapedColon, colon)\n\tn.indices = strings.ReplaceAll(n.indices, backslash, colon)\n\tif n.children == nil {\n\t\treturn\n\t}\n\tfor _, child := range n.children {\n\t\tupdateRouteTree(child)\n\t}\n}\n\n// updateRouteTrees do update to the route trees\nfunc (engine *Engine) updateRouteTrees() {\n\tfor _, tree := range engine.trees {\n\t\tupdateRouteTree(tree.root)\n\t}\n}\n\n// parseIP parse a string representation of an IP and returns a net.IP with the\n// minimum byte representation or nil if input is invalid.\nfunc parseIP(ip string) net.IP {\n\tparsedIP := net.ParseIP(ip)\n\n\tif ipv4 := parsedIP.To4(); ipv4 != nil {\n\t\t// return ip in a 4-byte representation\n\t\treturn ipv4\n\t}\n\n\t// return ip in a 16-byte representation or nil\n\treturn parsedIP\n}\n\n// Run attaches the router to a http.Server and starts listening and serving HTTP requests.\n// It is a shortcut for http.ListenAndServe(addr, router)\n// Note: this method will block the calling goroutine indefinitely unless an error happens.\nfunc (engine *Engine) Run(addr ...string) (err error) {\n\tdefer func() { debugPrintError(err) }()\n\n\tif engine.isUnsafeTrustedProxies() {\n\t\tdebugPrint(\"[WARNING] You trusted all proxies, this is NOT safe. We recommend you to set a value.\\n\" +\n\t\t\t\"Please check https://github.com/gin-gonic/gin/blob/master/docs/doc.md#dont-trust-all-proxies for details.\")\n\t}\n\tengine.updateRouteTrees()\n\taddress := resolveAddress(addr)\n\tdebugPrint(\"Listening and serving HTTP on %s\\n\", address)\n\tserver := &http.Server{ // #nosec G112\n\t\tAddr:    address,\n\t\tHandler: engine.Handler(),\n\t}\n\terr = server.ListenAndServe()\n\treturn\n}\n\n// RunTLS attaches the router to a http.Server and starts listening and serving HTTPS (secure) requests.\n// It is a shortcut for http.ListenAndServeTLS(addr, certFile, keyFile, router)\n// Note: this method will block the calling goroutine indefinitely unless an error happens.\nfunc (engine *Engine) RunTLS(addr, certFile, keyFile string) (err error) {\n\tdebugPrint(\"Listening and serving HTTPS on %s\\n\", addr)\n\tdefer func() { debugPrintError(err) }()\n\n\tif engine.isUnsafeTrustedProxies() {\n\t\tdebugPrint(\"[WARNING] You trusted all proxies, this is NOT safe. We recommend you to set a value.\\n\" +\n\t\t\t\"Please check https://github.com/gin-gonic/gin/blob/master/docs/doc.md#dont-trust-all-proxies for details.\")\n\t}\n\n\tserver := &http.Server{ // #nosec G112\n\t\tAddr:    addr,\n\t\tHandler: engine.Handler(),\n\t}\n\terr = server.ListenAndServeTLS(certFile, keyFile)\n\treturn\n}\n\n// RunUnix attaches the router to a http.Server and starts listening and serving HTTP requests\n// through the specified unix socket (i.e. a file).\n// Note: this method will block the calling goroutine indefinitely unless an error happens.\nfunc (engine *Engine) RunUnix(file string) (err error) {\n\tdebugPrint(\"Listening and serving HTTP on unix:/%s\", file)\n\tdefer func() { debugPrintError(err) }()\n\n\tif engine.isUnsafeTrustedProxies() {\n\t\tdebugPrint(\"[WARNING] You trusted all proxies, this is NOT safe. We recommend you to set a value.\\n\" +\n\t\t\t\"Please check https://github.com/gin-gonic/gin/blob/master/docs/doc.md#dont-trust-all-proxies for details.\")\n\t}\n\n\tlistener, err := net.Listen(\"unix\", file)\n\tif err != nil {\n\t\treturn\n\t}\n\tdefer listener.Close()\n\tdefer os.Remove(file)\n\n\tserver := &http.Server{ // #nosec G112\n\t\tHandler: engine.Handler(),\n\t}\n\terr = server.Serve(listener)\n\treturn\n}\n\n// RunFd attaches the router to a http.Server and starts listening and serving HTTP requests\n// through the specified file descriptor.\n// Note: this method will block the calling goroutine indefinitely unless an error happens.\nfunc (engine *Engine) RunFd(fd int) (err error) {\n\tdebugPrint(\"Listening and serving HTTP on fd@%d\", fd)\n\tdefer func() { debugPrintError(err) }()\n\n\tif engine.isUnsafeTrustedProxies() {\n\t\tdebugPrint(\"[WARNING] You trusted all proxies, this is NOT safe. We recommend you to set a value.\\n\" +\n\t\t\t\"Please check https://github.com/gin-gonic/gin/blob/master/docs/doc.md#dont-trust-all-proxies for details.\")\n\t}\n\n\tf := os.NewFile(uintptr(fd), fmt.Sprintf(\"fd@%d\", fd))\n\tdefer f.Close()\n\tlistener, err := net.FileListener(f)\n\tif err != nil {\n\t\treturn\n\t}\n\tdefer listener.Close()\n\terr = engine.RunListener(listener)\n\treturn\n}\n\n// RunQUIC attaches the router to a http.Server and starts listening and serving QUIC requests.\n// It is a shortcut for http3.ListenAndServeQUIC(addr, certFile, keyFile, router)\n// Note: this method will block the calling goroutine indefinitely unless an error happens.\nfunc (engine *Engine) RunQUIC(addr, certFile, keyFile string) (err error) {\n\tdebugPrint(\"Listening and serving QUIC on %s\\n\", addr)\n\tdefer func() { debugPrintError(err) }()\n\n\tif engine.isUnsafeTrustedProxies() {\n\t\tdebugPrint(\"[WARNING] You trusted all proxies, this is NOT safe. We recommend you to set a value.\\n\" +\n\t\t\t\"Please check https://github.com/gin-gonic/gin/blob/master/docs/doc.md#dont-trust-all-proxies for details.\")\n\t}\n\n\terr = http3.ListenAndServeQUIC(addr, certFile, keyFile, engine.Handler())\n\treturn\n}\n\n// RunListener attaches the router to a http.Server and starts listening and serving HTTP requests\n// through the specified net.Listener\nfunc (engine *Engine) RunListener(listener net.Listener) (err error) {\n\tdebugPrint(\"Listening and serving HTTP on listener what's bind with address@%s\", listener.Addr())\n\tdefer func() { debugPrintError(err) }()\n\n\tif engine.isUnsafeTrustedProxies() {\n\t\tdebugPrint(\"[WARNING] You trusted all proxies, this is NOT safe. We recommend you to set a value.\\n\" +\n\t\t\t\"Please check https://github.com/gin-gonic/gin/blob/master/docs/doc.md#dont-trust-all-proxies for details.\")\n\t}\n\n\tserver := &http.Server{ // #nosec G112\n\t\tHandler: engine.Handler(),\n\t}\n\terr = server.Serve(listener)\n\treturn\n}\n\n// ServeHTTP conforms to the http.Handler interface.\nfunc (engine *Engine) ServeHTTP(w http.ResponseWriter, req *http.Request) {\n\tengine.routeTreesUpdated.Do(func() {\n\t\tengine.updateRouteTrees()\n\t})\n\n\tc := engine.pool.Get().(*Context)\n\tc.writermem.reset(w)\n\tc.Request = req\n\tc.reset()\n\n\tengine.handleHTTPRequest(c)\n\n\tengine.pool.Put(c)\n}\n\n// HandleContext re-enters a context that has been rewritten.\n// This can be done by setting c.Request.URL.Path to your new target.\n// Disclaimer: You can loop yourself to deal with this, use wisely.\nfunc (engine *Engine) HandleContext(c *Context) {\n\toldIndexValue := c.index\n\toldHandlers := c.handlers\n\tc.reset()\n\tengine.handleHTTPRequest(c)\n\n\tc.index = oldIndexValue\n\tc.handlers = oldHandlers\n}\n\nfunc (engine *Engine) handleHTTPRequest(c *Context) {\n\thttpMethod := c.Request.Method\n\trPath := c.Request.URL.Path\n\tunescape := false\n\n\tif engine.UseEscapedPath {\n\t\trPath = c.Request.URL.EscapedPath()\n\t\tunescape = engine.UnescapePathValues\n\t} else if engine.UseRawPath && len(c.Request.URL.RawPath) > 0 {\n\t\trPath = c.Request.URL.RawPath\n\t\tunescape = engine.UnescapePathValues\n\t}\n\n\tif engine.RemoveExtraSlash {\n\t\trPath = cleanPath(rPath)\n\t}\n\n\t// Find root of the tree for the given HTTP method\n\tt := engine.trees\n\tfor i, tl := 0, len(t); i < tl; i++ {\n\t\tif t[i].method != httpMethod {\n\t\t\tcontinue\n\t\t}\n\t\troot := t[i].root\n\t\t// Find route in tree\n\t\tvalue := root.getValue(rPath, c.params, c.skippedNodes, unescape)\n\t\tif value.params != nil {\n\t\t\tc.Params = *value.params\n\t\t}\n\t\tif value.handlers != nil {\n\t\t\tc.handlers = value.handlers\n\t\t\tc.fullPath = value.fullPath\n\t\t\tc.Next()\n\t\t\tc.writermem.WriteHeaderNow()\n\t\t\treturn\n\t\t}\n\t\tif httpMethod != http.MethodConnect && rPath != \"/\" {\n\t\t\tif value.tsr && engine.RedirectTrailingSlash {\n\t\t\t\tredirectTrailingSlash(c)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif engine.RedirectFixedPath && redirectFixedPath(c, root, engine.RedirectFixedPath) {\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\t\tbreak\n\t}\n\n\tif engine.HandleMethodNotAllowed && len(t) > 0 {\n\t\t// According to RFC 7231 section 6.5.5, MUST generate an Allow header field in response\n\t\t// containing a list of the target resource's currently supported methods.\n\t\tallowed := make([]string, 0, len(t)-1)\n\t\tfor _, tree := range engine.trees {\n\t\t\tif tree.method == httpMethod {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif value := tree.root.getValue(rPath, nil, c.skippedNodes, unescape); value.handlers != nil {\n\t\t\t\tallowed = append(allowed, tree.method)\n\t\t\t}\n\t\t}\n\t\tif len(allowed) > 0 {\n\t\t\tc.handlers = engine.allNoMethod\n\t\t\tc.writermem.Header().Set(\"Allow\", strings.Join(allowed, \", \"))\n\t\t\tserveError(c, http.StatusMethodNotAllowed, default405Body)\n\t\t\treturn\n\t\t}\n\t}\n\n\tc.handlers = engine.allNoRoute\n\tserveError(c, http.StatusNotFound, default404Body)\n}\n\nvar mimePlain = []string{MIMEPlain}\n\nfunc serveError(c *Context, code int, defaultMessage []byte) {\n\tc.writermem.status = code\n\tc.Next()\n\tif c.writermem.Written() {\n\t\treturn\n\t}\n\tif c.writermem.Status() == code {\n\t\tc.writermem.Header()[\"Content-Type\"] = mimePlain\n\t\t_, err := c.Writer.Write(defaultMessage)\n\t\tif err != nil {\n\t\t\tdebugPrint(\"cannot write message to writer during serve error: %v\", err)\n\t\t}\n\t\treturn\n\t}\n\tc.writermem.WriteHeaderNow()\n}\n\nfunc redirectTrailingSlash(c *Context) {\n\treq := c.Request\n\tp := req.URL.Path\n\tif prefix := path.Clean(c.Request.Header.Get(\"X-Forwarded-Prefix\")); prefix != \".\" {\n\t\tprefix = sanitizePathChars(prefix)\n\t\tprefix = removeRepeatedChar(prefix, '/')\n\n\t\tp = prefix + \"/\" + req.URL.Path\n\t}\n\treq.URL.Path = p + \"/\"\n\tif length := len(p); length > 1 && p[length-1] == '/' {\n\t\treq.URL.Path = p[:length-1]\n\t}\n\tredirectRequest(c)\n}\n\n// sanitizePathChars removes unsafe characters from path strings,\n// keeping only ASCII letters, ASCII numbers, forward slashes, and hyphens.\nfunc sanitizePathChars(s string) string {\n\treturn strings.Map(func(r rune) rune {\n\t\tif (r >= 'a' && r <= 'z') || (r >= 'A' && r <= 'Z') || (r >= '0' && r <= '9') || r == '/' || r == '-' {\n\t\t\treturn r\n\t\t}\n\t\treturn -1\n\t}, s)\n}\n\nfunc redirectFixedPath(c *Context, root *node, trailingSlash bool) bool {\n\treq := c.Request\n\trPath := req.URL.Path\n\n\tif fixedPath, ok := root.findCaseInsensitivePath(cleanPath(rPath), trailingSlash); ok {\n\t\treq.URL.Path = bytesconv.BytesToString(fixedPath)\n\t\tredirectRequest(c)\n\t\treturn true\n\t}\n\treturn false\n}\n\nfunc redirectRequest(c *Context) {\n\treq := c.Request\n\trPath := req.URL.Path\n\trURL := req.URL.String()\n\n\tcode := http.StatusMovedPermanently // Permanent redirect, request with GET method\n\tif req.Method != http.MethodGet {\n\t\tcode = http.StatusTemporaryRedirect\n\t}\n\tdebugPrint(\"redirecting request %d: %s --> %s\", code, rPath, rURL)\n\thttp.Redirect(c.Writer, req, rURL, code)\n\tc.writermem.WriteHeaderNow()\n}\n"
  },
  {
    "path": "ginS/README.md",
    "content": "# Gin Default Server\n\nThis is API experiment for Gin.\n\n```go\npackage main\n\nimport (\n\t\"github.com/gin-gonic/gin\"\n\t\"github.com/gin-gonic/gin/ginS\"\n)\n\nfunc main() {\n\tginS.GET(\"/\", func(c *gin.Context) { c.String(200, \"Hello World\") })\n\tginS.Run()\n}\n```\n"
  },
  {
    "path": "ginS/gins.go",
    "content": "// Copyright 2014 Manu Martinez-Almeida. All rights reserved.\n// Use of this source code is governed by a MIT style\n// license that can be found in the LICENSE file.\n\npackage ginS\n\nimport (\n\t\"html/template\"\n\t\"net/http\"\n\t\"sync\"\n\n\t\"github.com/gin-gonic/gin\"\n)\n\nvar engine = sync.OnceValue(func() *gin.Engine {\n\treturn gin.Default()\n})\n\n// LoadHTMLGlob is a wrapper for Engine.LoadHTMLGlob.\nfunc LoadHTMLGlob(pattern string) {\n\tengine().LoadHTMLGlob(pattern)\n}\n\n// LoadHTMLFiles is a wrapper for Engine.LoadHTMLFiles.\nfunc LoadHTMLFiles(files ...string) {\n\tengine().LoadHTMLFiles(files...)\n}\n\n// LoadHTMLFS is a wrapper for Engine.LoadHTMLFS.\nfunc LoadHTMLFS(fs http.FileSystem, patterns ...string) {\n\tengine().LoadHTMLFS(fs, patterns...)\n}\n\n// SetHTMLTemplate is a wrapper for Engine.SetHTMLTemplate.\nfunc SetHTMLTemplate(templ *template.Template) {\n\tengine().SetHTMLTemplate(templ)\n}\n\n// NoRoute adds handlers for NoRoute. It returns a 404 code by default.\nfunc NoRoute(handlers ...gin.HandlerFunc) {\n\tengine().NoRoute(handlers...)\n}\n\n// NoMethod is a wrapper for Engine.NoMethod.\nfunc NoMethod(handlers ...gin.HandlerFunc) {\n\tengine().NoMethod(handlers...)\n}\n\n// Group creates a new router group. You should add all the routes that have common middlewares or the same path prefix.\n// For example, all the routes that use a common middleware for authorization could be grouped.\nfunc Group(relativePath string, handlers ...gin.HandlerFunc) *gin.RouterGroup {\n\treturn engine().Group(relativePath, handlers...)\n}\n\n// Handle is a wrapper for Engine.Handle.\nfunc Handle(httpMethod, relativePath string, handlers ...gin.HandlerFunc) gin.IRoutes {\n\treturn engine().Handle(httpMethod, relativePath, handlers...)\n}\n\n// POST is a shortcut for router.Handle(\"POST\", path, handle)\nfunc POST(relativePath string, handlers ...gin.HandlerFunc) gin.IRoutes {\n\treturn engine().POST(relativePath, handlers...)\n}\n\n// GET is a shortcut for router.Handle(\"GET\", path, handle)\nfunc GET(relativePath string, handlers ...gin.HandlerFunc) gin.IRoutes {\n\treturn engine().GET(relativePath, handlers...)\n}\n\n// DELETE is a shortcut for router.Handle(\"DELETE\", path, handle)\nfunc DELETE(relativePath string, handlers ...gin.HandlerFunc) gin.IRoutes {\n\treturn engine().DELETE(relativePath, handlers...)\n}\n\n// PATCH is a shortcut for router.Handle(\"PATCH\", path, handle)\nfunc PATCH(relativePath string, handlers ...gin.HandlerFunc) gin.IRoutes {\n\treturn engine().PATCH(relativePath, handlers...)\n}\n\n// PUT is a shortcut for router.Handle(\"PUT\", path, handle)\nfunc PUT(relativePath string, handlers ...gin.HandlerFunc) gin.IRoutes {\n\treturn engine().PUT(relativePath, handlers...)\n}\n\n// OPTIONS is a shortcut for router.Handle(\"OPTIONS\", path, handle)\nfunc OPTIONS(relativePath string, handlers ...gin.HandlerFunc) gin.IRoutes {\n\treturn engine().OPTIONS(relativePath, handlers...)\n}\n\n// HEAD is a shortcut for router.Handle(\"HEAD\", path, handle)\nfunc HEAD(relativePath string, handlers ...gin.HandlerFunc) gin.IRoutes {\n\treturn engine().HEAD(relativePath, handlers...)\n}\n\n// Any is a wrapper for Engine.Any.\nfunc Any(relativePath string, handlers ...gin.HandlerFunc) gin.IRoutes {\n\treturn engine().Any(relativePath, handlers...)\n}\n\n// StaticFile is a wrapper for Engine.StaticFile.\nfunc StaticFile(relativePath, filepath string) gin.IRoutes {\n\treturn engine().StaticFile(relativePath, filepath)\n}\n\n// Static serves files from the given file system root.\n// Internally a http.FileServer is used, therefore http.NotFound is used instead\n// of the Router's NotFound handler.\n// To use the operating system's file system implementation,\n// use :\n//\n//\trouter.Static(\"/static\", \"/var/www\")\nfunc Static(relativePath, root string) gin.IRoutes {\n\treturn engine().Static(relativePath, root)\n}\n\n// StaticFS is a wrapper for Engine.StaticFS.\nfunc StaticFS(relativePath string, fs http.FileSystem) gin.IRoutes {\n\treturn engine().StaticFS(relativePath, fs)\n}\n\n// Use attaches a global middleware to the router. i.e. the middlewares attached through Use() will be\n// included in the handlers chain for every single request. Even 404, 405, static files...\n// For example, this is the right place for a logger or error management middleware.\nfunc Use(middlewares ...gin.HandlerFunc) gin.IRoutes {\n\treturn engine().Use(middlewares...)\n}\n\n// Routes returns a slice of registered routes.\nfunc Routes() gin.RoutesInfo {\n\treturn engine().Routes()\n}\n\n// Run attaches to a http.Server and starts listening and serving HTTP requests.\n// It is a shortcut for http.ListenAndServe(addr, router)\n// Note: this method will block the calling goroutine indefinitely unless an error happens.\nfunc Run(addr ...string) (err error) {\n\treturn engine().Run(addr...)\n}\n\n// RunTLS attaches to a http.Server and starts listening and serving HTTPS requests.\n// It is a shortcut for http.ListenAndServeTLS(addr, certFile, keyFile, router)\n// Note: this method will block the calling goroutine indefinitely unless an error happens.\nfunc RunTLS(addr, certFile, keyFile string) (err error) {\n\treturn engine().RunTLS(addr, certFile, keyFile)\n}\n\n// RunUnix attaches to a http.Server and starts listening and serving HTTP requests\n// through the specified unix socket (i.e. a file)\n// Note: this method will block the calling goroutine indefinitely unless an error happens.\nfunc RunUnix(file string) (err error) {\n\treturn engine().RunUnix(file)\n}\n\n// RunFd attaches the router to a http.Server and starts listening and serving HTTP requests\n// through the specified file descriptor.\n// Note: the method will block the calling goroutine indefinitely unless an error happens.\nfunc RunFd(fd int) (err error) {\n\treturn engine().RunFd(fd)\n}\n"
  },
  {
    "path": "ginS/gins_test.go",
    "content": "// Copyright 2025 Gin Core Team. All rights reserved.\n// Use of this source code is governed by a MIT style\n// license that can be found in the LICENSE file.\n\npackage ginS\n\nimport (\n\t\"html/template\"\n\t\"net/http\"\n\t\"net/http/httptest\"\n\t\"testing\"\n\n\t\"github.com/gin-gonic/gin\"\n\t\"github.com/stretchr/testify/assert\"\n)\n\nfunc init() {\n\tgin.SetMode(gin.TestMode)\n}\n\nfunc TestGET(t *testing.T) {\n\tGET(\"/test\", func(c *gin.Context) {\n\t\tc.String(http.StatusOK, \"test\")\n\t})\n\n\treq := httptest.NewRequest(http.MethodGet, \"/test\", nil)\n\tw := httptest.NewRecorder()\n\tengine().ServeHTTP(w, req)\n\n\tassert.Equal(t, http.StatusOK, w.Code)\n\tassert.Equal(t, \"test\", w.Body.String())\n}\n\nfunc TestPOST(t *testing.T) {\n\tPOST(\"/post\", func(c *gin.Context) {\n\t\tc.String(http.StatusCreated, \"created\")\n\t})\n\n\treq := httptest.NewRequest(http.MethodPost, \"/post\", nil)\n\tw := httptest.NewRecorder()\n\tengine().ServeHTTP(w, req)\n\n\tassert.Equal(t, http.StatusCreated, w.Code)\n\tassert.Equal(t, \"created\", w.Body.String())\n}\n\nfunc TestPUT(t *testing.T) {\n\tPUT(\"/put\", func(c *gin.Context) {\n\t\tc.String(http.StatusOK, \"updated\")\n\t})\n\n\treq := httptest.NewRequest(http.MethodPut, \"/put\", nil)\n\tw := httptest.NewRecorder()\n\tengine().ServeHTTP(w, req)\n\n\tassert.Equal(t, http.StatusOK, w.Code)\n\tassert.Equal(t, \"updated\", w.Body.String())\n}\n\nfunc TestDELETE(t *testing.T) {\n\tDELETE(\"/delete\", func(c *gin.Context) {\n\t\tc.String(http.StatusOK, \"deleted\")\n\t})\n\n\treq := httptest.NewRequest(http.MethodDelete, \"/delete\", nil)\n\tw := httptest.NewRecorder()\n\tengine().ServeHTTP(w, req)\n\n\tassert.Equal(t, http.StatusOK, w.Code)\n\tassert.Equal(t, \"deleted\", w.Body.String())\n}\n\nfunc TestPATCH(t *testing.T) {\n\tPATCH(\"/patch\", func(c *gin.Context) {\n\t\tc.String(http.StatusOK, \"patched\")\n\t})\n\n\treq := httptest.NewRequest(http.MethodPatch, \"/patch\", nil)\n\tw := httptest.NewRecorder()\n\tengine().ServeHTTP(w, req)\n\n\tassert.Equal(t, http.StatusOK, w.Code)\n\tassert.Equal(t, \"patched\", w.Body.String())\n}\n\nfunc TestOPTIONS(t *testing.T) {\n\tOPTIONS(\"/options\", func(c *gin.Context) {\n\t\tc.String(http.StatusOK, \"options\")\n\t})\n\n\treq := httptest.NewRequest(http.MethodOptions, \"/options\", nil)\n\tw := httptest.NewRecorder()\n\tengine().ServeHTTP(w, req)\n\n\tassert.Equal(t, http.StatusOK, w.Code)\n\tassert.Equal(t, \"options\", w.Body.String())\n}\n\nfunc TestHEAD(t *testing.T) {\n\tHEAD(\"/head\", func(c *gin.Context) {\n\t\tc.String(http.StatusOK, \"head\")\n\t})\n\n\treq := httptest.NewRequest(http.MethodHead, \"/head\", nil)\n\tw := httptest.NewRecorder()\n\tengine().ServeHTTP(w, req)\n\n\tassert.Equal(t, http.StatusOK, w.Code)\n}\n\nfunc TestAny(t *testing.T) {\n\tAny(\"/any\", func(c *gin.Context) {\n\t\tc.String(http.StatusOK, \"any\")\n\t})\n\n\treq := httptest.NewRequest(http.MethodGet, \"/any\", nil)\n\tw := httptest.NewRecorder()\n\tengine().ServeHTTP(w, req)\n\n\tassert.Equal(t, http.StatusOK, w.Code)\n\tassert.Equal(t, \"any\", w.Body.String())\n}\n\nfunc TestHandle(t *testing.T) {\n\tHandle(http.MethodGet, \"/handle\", func(c *gin.Context) {\n\t\tc.String(http.StatusOK, \"handle\")\n\t})\n\n\treq := httptest.NewRequest(http.MethodGet, \"/handle\", nil)\n\tw := httptest.NewRecorder()\n\tengine().ServeHTTP(w, req)\n\n\tassert.Equal(t, http.StatusOK, w.Code)\n\tassert.Equal(t, \"handle\", w.Body.String())\n}\n\nfunc TestGroup(t *testing.T) {\n\tgroup := Group(\"/group\")\n\tgroup.GET(\"/test\", func(c *gin.Context) {\n\t\tc.String(http.StatusOK, \"group test\")\n\t})\n\n\treq := httptest.NewRequest(http.MethodGet, \"/group/test\", nil)\n\tw := httptest.NewRecorder()\n\tengine().ServeHTTP(w, req)\n\n\tassert.Equal(t, http.StatusOK, w.Code)\n\tassert.Equal(t, \"group test\", w.Body.String())\n}\n\nfunc TestUse(t *testing.T) {\n\tvar middlewareExecuted bool\n\tUse(func(c *gin.Context) {\n\t\tmiddlewareExecuted = true\n\t\tc.Next()\n\t})\n\n\tGET(\"/middleware-test\", func(c *gin.Context) {\n\t\tc.String(http.StatusOK, \"ok\")\n\t})\n\n\treq := httptest.NewRequest(http.MethodGet, \"/middleware-test\", nil)\n\tw := httptest.NewRecorder()\n\tengine().ServeHTTP(w, req)\n\n\tassert.True(t, middlewareExecuted)\n\tassert.Equal(t, http.StatusOK, w.Code)\n}\n\nfunc TestNoRoute(t *testing.T) {\n\tNoRoute(func(c *gin.Context) {\n\t\tc.String(http.StatusNotFound, \"custom 404\")\n\t})\n\n\treq := httptest.NewRequest(http.MethodGet, \"/nonexistent\", nil)\n\tw := httptest.NewRecorder()\n\tengine().ServeHTTP(w, req)\n\n\tassert.Equal(t, http.StatusNotFound, w.Code)\n\tassert.Equal(t, \"custom 404\", w.Body.String())\n}\n\nfunc TestNoMethod(t *testing.T) {\n\tNoMethod(func(c *gin.Context) {\n\t\tc.String(http.StatusMethodNotAllowed, \"method not allowed\")\n\t})\n\n\t// This just verifies that NoMethod is callable\n\t// Testing the actual behavior would require a separate engine instance\n\tassert.NotNil(t, engine())\n}\n\nfunc TestRoutes(t *testing.T) {\n\tGET(\"/routes-test\", func(c *gin.Context) {})\n\n\troutes := Routes()\n\tassert.NotEmpty(t, routes)\n\n\tfound := false\n\tfor _, route := range routes {\n\t\tif route.Path == \"/routes-test\" && route.Method == http.MethodGet {\n\t\t\tfound = true\n\t\t\tbreak\n\t\t}\n\t}\n\tassert.True(t, found)\n}\n\nfunc TestSetHTMLTemplate(t *testing.T) {\n\ttmpl := template.Must(template.New(\"test\").Parse(\"Hello {{.}}\"))\n\tSetHTMLTemplate(tmpl)\n\n\t// Verify engine has template set\n\tassert.NotNil(t, engine())\n}\n\nfunc TestStaticFile(t *testing.T) {\n\tStaticFile(\"/static-file\", \"../testdata/test_file.txt\")\n\n\treq := httptest.NewRequest(http.MethodGet, \"/static-file\", nil)\n\tw := httptest.NewRecorder()\n\tengine().ServeHTTP(w, req)\n\n\tassert.Equal(t, http.StatusOK, w.Code)\n}\n\nfunc TestStatic(t *testing.T) {\n\tStatic(\"/static-dir\", \"../testdata\")\n\n\treq := httptest.NewRequest(http.MethodGet, \"/static-dir/test_file.txt\", nil)\n\tw := httptest.NewRecorder()\n\tengine().ServeHTTP(w, req)\n\n\tassert.Equal(t, http.StatusOK, w.Code)\n}\n\nfunc TestStaticFS(t *testing.T) {\n\tfs := http.Dir(\"../testdata\")\n\tStaticFS(\"/static-fs\", fs)\n\n\treq := httptest.NewRequest(http.MethodGet, \"/static-fs/test_file.txt\", nil)\n\tw := httptest.NewRecorder()\n\tengine().ServeHTTP(w, req)\n\n\tassert.Equal(t, http.StatusOK, w.Code)\n}\n"
  },
  {
    "path": "gin_integration_test.go",
    "content": "// Copyright 2017 Manu Martinez-Almeida. All rights reserved.\n// Use of this source code is governed by a MIT style\n// license that can be found in the LICENSE file.\n\npackage gin\n\nimport (\n\t\"bufio\"\n\t\"crypto/tls\"\n\t\"fmt\"\n\t\"html/template\"\n\t\"io\"\n\t\"net\"\n\t\"net/http\"\n\t\"net/http/httptest\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"runtime\"\n\t\"strings\"\n\t\"sync\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n)\n\n// params[0]=url example:http://127.0.0.1:8080/index (cannot be empty)\n// params[1]=response status (custom compare status) default:\"200 OK\"\n// params[2]=response body (custom compare content)  default:\"it worked\"\nfunc testRequest(t *testing.T, params ...string) {\n\tif len(params) == 0 {\n\t\tt.Fatal(\"url cannot be empty\")\n\t}\n\n\ttr := &http.Transport{\n\t\tTLSClientConfig: &tls.Config{\n\t\t\tInsecureSkipVerify: true,\n\t\t},\n\t}\n\tclient := &http.Client{Transport: tr}\n\n\tresp, err := client.Get(params[0])\n\trequire.NoError(t, err)\n\tdefer resp.Body.Close()\n\n\tbody, ioerr := io.ReadAll(resp.Body)\n\trequire.NoError(t, ioerr)\n\n\tresponseStatus := \"200 OK\"\n\tif len(params) > 1 && params[1] != \"\" {\n\t\tresponseStatus = params[1]\n\t}\n\n\tresponseBody := \"it worked\"\n\tif len(params) > 2 && params[2] != \"\" {\n\t\tresponseBody = params[2]\n\t}\n\n\tassert.Equal(t, responseStatus, resp.Status, \"should get a \"+responseStatus)\n\tif responseStatus == \"200 OK\" {\n\t\tassert.Equal(t, responseBody, string(body), \"resp body should match\")\n\t}\n}\n\nfunc TestRunEmpty(t *testing.T) {\n\tos.Setenv(\"PORT\", \"\")\n\trouter := New()\n\tgo func() {\n\t\trouter.GET(\"/example\", func(c *Context) { c.String(http.StatusOK, \"it worked\") })\n\t\tassert.NoError(t, router.Run())\n\t}()\n\n\t// Wait for server to be ready with exponential backoff\n\terr := waitForServerReady(\"http://localhost:8080/example\", 10)\n\trequire.NoError(t, err, \"server should start successfully\")\n\n\trequire.Error(t, router.Run(\":8080\"))\n\ttestRequest(t, \"http://localhost:8080/example\")\n}\n\nfunc TestBadTrustedCIDRs(t *testing.T) {\n\trouter := New()\n\trequire.Error(t, router.SetTrustedProxies([]string{\"hello/world\"}))\n}\n\n/* legacy tests\nfunc TestBadTrustedCIDRsForRun(t *testing.T) {\n\tos.Setenv(\"PORT\", \"\")\n\trouter := New()\n\trouter.TrustedProxies = []string{\"hello/world\"}\n\trequire.Error(t, router.Run(\":8080\"))\n}\n\nfunc TestBadTrustedCIDRsForRunUnix(t *testing.T) {\n\trouter := New()\n\trouter.TrustedProxies = []string{\"hello/world\"}\n\n\tunixTestSocket := filepath.Join(os.TempDir(), \"unix_unit_test\")\n\n\tdefer os.Remove(unixTestSocket)\n\n\tgo func() {\n\t\trouter.GET(\"/example\", func(c *Context) { c.String(http.StatusOK, \"it worked\") })\n\t\trequire.Error(t, router.RunUnix(unixTestSocket))\n\t}()\n\t// have to wait for the goroutine to start and run the server\n\t// otherwise the main thread will complete\n\ttime.Sleep(5 * time.Millisecond)\n}\n\nfunc TestBadTrustedCIDRsForRunFd(t *testing.T) {\n\trouter := New()\n\trouter.TrustedProxies = []string{\"hello/world\"}\n\n\taddr, err := net.ResolveTCPAddr(\"tcp\", \"localhost:0\")\n\trequire.NoError(t, err)\n\tlistener, err := net.ListenTCP(\"tcp\", addr)\n\trequire.NoError(t, err)\n\tsocketFile, err := listener.File()\n\trequire.NoError(t, err)\n\n\tgo func() {\n\t\trouter.GET(\"/example\", func(c *Context) { c.String(http.StatusOK, \"it worked\") })\n\t\trequire.Error(t, router.RunFd(int(socketFile.Fd())))\n\t}()\n\t// have to wait for the goroutine to start and run the server\n\t// otherwise the main thread will complete\n\ttime.Sleep(5 * time.Millisecond)\n}\n\nfunc TestBadTrustedCIDRsForRunListener(t *testing.T) {\n\trouter := New()\n\trouter.TrustedProxies = []string{\"hello/world\"}\n\n\taddr, err := net.ResolveTCPAddr(\"tcp\", \"localhost:0\")\n\trequire.NoError(t, err)\n\tlistener, err := net.ListenTCP(\"tcp\", addr)\n\trequire.NoError(t, err)\n\tgo func() {\n\t\trouter.GET(\"/example\", func(c *Context) { c.String(http.StatusOK, \"it worked\") })\n\t\trequire.Error(t, router.RunListener(listener))\n\t}()\n\t// have to wait for the goroutine to start and run the server\n\t// otherwise the main thread will complete\n\ttime.Sleep(5 * time.Millisecond)\n}\n\nfunc TestBadTrustedCIDRsForRunTLS(t *testing.T) {\n\tos.Setenv(\"PORT\", \"\")\n\trouter := New()\n\trouter.TrustedProxies = []string{\"hello/world\"}\n\trequire.Error(t, router.RunTLS(\":8080\", \"./testdata/certificate/cert.pem\", \"./testdata/certificate/key.pem\"))\n}\n*/\n\nfunc TestRunTLS(t *testing.T) {\n\trouter := New()\n\tgo func() {\n\t\trouter.GET(\"/example\", func(c *Context) { c.String(http.StatusOK, \"it worked\") })\n\n\t\tassert.NoError(t, router.RunTLS(\":8443\", \"./testdata/certificate/cert.pem\", \"./testdata/certificate/key.pem\"))\n\t}()\n\n\t// have to wait for the goroutine to start and run the server\n\t// otherwise the main thread will complete\n\ttime.Sleep(5 * time.Millisecond)\n\n\trequire.Error(t, router.RunTLS(\":8443\", \"./testdata/certificate/cert.pem\", \"./testdata/certificate/key.pem\"))\n\ttestRequest(t, \"https://localhost:8443/example\")\n}\n\nfunc TestPusher(t *testing.T) {\n\thtml := template.Must(template.New(\"https\").Parse(`\n<html>\n<head>\n  <title>Https Test</title>\n  <script src=\"/assets/app.js\"></script>\n</head>\n<body>\n  <h1 style=\"color:red;\">Welcome, Ginner!</h1>\n</body>\n</html>\n`))\n\n\trouter := New()\n\trouter.Static(\"./assets\", \"./assets\")\n\trouter.SetHTMLTemplate(html)\n\n\tgo func() {\n\t\trouter.GET(\"/pusher\", func(c *Context) {\n\t\t\tif pusher := c.Writer.Pusher(); pusher != nil {\n\t\t\t\terr := pusher.Push(\"/assets/app.js\", nil)\n\t\t\t\tassert.NoError(t, err)\n\t\t\t}\n\t\t\tc.String(http.StatusOK, \"it worked\")\n\t\t})\n\n\t\tassert.NoError(t, router.RunTLS(\":8449\", \"./testdata/certificate/cert.pem\", \"./testdata/certificate/key.pem\"))\n\t}()\n\n\t// have to wait for the goroutine to start and run the server\n\t// otherwise the main thread will complete\n\ttime.Sleep(5 * time.Millisecond)\n\n\trequire.Error(t, router.RunTLS(\":8449\", \"./testdata/certificate/cert.pem\", \"./testdata/certificate/key.pem\"))\n\ttestRequest(t, \"https://localhost:8449/pusher\")\n}\n\nfunc TestRunEmptyWithEnv(t *testing.T) {\n\tos.Setenv(\"PORT\", \"3123\")\n\trouter := New()\n\tgo func() {\n\t\trouter.GET(\"/example\", func(c *Context) { c.String(http.StatusOK, \"it worked\") })\n\t\tassert.NoError(t, router.Run())\n\t}()\n\n\t// Wait for server to be ready with exponential backoff\n\terr := waitForServerReady(\"http://localhost:3123/example\", 10)\n\trequire.NoError(t, err, \"server should start successfully\")\n\n\trequire.Error(t, router.Run(\":3123\"))\n\ttestRequest(t, \"http://localhost:3123/example\")\n}\n\nfunc TestRunTooMuchParams(t *testing.T) {\n\trouter := New()\n\tassert.Panics(t, func() {\n\t\trequire.NoError(t, router.Run(\"2\", \"2\"))\n\t})\n}\n\nfunc TestRunWithPort(t *testing.T) {\n\trouter := New()\n\tgo func() {\n\t\trouter.GET(\"/example\", func(c *Context) { c.String(http.StatusOK, \"it worked\") })\n\t\tassert.NoError(t, router.Run(\":5150\"))\n\t}()\n\n\t// Wait for server to be ready with exponential backoff\n\terr := waitForServerReady(\"http://localhost:5150/example\", 10)\n\trequire.NoError(t, err, \"server should start successfully\")\n\n\trequire.Error(t, router.Run(\":5150\"))\n\ttestRequest(t, \"http://localhost:5150/example\")\n}\n\nfunc TestUnixSocket(t *testing.T) {\n\trouter := New()\n\n\tunixTestSocket := filepath.Join(os.TempDir(), \"unix_unit_test\")\n\n\tdefer os.Remove(unixTestSocket)\n\n\tgo func() {\n\t\trouter.GET(\"/example\", func(c *Context) { c.String(http.StatusOK, \"it worked\") })\n\t\tassert.NoError(t, router.RunUnix(unixTestSocket))\n\t}()\n\t// have to wait for the goroutine to start and run the server\n\t// otherwise the main thread will complete\n\ttime.Sleep(5 * time.Millisecond)\n\n\tc, err := net.Dial(\"unix\", unixTestSocket)\n\trequire.NoError(t, err)\n\n\tfmt.Fprint(c, \"GET /example HTTP/1.0\\r\\n\\r\\n\")\n\tscanner := bufio.NewScanner(c)\n\tvar responseBuilder strings.Builder\n\tfor scanner.Scan() {\n\t\tresponseBuilder.WriteString(scanner.Text())\n\t}\n\tresponse := responseBuilder.String()\n\tassert.Contains(t, response, \"HTTP/1.0 200\", \"should get a 200\")\n\tassert.Contains(t, response, \"it worked\", \"resp body should match\")\n}\n\nfunc TestBadUnixSocket(t *testing.T) {\n\trouter := New()\n\trequire.Error(t, router.RunUnix(\"#/tmp/unix_unit_test\"))\n}\n\nfunc TestRunQUIC(t *testing.T) {\n\trouter := New()\n\tgo func() {\n\t\trouter.GET(\"/example\", func(c *Context) { c.String(http.StatusOK, \"it worked\") })\n\n\t\tassert.NoError(t, router.RunQUIC(\":8443\", \"./testdata/certificate/cert.pem\", \"./testdata/certificate/key.pem\"))\n\t}()\n\n\t// have to wait for the goroutine to start and run the server\n\t// otherwise the main thread will complete\n\ttime.Sleep(5 * time.Millisecond)\n\n\trequire.Error(t, router.RunQUIC(\":8443\", \"./testdata/certificate/cert.pem\", \"./testdata/certificate/key.pem\"))\n\ttestRequest(t, \"https://localhost:8443/example\")\n}\n\nfunc TestFileDescriptor(t *testing.T) {\n\trouter := New()\n\n\taddr, err := net.ResolveTCPAddr(\"tcp\", \"localhost:0\")\n\trequire.NoError(t, err)\n\tlistener, err := net.ListenTCP(\"tcp\", addr)\n\trequire.NoError(t, err)\n\tsocketFile, err := listener.File()\n\tif isWindows() {\n\t\t// not supported by windows, it is unimplemented now\n\t\trequire.Error(t, err)\n\t} else {\n\t\trequire.NoError(t, err)\n\t}\n\n\tif socketFile == nil {\n\t\treturn\n\t}\n\n\tgo func() {\n\t\trouter.GET(\"/example\", func(c *Context) { c.String(http.StatusOK, \"it worked\") })\n\t\tassert.NoError(t, router.RunFd(int(socketFile.Fd())))\n\t}()\n\t// have to wait for the goroutine to start and run the server\n\t// otherwise the main thread will complete\n\ttime.Sleep(5 * time.Millisecond)\n\n\tc, err := net.Dial(\"tcp\", listener.Addr().String())\n\trequire.NoError(t, err)\n\n\tfmt.Fprintf(c, \"GET /example HTTP/1.0\\r\\n\\r\\n\")\n\tscanner := bufio.NewScanner(c)\n\tvar responseBuilder strings.Builder\n\tfor scanner.Scan() {\n\t\tresponseBuilder.WriteString(scanner.Text())\n\t}\n\tresponse := responseBuilder.String()\n\tassert.Contains(t, response, \"HTTP/1.0 200\", \"should get a 200\")\n\tassert.Contains(t, response, \"it worked\", \"resp body should match\")\n}\n\nfunc TestBadFileDescriptor(t *testing.T) {\n\trouter := New()\n\trequire.Error(t, router.RunFd(0))\n}\n\nfunc TestListener(t *testing.T) {\n\trouter := New()\n\taddr, err := net.ResolveTCPAddr(\"tcp\", \"localhost:0\")\n\trequire.NoError(t, err)\n\tlistener, err := net.ListenTCP(\"tcp\", addr)\n\trequire.NoError(t, err)\n\tgo func() {\n\t\trouter.GET(\"/example\", func(c *Context) { c.String(http.StatusOK, \"it worked\") })\n\t\tassert.NoError(t, router.RunListener(listener))\n\t}()\n\t// have to wait for the goroutine to start and run the server\n\t// otherwise the main thread will complete\n\ttime.Sleep(5 * time.Millisecond)\n\n\tc, err := net.Dial(\"tcp\", listener.Addr().String())\n\trequire.NoError(t, err)\n\n\tfmt.Fprintf(c, \"GET /example HTTP/1.0\\r\\n\\r\\n\")\n\tscanner := bufio.NewScanner(c)\n\tvar responseBuilder strings.Builder\n\tfor scanner.Scan() {\n\t\tresponseBuilder.WriteString(scanner.Text())\n\t}\n\tresponse := responseBuilder.String()\n\tassert.Contains(t, response, \"HTTP/1.0 200\", \"should get a 200\")\n\tassert.Contains(t, response, \"it worked\", \"resp body should match\")\n}\n\nfunc TestBadListener(t *testing.T) {\n\trouter := New()\n\taddr, err := net.ResolveTCPAddr(\"tcp\", \"localhost:10086\")\n\trequire.NoError(t, err)\n\tlistener, err := net.ListenTCP(\"tcp\", addr)\n\trequire.NoError(t, err)\n\tlistener.Close()\n\trequire.Error(t, router.RunListener(listener))\n}\n\nfunc TestWithHttptestWithAutoSelectedPort(t *testing.T) {\n\trouter := New()\n\trouter.GET(\"/example\", func(c *Context) { c.String(http.StatusOK, \"it worked\") })\n\n\tts := httptest.NewServer(router)\n\tdefer ts.Close()\n\n\ttestRequest(t, ts.URL+\"/example\")\n}\n\nfunc TestConcurrentHandleContext(t *testing.T) {\n\trouter := New()\n\trouter.GET(\"/\", func(c *Context) {\n\t\tc.Request.URL.Path = \"/example\"\n\t\trouter.HandleContext(c)\n\t})\n\trouter.GET(\"/example\", func(c *Context) { c.String(http.StatusOK, \"it worked\") })\n\n\tvar wg sync.WaitGroup\n\titerations := 200\n\twg.Add(iterations)\n\tfor range iterations {\n\t\tgo func() {\n\t\t\treq, err := http.NewRequest(http.MethodGet, \"/\", nil)\n\t\t\tassert.NoError(t, err)\n\n\t\t\tw := httptest.NewRecorder()\n\t\t\trouter.ServeHTTP(w, req)\n\n\t\t\tassert.Equal(t, \"it worked\", w.Body.String(), \"resp body should match\")\n\t\t\tassert.Equal(t, 200, w.Code, \"should get a 200\")\n\t\t\twg.Done()\n\t\t}()\n\t}\n\twg.Wait()\n}\n\n// func TestWithHttptestWithSpecifiedPort(t *testing.T) {\n// \trouter := New()\n// \trouter.GET(\"/example\", func(c *Context) { c.String(http.StatusOK, \"it worked\") })\n\n// \tl, _ := net.Listen(\"tcp\", \":8033\")\n// \tts := httptest.Server{\n// \t\tListener: l,\n// \t\tConfig:   &http.Server{Handler: router},\n// \t}\n// \tts.Start()\n// \tdefer ts.Close()\n\n// \ttestRequest(t, \"http://localhost:8033/example\")\n// }\n\nfunc TestTreeRunDynamicRouting(t *testing.T) {\n\trouter := New()\n\trouter.GET(\"/aa/*xx\", func(c *Context) { c.String(http.StatusOK, \"/aa/*xx\") })\n\trouter.GET(\"/ab/*xx\", func(c *Context) { c.String(http.StatusOK, \"/ab/*xx\") })\n\trouter.GET(\"/\", func(c *Context) { c.String(http.StatusOK, \"home\") })\n\trouter.GET(\"/:cc\", func(c *Context) { c.String(http.StatusOK, \"/:cc\") })\n\trouter.GET(\"/c1/:dd/e\", func(c *Context) { c.String(http.StatusOK, \"/c1/:dd/e\") })\n\trouter.GET(\"/c1/:dd/e1\", func(c *Context) { c.String(http.StatusOK, \"/c1/:dd/e1\") })\n\trouter.GET(\"/c1/:dd/f1\", func(c *Context) { c.String(http.StatusOK, \"/c1/:dd/f1\") })\n\trouter.GET(\"/c1/:dd/f2\", func(c *Context) { c.String(http.StatusOK, \"/c1/:dd/f2\") })\n\trouter.GET(\"/:cc/cc\", func(c *Context) { c.String(http.StatusOK, \"/:cc/cc\") })\n\trouter.GET(\"/:cc/:dd/ee\", func(c *Context) { c.String(http.StatusOK, \"/:cc/:dd/ee\") })\n\trouter.GET(\"/:cc/:dd/f\", func(c *Context) { c.String(http.StatusOK, \"/:cc/:dd/f\") })\n\trouter.GET(\"/:cc/:dd/:ee/ff\", func(c *Context) { c.String(http.StatusOK, \"/:cc/:dd/:ee/ff\") })\n\trouter.GET(\"/:cc/:dd/:ee/:ff/gg\", func(c *Context) { c.String(http.StatusOK, \"/:cc/:dd/:ee/:ff/gg\") })\n\trouter.GET(\"/:cc/:dd/:ee/:ff/:gg/hh\", func(c *Context) { c.String(http.StatusOK, \"/:cc/:dd/:ee/:ff/:gg/hh\") })\n\trouter.GET(\"/get/test/abc/\", func(c *Context) { c.String(http.StatusOK, \"/get/test/abc/\") })\n\trouter.GET(\"/get/:param/abc/\", func(c *Context) { c.String(http.StatusOK, \"/get/:param/abc/\") })\n\trouter.GET(\"/something/:paramname/thirdthing\", func(c *Context) { c.String(http.StatusOK, \"/something/:paramname/thirdthing\") })\n\trouter.GET(\"/something/secondthing/test\", func(c *Context) { c.String(http.StatusOK, \"/something/secondthing/test\") })\n\trouter.GET(\"/get/abc\", func(c *Context) { c.String(http.StatusOK, \"/get/abc\") })\n\trouter.GET(\"/get/:param\", func(c *Context) { c.String(http.StatusOK, \"/get/:param\") })\n\trouter.GET(\"/get/abc/123abc\", func(c *Context) { c.String(http.StatusOK, \"/get/abc/123abc\") })\n\trouter.GET(\"/get/abc/:param\", func(c *Context) { c.String(http.StatusOK, \"/get/abc/:param\") })\n\trouter.GET(\"/get/abc/123abc/xxx8\", func(c *Context) { c.String(http.StatusOK, \"/get/abc/123abc/xxx8\") })\n\trouter.GET(\"/get/abc/123abc/:param\", func(c *Context) { c.String(http.StatusOK, \"/get/abc/123abc/:param\") })\n\trouter.GET(\"/get/abc/123abc/xxx8/1234\", func(c *Context) { c.String(http.StatusOK, \"/get/abc/123abc/xxx8/1234\") })\n\trouter.GET(\"/get/abc/123abc/xxx8/:param\", func(c *Context) { c.String(http.StatusOK, \"/get/abc/123abc/xxx8/:param\") })\n\trouter.GET(\"/get/abc/123abc/xxx8/1234/ffas\", func(c *Context) { c.String(http.StatusOK, \"/get/abc/123abc/xxx8/1234/ffas\") })\n\trouter.GET(\"/get/abc/123abc/xxx8/1234/:param\", func(c *Context) { c.String(http.StatusOK, \"/get/abc/123abc/xxx8/1234/:param\") })\n\trouter.GET(\"/get/abc/123abc/xxx8/1234/kkdd/12c\", func(c *Context) { c.String(http.StatusOK, \"/get/abc/123abc/xxx8/1234/kkdd/12c\") })\n\trouter.GET(\"/get/abc/123abc/xxx8/1234/kkdd/:param\", func(c *Context) { c.String(http.StatusOK, \"/get/abc/123abc/xxx8/1234/kkdd/:param\") })\n\trouter.GET(\"/get/abc/:param/test\", func(c *Context) { c.String(http.StatusOK, \"/get/abc/:param/test\") })\n\trouter.GET(\"/get/abc/123abd/:param\", func(c *Context) { c.String(http.StatusOK, \"/get/abc/123abd/:param\") })\n\trouter.GET(\"/get/abc/123abddd/:param\", func(c *Context) { c.String(http.StatusOK, \"/get/abc/123abddd/:param\") })\n\trouter.GET(\"/get/abc/123/:param\", func(c *Context) { c.String(http.StatusOK, \"/get/abc/123/:param\") })\n\trouter.GET(\"/get/abc/123abg/:param\", func(c *Context) { c.String(http.StatusOK, \"/get/abc/123abg/:param\") })\n\trouter.GET(\"/get/abc/123abf/:param\", func(c *Context) { c.String(http.StatusOK, \"/get/abc/123abf/:param\") })\n\trouter.GET(\"/get/abc/123abfff/:param\", func(c *Context) { c.String(http.StatusOK, \"/get/abc/123abfff/:param\") })\n\n\tts := httptest.NewServer(router)\n\tdefer ts.Close()\n\n\ttestRequest(t, ts.URL+\"/\", \"\", \"home\")\n\ttestRequest(t, ts.URL+\"/aa/aa\", \"\", \"/aa/*xx\")\n\ttestRequest(t, ts.URL+\"/ab/ab\", \"\", \"/ab/*xx\")\n\ttestRequest(t, ts.URL+\"/all\", \"\", \"/:cc\")\n\ttestRequest(t, ts.URL+\"/all/cc\", \"\", \"/:cc/cc\")\n\ttestRequest(t, ts.URL+\"/a/cc\", \"\", \"/:cc/cc\")\n\ttestRequest(t, ts.URL+\"/c1/d/e\", \"\", \"/c1/:dd/e\")\n\ttestRequest(t, ts.URL+\"/c1/d/e1\", \"\", \"/c1/:dd/e1\")\n\ttestRequest(t, ts.URL+\"/c1/d/ee\", \"\", \"/:cc/:dd/ee\")\n\ttestRequest(t, ts.URL+\"/c1/d/f\", \"\", \"/:cc/:dd/f\")\n\ttestRequest(t, ts.URL+\"/c/d/ee\", \"\", \"/:cc/:dd/ee\")\n\ttestRequest(t, ts.URL+\"/c/d/e/ff\", \"\", \"/:cc/:dd/:ee/ff\")\n\ttestRequest(t, ts.URL+\"/c/d/e/f/gg\", \"\", \"/:cc/:dd/:ee/:ff/gg\")\n\ttestRequest(t, ts.URL+\"/c/d/e/f/g/hh\", \"\", \"/:cc/:dd/:ee/:ff/:gg/hh\")\n\ttestRequest(t, ts.URL+\"/cc/dd/ee/ff/gg/hh\", \"\", \"/:cc/:dd/:ee/:ff/:gg/hh\")\n\ttestRequest(t, ts.URL+\"/a\", \"\", \"/:cc\")\n\ttestRequest(t, ts.URL+\"/d\", \"\", \"/:cc\")\n\ttestRequest(t, ts.URL+\"/ad\", \"\", \"/:cc\")\n\ttestRequest(t, ts.URL+\"/dd\", \"\", \"/:cc\")\n\ttestRequest(t, ts.URL+\"/aa\", \"\", \"/:cc\")\n\ttestRequest(t, ts.URL+\"/aaa\", \"\", \"/:cc\")\n\ttestRequest(t, ts.URL+\"/aaa/cc\", \"\", \"/:cc/cc\")\n\ttestRequest(t, ts.URL+\"/ab\", \"\", \"/:cc\")\n\ttestRequest(t, ts.URL+\"/abb\", \"\", \"/:cc\")\n\ttestRequest(t, ts.URL+\"/abb/cc\", \"\", \"/:cc/cc\")\n\ttestRequest(t, ts.URL+\"/dddaa\", \"\", \"/:cc\")\n\ttestRequest(t, ts.URL+\"/allxxxx\", \"\", \"/:cc\")\n\ttestRequest(t, ts.URL+\"/alldd\", \"\", \"/:cc\")\n\ttestRequest(t, ts.URL+\"/cc/cc\", \"\", \"/:cc/cc\")\n\ttestRequest(t, ts.URL+\"/ccc/cc\", \"\", \"/:cc/cc\")\n\ttestRequest(t, ts.URL+\"/deedwjfs/cc\", \"\", \"/:cc/cc\")\n\ttestRequest(t, ts.URL+\"/acllcc/cc\", \"\", \"/:cc/cc\")\n\ttestRequest(t, ts.URL+\"/get/test/abc/\", \"\", \"/get/test/abc/\")\n\ttestRequest(t, ts.URL+\"/get/testaa/abc/\", \"\", \"/get/:param/abc/\")\n\ttestRequest(t, ts.URL+\"/get/te/abc/\", \"\", \"/get/:param/abc/\")\n\ttestRequest(t, ts.URL+\"/get/xx/abc/\", \"\", \"/get/:param/abc/\")\n\ttestRequest(t, ts.URL+\"/get/tt/abc/\", \"\", \"/get/:param/abc/\")\n\ttestRequest(t, ts.URL+\"/get/a/abc/\", \"\", \"/get/:param/abc/\")\n\ttestRequest(t, ts.URL+\"/get/t/abc/\", \"\", \"/get/:param/abc/\")\n\ttestRequest(t, ts.URL+\"/get/aa/abc/\", \"\", \"/get/:param/abc/\")\n\ttestRequest(t, ts.URL+\"/get/abas/abc/\", \"\", \"/get/:param/abc/\")\n\ttestRequest(t, ts.URL+\"/something/secondthing/test\", \"\", \"/something/secondthing/test\")\n\ttestRequest(t, ts.URL+\"/something/secondthingaaaa/thirdthing\", \"\", \"/something/:paramname/thirdthing\")\n\ttestRequest(t, ts.URL+\"/something/abcdad/thirdthing\", \"\", \"/something/:paramname/thirdthing\")\n\ttestRequest(t, ts.URL+\"/something/se/thirdthing\", \"\", \"/something/:paramname/thirdthing\")\n\ttestRequest(t, ts.URL+\"/something/s/thirdthing\", \"\", \"/something/:paramname/thirdthing\")\n\ttestRequest(t, ts.URL+\"/something/secondthing/thirdthing\", \"\", \"/something/:paramname/thirdthing\")\n\ttestRequest(t, ts.URL+\"/get/abc\", \"\", \"/get/abc\")\n\ttestRequest(t, ts.URL+\"/get/a\", \"\", \"/get/:param\")\n\ttestRequest(t, ts.URL+\"/get/abz\", \"\", \"/get/:param\")\n\ttestRequest(t, ts.URL+\"/get/12a\", \"\", \"/get/:param\")\n\ttestRequest(t, ts.URL+\"/get/abcd\", \"\", \"/get/:param\")\n\ttestRequest(t, ts.URL+\"/get/abc/123abc\", \"\", \"/get/abc/123abc\")\n\ttestRequest(t, ts.URL+\"/get/abc/12\", \"\", \"/get/abc/:param\")\n\ttestRequest(t, ts.URL+\"/get/abc/123ab\", \"\", \"/get/abc/:param\")\n\ttestRequest(t, ts.URL+\"/get/abc/xyz\", \"\", \"/get/abc/:param\")\n\ttestRequest(t, ts.URL+\"/get/abc/123abcddxx\", \"\", \"/get/abc/:param\")\n\ttestRequest(t, ts.URL+\"/get/abc/123abc/xxx8\", \"\", \"/get/abc/123abc/xxx8\")\n\ttestRequest(t, ts.URL+\"/get/abc/123abc/x\", \"\", \"/get/abc/123abc/:param\")\n\ttestRequest(t, ts.URL+\"/get/abc/123abc/xxx\", \"\", \"/get/abc/123abc/:param\")\n\ttestRequest(t, ts.URL+\"/get/abc/123abc/abc\", \"\", \"/get/abc/123abc/:param\")\n\ttestRequest(t, ts.URL+\"/get/abc/123abc/xxx8xxas\", \"\", \"/get/abc/123abc/:param\")\n\ttestRequest(t, ts.URL+\"/get/abc/123abc/xxx8/1234\", \"\", \"/get/abc/123abc/xxx8/1234\")\n\ttestRequest(t, ts.URL+\"/get/abc/123abc/xxx8/1\", \"\", \"/get/abc/123abc/xxx8/:param\")\n\ttestRequest(t, ts.URL+\"/get/abc/123abc/xxx8/123\", \"\", \"/get/abc/123abc/xxx8/:param\")\n\ttestRequest(t, ts.URL+\"/get/abc/123abc/xxx8/78k\", \"\", \"/get/abc/123abc/xxx8/:param\")\n\ttestRequest(t, ts.URL+\"/get/abc/123abc/xxx8/1234xxxd\", \"\", \"/get/abc/123abc/xxx8/:param\")\n\ttestRequest(t, ts.URL+\"/get/abc/123abc/xxx8/1234/ffas\", \"\", \"/get/abc/123abc/xxx8/1234/ffas\")\n\ttestRequest(t, ts.URL+\"/get/abc/123abc/xxx8/1234/f\", \"\", \"/get/abc/123abc/xxx8/1234/:param\")\n\ttestRequest(t, ts.URL+\"/get/abc/123abc/xxx8/1234/ffa\", \"\", \"/get/abc/123abc/xxx8/1234/:param\")\n\ttestRequest(t, ts.URL+\"/get/abc/123abc/xxx8/1234/kka\", \"\", \"/get/abc/123abc/xxx8/1234/:param\")\n\ttestRequest(t, ts.URL+\"/get/abc/123abc/xxx8/1234/ffas321\", \"\", \"/get/abc/123abc/xxx8/1234/:param\")\n\ttestRequest(t, ts.URL+\"/get/abc/123abc/xxx8/1234/kkdd/12c\", \"\", \"/get/abc/123abc/xxx8/1234/kkdd/12c\")\n\ttestRequest(t, ts.URL+\"/get/abc/123abc/xxx8/1234/kkdd/1\", \"\", \"/get/abc/123abc/xxx8/1234/kkdd/:param\")\n\ttestRequest(t, ts.URL+\"/get/abc/123abc/xxx8/1234/kkdd/12\", \"\", \"/get/abc/123abc/xxx8/1234/kkdd/:param\")\n\ttestRequest(t, ts.URL+\"/get/abc/123abc/xxx8/1234/kkdd/12b\", \"\", \"/get/abc/123abc/xxx8/1234/kkdd/:param\")\n\ttestRequest(t, ts.URL+\"/get/abc/123abc/xxx8/1234/kkdd/34\", \"\", \"/get/abc/123abc/xxx8/1234/kkdd/:param\")\n\ttestRequest(t, ts.URL+\"/get/abc/123abc/xxx8/1234/kkdd/12c2e3\", \"\", \"/get/abc/123abc/xxx8/1234/kkdd/:param\")\n\ttestRequest(t, ts.URL+\"/get/abc/12/test\", \"\", \"/get/abc/:param/test\")\n\ttestRequest(t, ts.URL+\"/get/abc/123abdd/test\", \"\", \"/get/abc/:param/test\")\n\ttestRequest(t, ts.URL+\"/get/abc/123abdddf/test\", \"\", \"/get/abc/:param/test\")\n\ttestRequest(t, ts.URL+\"/get/abc/123ab/test\", \"\", \"/get/abc/:param/test\")\n\ttestRequest(t, ts.URL+\"/get/abc/123abgg/test\", \"\", \"/get/abc/:param/test\")\n\ttestRequest(t, ts.URL+\"/get/abc/123abff/test\", \"\", \"/get/abc/:param/test\")\n\ttestRequest(t, ts.URL+\"/get/abc/123abffff/test\", \"\", \"/get/abc/:param/test\")\n\ttestRequest(t, ts.URL+\"/get/abc/123abd/test\", \"\", \"/get/abc/123abd/:param\")\n\ttestRequest(t, ts.URL+\"/get/abc/123abddd/test\", \"\", \"/get/abc/123abddd/:param\")\n\ttestRequest(t, ts.URL+\"/get/abc/123/test22\", \"\", \"/get/abc/123/:param\")\n\ttestRequest(t, ts.URL+\"/get/abc/123abg/test\", \"\", \"/get/abc/123abg/:param\")\n\ttestRequest(t, ts.URL+\"/get/abc/123abf/testss\", \"\", \"/get/abc/123abf/:param\")\n\ttestRequest(t, ts.URL+\"/get/abc/123abfff/te\", \"\", \"/get/abc/123abfff/:param\")\n\t// 404 not found\n\ttestRequest(t, ts.URL+\"/c/d/e\", \"404 Not Found\")\n\ttestRequest(t, ts.URL+\"/c/d/e1\", \"404 Not Found\")\n\ttestRequest(t, ts.URL+\"/c/d/eee\", \"404 Not Found\")\n\ttestRequest(t, ts.URL+\"/c1/d/eee\", \"404 Not Found\")\n\ttestRequest(t, ts.URL+\"/c1/d/e2\", \"404 Not Found\")\n\ttestRequest(t, ts.URL+\"/cc/dd/ee/ff/gg/hh1\", \"404 Not Found\")\n\ttestRequest(t, ts.URL+\"/a/dd\", \"404 Not Found\")\n\ttestRequest(t, ts.URL+\"/addr/dd/aa\", \"404 Not Found\")\n\ttestRequest(t, ts.URL+\"/something/secondthing/121\", \"404 Not Found\")\n}\n\nfunc isWindows() bool {\n\treturn runtime.GOOS == \"windows\"\n}\n\nfunc TestEscapedColon(t *testing.T) {\n\trouter := New()\n\tf := func(u string) {\n\t\trouter.GET(u, func(c *Context) { c.String(http.StatusOK, u) })\n\t}\n\tf(\"/r/r\\\\:r\")\n\tf(\"/r/r:r\")\n\tf(\"/r/r/:r\")\n\tf(\"/r/r/\\\\:r\")\n\tf(\"/r/r/r\\\\:r\")\n\tassert.Panics(t, func() {\n\t\tf(\"\\\\foo:\")\n\t})\n\n\trouter.updateRouteTrees()\n\tts := httptest.NewServer(router)\n\tdefer ts.Close()\n\n\ttestRequest(t, ts.URL+\"/r/r123\", \"\", \"/r/r:r\")\n\ttestRequest(t, ts.URL+\"/r/r:r\", \"\", \"/r/r\\\\:r\")\n\ttestRequest(t, ts.URL+\"/r/r/r123\", \"\", \"/r/r/:r\")\n\ttestRequest(t, ts.URL+\"/r/r/:r\", \"\", \"/r/r/\\\\:r\")\n\ttestRequest(t, ts.URL+\"/r/r/r:r\", \"\", \"/r/r/r\\\\:r\")\n}\n"
  },
  {
    "path": "gin_test.go",
    "content": "// Copyright 2014 Manu Martinez-Almeida. All rights reserved.\n// Use of this source code is governed by a MIT style\n// license that can be found in the LICENSE file.\n\npackage gin\n\nimport (\n\t\"crypto/tls\"\n\t\"fmt\"\n\t\"html/template\"\n\t\"io\"\n\t\"net\"\n\t\"net/http\"\n\t\"net/http/httptest\"\n\t\"reflect\"\n\t\"strconv\"\n\t\"strings\"\n\t\"sync/atomic\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n\t\"golang.org/x/net/http2\"\n)\n\nfunc formatAsDate(t time.Time) string {\n\tyear, month, day := t.Date()\n\treturn fmt.Sprintf(\"%d/%02d/%02d\", year, month, day)\n}\n\nfunc setupHTMLFiles(t *testing.T, mode string, tls bool, loadMethod func(*Engine)) *httptest.Server {\n\tSetMode(mode)\n\tdefer SetMode(TestMode)\n\n\tvar router *Engine\n\tcaptureOutput(t, func() {\n\t\trouter = New()\n\t\trouter.Delims(\"{[{\", \"}]}\")\n\t\trouter.SetFuncMap(template.FuncMap{\n\t\t\t\"formatAsDate\": formatAsDate,\n\t\t})\n\t\tloadMethod(router)\n\t\trouter.GET(\"/test\", func(c *Context) {\n\t\t\tc.HTML(http.StatusOK, \"hello.tmpl\", map[string]string{\"name\": \"world\"})\n\t\t})\n\t\trouter.GET(\"/raw\", func(c *Context) {\n\t\t\tc.HTML(http.StatusOK, \"raw.tmpl\", map[string]any{\n\t\t\t\t\"now\": time.Date(2017, 07, 01, 0, 0, 0, 0, time.UTC), //nolint:gofumpt\n\t\t\t})\n\t\t})\n\t})\n\n\tvar ts *httptest.Server\n\n\tif tls {\n\t\tts = httptest.NewTLSServer(router)\n\t} else {\n\t\tts = httptest.NewServer(router)\n\t}\n\n\treturn ts\n}\n\nfunc TestLoadHTMLGlobDebugMode(t *testing.T) {\n\tts := setupHTMLFiles(\n\t\tt,\n\t\tDebugMode,\n\t\tfalse,\n\t\tfunc(router *Engine) {\n\t\t\trouter.LoadHTMLGlob(\"./testdata/template/*\")\n\t\t},\n\t)\n\tdefer ts.Close()\n\n\tres, err := http.Get(ts.URL + \"/test\")\n\tif err != nil {\n\t\tt.Error(err)\n\t}\n\n\tresp, _ := io.ReadAll(res.Body)\n\tassert.Equal(t, \"<h1>Hello world</h1>\", string(resp))\n}\n\nfunc TestH2c(t *testing.T) {\n\tln, err := net.Listen(\"tcp\", localhostIP+\":0\")\n\tif err != nil {\n\t\tt.Error(err)\n\t}\n\tr := Default()\n\tr.UseH2C = true\n\tr.GET(\"/\", func(c *Context) {\n\t\tc.String(200, \"<h1>Hello world</h1>\")\n\t})\n\tgo func() {\n\t\terr := http.Serve(ln, r.Handler())\n\t\tif err != nil {\n\t\t\tt.Log(err)\n\t\t}\n\t}()\n\tdefer ln.Close()\n\n\turl := \"http://\" + ln.Addr().String() + \"/\"\n\n\thttpClient := http.Client{\n\t\tTransport: &http2.Transport{\n\t\t\tAllowHTTP: true,\n\t\t\tDialTLS: func(netw, addr string, cfg *tls.Config) (net.Conn, error) {\n\t\t\t\treturn net.Dial(netw, addr)\n\t\t\t},\n\t\t},\n\t}\n\n\tres, err := httpClient.Get(url)\n\tif err != nil {\n\t\tt.Error(err)\n\t}\n\n\tresp, _ := io.ReadAll(res.Body)\n\tassert.Equal(t, \"<h1>Hello world</h1>\", string(resp))\n}\n\nfunc TestLoadHTMLGlobTestMode(t *testing.T) {\n\tts := setupHTMLFiles(\n\t\tt,\n\t\tTestMode,\n\t\tfalse,\n\t\tfunc(router *Engine) {\n\t\t\trouter.LoadHTMLGlob(\"./testdata/template/*\")\n\t\t},\n\t)\n\tdefer ts.Close()\n\n\tres, err := http.Get(ts.URL + \"/test\")\n\tif err != nil {\n\t\tt.Error(err)\n\t}\n\n\tresp, _ := io.ReadAll(res.Body)\n\tassert.Equal(t, \"<h1>Hello world</h1>\", string(resp))\n}\n\nfunc TestLoadHTMLGlobReleaseMode(t *testing.T) {\n\tts := setupHTMLFiles(\n\t\tt,\n\t\tReleaseMode,\n\t\tfalse,\n\t\tfunc(router *Engine) {\n\t\t\trouter.LoadHTMLGlob(\"./testdata/template/*\")\n\t\t},\n\t)\n\tdefer ts.Close()\n\n\tres, err := http.Get(ts.URL + \"/test\")\n\tif err != nil {\n\t\tt.Error(err)\n\t}\n\n\tresp, _ := io.ReadAll(res.Body)\n\tassert.Equal(t, \"<h1>Hello world</h1>\", string(resp))\n}\n\nfunc TestLoadHTMLGlobUsingTLS(t *testing.T) {\n\tts := setupHTMLFiles(\n\t\tt,\n\t\tDebugMode,\n\t\ttrue,\n\t\tfunc(router *Engine) {\n\t\t\trouter.LoadHTMLGlob(\"./testdata/template/*\")\n\t\t},\n\t)\n\tdefer ts.Close()\n\n\t// Use InsecureSkipVerify for avoiding `x509: certificate signed by unknown authority` error\n\ttr := &http.Transport{\n\t\tTLSClientConfig: &tls.Config{\n\t\t\tInsecureSkipVerify: true,\n\t\t},\n\t}\n\tclient := &http.Client{Transport: tr}\n\tres, err := client.Get(ts.URL + \"/test\")\n\tif err != nil {\n\t\tt.Error(err)\n\t}\n\n\tresp, _ := io.ReadAll(res.Body)\n\tassert.Equal(t, \"<h1>Hello world</h1>\", string(resp))\n}\n\nfunc TestLoadHTMLGlobFromFuncMap(t *testing.T) {\n\tts := setupHTMLFiles(\n\t\tt,\n\t\tDebugMode,\n\t\tfalse,\n\t\tfunc(router *Engine) {\n\t\t\trouter.LoadHTMLGlob(\"./testdata/template/*\")\n\t\t},\n\t)\n\tdefer ts.Close()\n\n\tres, err := http.Get(ts.URL + \"/raw\")\n\tif err != nil {\n\t\tt.Error(err)\n\t}\n\n\tresp, _ := io.ReadAll(res.Body)\n\tassert.Equal(t, \"Date: 2017/07/01\", string(resp))\n}\n\nfunc init() {\n\tSetMode(TestMode)\n}\n\nfunc TestCreateEngine(t *testing.T) {\n\trouter := New()\n\tassert.Equal(t, \"/\", router.basePath)\n\tassert.Equal(t, router.engine, router)\n\tassert.Empty(t, router.Handlers)\n}\n\nfunc TestLoadHTMLFilesTestMode(t *testing.T) {\n\tts := setupHTMLFiles(\n\t\tt,\n\t\tTestMode,\n\t\tfalse,\n\t\tfunc(router *Engine) {\n\t\t\trouter.LoadHTMLFiles(\"./testdata/template/hello.tmpl\", \"./testdata/template/raw.tmpl\")\n\t\t},\n\t)\n\tdefer ts.Close()\n\n\tres, err := http.Get(ts.URL + \"/test\")\n\tif err != nil {\n\t\tt.Error(err)\n\t}\n\n\tresp, _ := io.ReadAll(res.Body)\n\tassert.Equal(t, \"<h1>Hello world</h1>\", string(resp))\n}\n\nfunc TestLoadHTMLFilesDebugMode(t *testing.T) {\n\tts := setupHTMLFiles(\n\t\tt,\n\t\tDebugMode,\n\t\tfalse,\n\t\tfunc(router *Engine) {\n\t\t\trouter.LoadHTMLFiles(\"./testdata/template/hello.tmpl\", \"./testdata/template/raw.tmpl\")\n\t\t},\n\t)\n\tdefer ts.Close()\n\n\tres, err := http.Get(ts.URL + \"/test\")\n\tif err != nil {\n\t\tt.Error(err)\n\t}\n\n\tresp, _ := io.ReadAll(res.Body)\n\tassert.Equal(t, \"<h1>Hello world</h1>\", string(resp))\n}\n\nfunc TestLoadHTMLFilesReleaseMode(t *testing.T) {\n\tts := setupHTMLFiles(\n\t\tt,\n\t\tReleaseMode,\n\t\tfalse,\n\t\tfunc(router *Engine) {\n\t\t\trouter.LoadHTMLFiles(\"./testdata/template/hello.tmpl\", \"./testdata/template/raw.tmpl\")\n\t\t},\n\t)\n\tdefer ts.Close()\n\n\tres, err := http.Get(ts.URL + \"/test\")\n\tif err != nil {\n\t\tt.Error(err)\n\t}\n\n\tresp, _ := io.ReadAll(res.Body)\n\tassert.Equal(t, \"<h1>Hello world</h1>\", string(resp))\n}\n\nfunc TestLoadHTMLFilesUsingTLS(t *testing.T) {\n\tts := setupHTMLFiles(\n\t\tt,\n\t\tTestMode,\n\t\ttrue,\n\t\tfunc(router *Engine) {\n\t\t\trouter.LoadHTMLFiles(\"./testdata/template/hello.tmpl\", \"./testdata/template/raw.tmpl\")\n\t\t},\n\t)\n\tdefer ts.Close()\n\n\t// Use InsecureSkipVerify for avoiding `x509: certificate signed by unknown authority` error\n\ttr := &http.Transport{\n\t\tTLSClientConfig: &tls.Config{\n\t\t\tInsecureSkipVerify: true,\n\t\t},\n\t}\n\tclient := &http.Client{Transport: tr}\n\tres, err := client.Get(ts.URL + \"/test\")\n\tif err != nil {\n\t\tt.Error(err)\n\t}\n\n\tresp, _ := io.ReadAll(res.Body)\n\tassert.Equal(t, \"<h1>Hello world</h1>\", string(resp))\n}\n\nfunc TestLoadHTMLFilesFuncMap(t *testing.T) {\n\tts := setupHTMLFiles(\n\t\tt,\n\t\tTestMode,\n\t\tfalse,\n\t\tfunc(router *Engine) {\n\t\t\trouter.LoadHTMLFiles(\"./testdata/template/hello.tmpl\", \"./testdata/template/raw.tmpl\")\n\t\t},\n\t)\n\tdefer ts.Close()\n\n\tres, err := http.Get(ts.URL + \"/raw\")\n\tif err != nil {\n\t\tt.Error(err)\n\t}\n\n\tresp, _ := io.ReadAll(res.Body)\n\tassert.Equal(t, \"Date: 2017/07/01\", string(resp))\n}\n\nvar tmplFS = http.Dir(\"testdata/template\")\n\nfunc TestLoadHTMLFSTestMode(t *testing.T) {\n\tts := setupHTMLFiles(\n\t\tt,\n\t\tTestMode,\n\t\tfalse,\n\t\tfunc(router *Engine) {\n\t\t\trouter.LoadHTMLFS(tmplFS, \"hello.tmpl\", \"raw.tmpl\")\n\t\t},\n\t)\n\tdefer ts.Close()\n\n\tres, err := http.Get(ts.URL + \"/test\")\n\tif err != nil {\n\t\tt.Error(err)\n\t}\n\n\tresp, _ := io.ReadAll(res.Body)\n\tassert.Equal(t, \"<h1>Hello world</h1>\", string(resp))\n}\n\nfunc TestLoadHTMLFSDebugMode(t *testing.T) {\n\tts := setupHTMLFiles(\n\t\tt,\n\t\tDebugMode,\n\t\tfalse,\n\t\tfunc(router *Engine) {\n\t\t\trouter.LoadHTMLFS(tmplFS, \"hello.tmpl\", \"raw.tmpl\")\n\t\t},\n\t)\n\tdefer ts.Close()\n\n\tres, err := http.Get(ts.URL + \"/test\")\n\tif err != nil {\n\t\tt.Error(err)\n\t}\n\n\tresp, _ := io.ReadAll(res.Body)\n\tassert.Equal(t, \"<h1>Hello world</h1>\", string(resp))\n}\n\nfunc TestLoadHTMLFSReleaseMode(t *testing.T) {\n\tts := setupHTMLFiles(\n\t\tt,\n\t\tReleaseMode,\n\t\tfalse,\n\t\tfunc(router *Engine) {\n\t\t\trouter.LoadHTMLFS(tmplFS, \"hello.tmpl\", \"raw.tmpl\")\n\t\t},\n\t)\n\tdefer ts.Close()\n\n\tres, err := http.Get(ts.URL + \"/test\")\n\tif err != nil {\n\t\tt.Error(err)\n\t}\n\n\tresp, _ := io.ReadAll(res.Body)\n\tassert.Equal(t, \"<h1>Hello world</h1>\", string(resp))\n}\n\nfunc TestLoadHTMLFSUsingTLS(t *testing.T) {\n\tts := setupHTMLFiles(\n\t\tt,\n\t\tTestMode,\n\t\ttrue,\n\t\tfunc(router *Engine) {\n\t\t\trouter.LoadHTMLFS(tmplFS, \"hello.tmpl\", \"raw.tmpl\")\n\t\t},\n\t)\n\tdefer ts.Close()\n\n\t// Use InsecureSkipVerify for avoiding `x509: certificate signed by unknown authority` error\n\ttr := &http.Transport{\n\t\tTLSClientConfig: &tls.Config{\n\t\t\tInsecureSkipVerify: true,\n\t\t},\n\t}\n\tclient := &http.Client{Transport: tr}\n\tres, err := client.Get(ts.URL + \"/test\")\n\tif err != nil {\n\t\tt.Error(err)\n\t}\n\n\tresp, _ := io.ReadAll(res.Body)\n\tassert.Equal(t, \"<h1>Hello world</h1>\", string(resp))\n}\n\nfunc TestLoadHTMLFSFuncMap(t *testing.T) {\n\tts := setupHTMLFiles(\n\t\tt,\n\t\tTestMode,\n\t\tfalse,\n\t\tfunc(router *Engine) {\n\t\t\trouter.LoadHTMLFS(tmplFS, \"hello.tmpl\", \"raw.tmpl\")\n\t\t},\n\t)\n\tdefer ts.Close()\n\n\tres, err := http.Get(ts.URL + \"/raw\")\n\tif err != nil {\n\t\tt.Error(err)\n\t}\n\n\tresp, _ := io.ReadAll(res.Body)\n\tassert.Equal(t, \"Date: 2017/07/01\", string(resp))\n}\n\nfunc TestAddRoute(t *testing.T) {\n\trouter := New()\n\trouter.addRoute(http.MethodGet, \"/\", HandlersChain{func(_ *Context) {}})\n\n\tassert.Len(t, router.trees, 1)\n\tassert.NotNil(t, router.trees.get(http.MethodGet))\n\tassert.Nil(t, router.trees.get(http.MethodPost))\n\n\trouter.addRoute(http.MethodPost, \"/\", HandlersChain{func(_ *Context) {}})\n\n\tassert.Len(t, router.trees, 2)\n\tassert.NotNil(t, router.trees.get(http.MethodGet))\n\tassert.NotNil(t, router.trees.get(http.MethodPost))\n\n\trouter.addRoute(http.MethodPost, \"/post\", HandlersChain{func(_ *Context) {}})\n\tassert.Len(t, router.trees, 2)\n}\n\nfunc TestAddRouteFails(t *testing.T) {\n\trouter := New()\n\tassert.Panics(t, func() { router.addRoute(\"\", \"/\", HandlersChain{func(_ *Context) {}}) })\n\tassert.Panics(t, func() { router.addRoute(http.MethodGet, \"a\", HandlersChain{func(_ *Context) {}}) })\n\tassert.Panics(t, func() { router.addRoute(http.MethodGet, \"/\", HandlersChain{}) })\n\n\trouter.addRoute(http.MethodPost, \"/post\", HandlersChain{func(_ *Context) {}})\n\tassert.Panics(t, func() {\n\t\trouter.addRoute(http.MethodPost, \"/post\", HandlersChain{func(_ *Context) {}})\n\t})\n}\n\nfunc TestCreateDefaultRouter(t *testing.T) {\n\trouter := Default()\n\tassert.Len(t, router.Handlers, 2)\n}\n\nfunc TestNoRouteWithoutGlobalHandlers(t *testing.T) {\n\tvar middleware0 HandlerFunc = func(c *Context) {}\n\tvar middleware1 HandlerFunc = func(c *Context) {}\n\n\trouter := New()\n\n\trouter.NoRoute(middleware0)\n\tassert.Nil(t, router.Handlers)\n\tassert.Len(t, router.noRoute, 1)\n\tassert.Len(t, router.allNoRoute, 1)\n\tcompareFunc(t, router.noRoute[0], middleware0)\n\tcompareFunc(t, router.allNoRoute[0], middleware0)\n\n\trouter.NoRoute(middleware1, middleware0)\n\tassert.Len(t, router.noRoute, 2)\n\tassert.Len(t, router.allNoRoute, 2)\n\tcompareFunc(t, router.noRoute[0], middleware1)\n\tcompareFunc(t, router.allNoRoute[0], middleware1)\n\tcompareFunc(t, router.noRoute[1], middleware0)\n\tcompareFunc(t, router.allNoRoute[1], middleware0)\n}\n\nfunc TestNoRouteWithGlobalHandlers(t *testing.T) {\n\tvar middleware0 HandlerFunc = func(c *Context) {}\n\tvar middleware1 HandlerFunc = func(c *Context) {}\n\tvar middleware2 HandlerFunc = func(c *Context) {}\n\n\trouter := New()\n\trouter.Use(middleware2)\n\n\trouter.NoRoute(middleware0)\n\tassert.Len(t, router.allNoRoute, 2)\n\tassert.Len(t, router.Handlers, 1)\n\tassert.Len(t, router.noRoute, 1)\n\n\tcompareFunc(t, router.Handlers[0], middleware2)\n\tcompareFunc(t, router.noRoute[0], middleware0)\n\tcompareFunc(t, router.allNoRoute[0], middleware2)\n\tcompareFunc(t, router.allNoRoute[1], middleware0)\n\n\trouter.Use(middleware1)\n\tassert.Len(t, router.allNoRoute, 3)\n\tassert.Len(t, router.Handlers, 2)\n\tassert.Len(t, router.noRoute, 1)\n\n\tcompareFunc(t, router.Handlers[0], middleware2)\n\tcompareFunc(t, router.Handlers[1], middleware1)\n\tcompareFunc(t, router.noRoute[0], middleware0)\n\tcompareFunc(t, router.allNoRoute[0], middleware2)\n\tcompareFunc(t, router.allNoRoute[1], middleware1)\n\tcompareFunc(t, router.allNoRoute[2], middleware0)\n}\n\nfunc TestNoMethodWithoutGlobalHandlers(t *testing.T) {\n\tvar middleware0 HandlerFunc = func(c *Context) {}\n\tvar middleware1 HandlerFunc = func(c *Context) {}\n\n\trouter := New()\n\n\trouter.NoMethod(middleware0)\n\tassert.Empty(t, router.Handlers)\n\tassert.Len(t, router.noMethod, 1)\n\tassert.Len(t, router.allNoMethod, 1)\n\tcompareFunc(t, router.noMethod[0], middleware0)\n\tcompareFunc(t, router.allNoMethod[0], middleware0)\n\n\trouter.NoMethod(middleware1, middleware0)\n\tassert.Len(t, router.noMethod, 2)\n\tassert.Len(t, router.allNoMethod, 2)\n\tcompareFunc(t, router.noMethod[0], middleware1)\n\tcompareFunc(t, router.allNoMethod[0], middleware1)\n\tcompareFunc(t, router.noMethod[1], middleware0)\n\tcompareFunc(t, router.allNoMethod[1], middleware0)\n}\n\nfunc TestRebuild404Handlers(t *testing.T) {\n\tvar middleware0 HandlerFunc = func(c *Context) {}\n\tvar middleware1 HandlerFunc = func(c *Context) {}\n\n\trouter := New()\n\n\t// Initially, allNoRoute should be nil\n\tassert.Nil(t, router.allNoRoute)\n\n\t// Set NoRoute handlers\n\trouter.NoRoute(middleware0)\n\tassert.Len(t, router.allNoRoute, 1)\n\tassert.Len(t, router.noRoute, 1)\n\tcompareFunc(t, router.allNoRoute[0], middleware0)\n\n\t// Add Use middleware should trigger rebuild404Handlers\n\trouter.Use(middleware1)\n\tassert.Len(t, router.allNoRoute, 2)\n\tassert.Len(t, router.Handlers, 1)\n\tassert.Len(t, router.noRoute, 1)\n\n\t// Global middleware should come first\n\tcompareFunc(t, router.allNoRoute[0], middleware1)\n\tcompareFunc(t, router.allNoRoute[1], middleware0)\n}\n\nfunc TestNoMethodWithGlobalHandlers(t *testing.T) {\n\tvar middleware0 HandlerFunc = func(c *Context) {}\n\tvar middleware1 HandlerFunc = func(c *Context) {}\n\tvar middleware2 HandlerFunc = func(c *Context) {}\n\n\trouter := New()\n\trouter.Use(middleware2)\n\n\trouter.NoMethod(middleware0)\n\tassert.Len(t, router.allNoMethod, 2)\n\tassert.Len(t, router.Handlers, 1)\n\tassert.Len(t, router.noMethod, 1)\n\n\tcompareFunc(t, router.Handlers[0], middleware2)\n\tcompareFunc(t, router.noMethod[0], middleware0)\n\tcompareFunc(t, router.allNoMethod[0], middleware2)\n\tcompareFunc(t, router.allNoMethod[1], middleware0)\n\n\trouter.Use(middleware1)\n\tassert.Len(t, router.allNoMethod, 3)\n\tassert.Len(t, router.Handlers, 2)\n\tassert.Len(t, router.noMethod, 1)\n\n\tcompareFunc(t, router.Handlers[0], middleware2)\n\tcompareFunc(t, router.Handlers[1], middleware1)\n\tcompareFunc(t, router.noMethod[0], middleware0)\n\tcompareFunc(t, router.allNoMethod[0], middleware2)\n\tcompareFunc(t, router.allNoMethod[1], middleware1)\n\tcompareFunc(t, router.allNoMethod[2], middleware0)\n}\n\nfunc compareFunc(t *testing.T, a, b any) {\n\tsf1 := reflect.ValueOf(a)\n\tsf2 := reflect.ValueOf(b)\n\tif sf1.Pointer() != sf2.Pointer() {\n\t\tt.Error(\"different functions\")\n\t}\n}\n\nfunc TestListOfRoutes(t *testing.T) {\n\trouter := New()\n\trouter.GET(\"/favicon.ico\", handlerTest1)\n\trouter.GET(\"/\", handlerTest1)\n\tgroup := router.Group(\"/users\")\n\t{\n\t\tgroup.GET(\"/\", handlerTest2)\n\t\tgroup.GET(\"/:id\", handlerTest1)\n\t\tgroup.POST(\"/:id\", handlerTest2)\n\t}\n\trouter.Static(\"/static\", \".\")\n\n\tlist := router.Routes()\n\n\tassert.Len(t, list, 7)\n\tassertRoutePresent(t, list, RouteInfo{\n\t\tMethod:  http.MethodGet,\n\t\tPath:    \"/favicon.ico\",\n\t\tHandler: \"^(.*/vendor/)?github.com/gin-gonic/gin.handlerTest1$\",\n\t})\n\tassertRoutePresent(t, list, RouteInfo{\n\t\tMethod:  http.MethodGet,\n\t\tPath:    \"/\",\n\t\tHandler: \"^(.*/vendor/)?github.com/gin-gonic/gin.handlerTest1$\",\n\t})\n\tassertRoutePresent(t, list, RouteInfo{\n\t\tMethod:  http.MethodGet,\n\t\tPath:    \"/users/\",\n\t\tHandler: \"^(.*/vendor/)?github.com/gin-gonic/gin.handlerTest2$\",\n\t})\n\tassertRoutePresent(t, list, RouteInfo{\n\t\tMethod:  http.MethodGet,\n\t\tPath:    \"/users/:id\",\n\t\tHandler: \"^(.*/vendor/)?github.com/gin-gonic/gin.handlerTest1$\",\n\t})\n\tassertRoutePresent(t, list, RouteInfo{\n\t\tMethod:  http.MethodPost,\n\t\tPath:    \"/users/:id\",\n\t\tHandler: \"^(.*/vendor/)?github.com/gin-gonic/gin.handlerTest2$\",\n\t})\n}\n\nfunc TestEngineHandleContext(t *testing.T) {\n\tr := New()\n\tr.GET(\"/\", func(c *Context) {\n\t\tc.Request.URL.Path = \"/v2\"\n\t\tr.HandleContext(c)\n\t})\n\tv2 := r.Group(\"/v2\")\n\t{\n\t\tv2.GET(\"/\", func(c *Context) {})\n\t}\n\n\tassert.NotPanics(t, func() {\n\t\tw := PerformRequest(r, http.MethodGet, \"/\")\n\t\tassert.Equal(t, 301, w.Code)\n\t})\n}\n\nfunc TestEngineHandleContextManyReEntries(t *testing.T) {\n\texpectValue := 10000\n\n\tvar handlerCounter, middlewareCounter int64\n\n\tr := New()\n\tr.Use(func(c *Context) {\n\t\tatomic.AddInt64(&middlewareCounter, 1)\n\t})\n\tr.GET(\"/:count\", func(c *Context) {\n\t\tcountStr := c.Param(\"count\")\n\t\tcount, err := strconv.Atoi(countStr)\n\t\trequire.NoError(t, err)\n\n\t\tn, err := c.Writer.Write([]byte(\".\"))\n\t\trequire.NoError(t, err)\n\t\tassert.Equal(t, 1, n)\n\n\t\tswitch {\n\t\tcase count > 0:\n\t\t\tc.Request.URL.Path = \"/\" + strconv.Itoa(count-1)\n\t\t\tr.HandleContext(c)\n\t\t}\n\t}, func(c *Context) {\n\t\tatomic.AddInt64(&handlerCounter, 1)\n\t})\n\n\tassert.NotPanics(t, func() {\n\t\tw := PerformRequest(r, http.MethodGet, \"/\"+strconv.Itoa(expectValue-1)) // include 0 value\n\t\tassert.Equal(t, 200, w.Code)\n\t\tassert.Equal(t, expectValue, w.Body.Len())\n\t})\n\n\tassert.Equal(t, int64(expectValue), handlerCounter)\n\tassert.Equal(t, int64(expectValue), middlewareCounter)\n}\n\nfunc TestEngineHandleContextPreventsMiddlewareReEntry(t *testing.T) {\n\t// given\n\tvar handlerCounterV1, handlerCounterV2, middlewareCounterV1 int64\n\n\tr := New()\n\tv1 := r.Group(\"/v1\")\n\t{\n\t\tv1.Use(func(c *Context) {\n\t\t\tatomic.AddInt64(&middlewareCounterV1, 1)\n\t\t})\n\t\tv1.GET(\"/test\", func(c *Context) {\n\t\t\tatomic.AddInt64(&handlerCounterV1, 1)\n\t\t\tc.Status(http.StatusOK)\n\t\t})\n\t}\n\n\tv2 := r.Group(\"/v2\")\n\t{\n\t\tv2.GET(\"/test\", func(c *Context) {\n\t\t\tc.Request.URL.Path = \"/v1/test\"\n\t\t\tr.HandleContext(c)\n\t\t}, func(c *Context) {\n\t\t\tatomic.AddInt64(&handlerCounterV2, 1)\n\t\t})\n\t}\n\n\t// when\n\tresponseV1 := PerformRequest(r, \"GET\", \"/v1/test\")\n\tresponseV2 := PerformRequest(r, \"GET\", \"/v2/test\")\n\n\t// then\n\tassert.Equal(t, 200, responseV1.Code)\n\tassert.Equal(t, 200, responseV2.Code)\n\tassert.Equal(t, int64(2), handlerCounterV1)\n\tassert.Equal(t, int64(2), middlewareCounterV1)\n\tassert.Equal(t, int64(1), handlerCounterV2)\n}\n\nfunc TestEngineHandleContextNoRouteWithGroupMiddleware(t *testing.T) {\n\t// Scenario from issue #1848:\n\t// - Engine with no global middleware (gin.New())\n\t// - A group with middleware\n\t// - A route in that group\n\t// - NoRoute handler that redirects via HandleContext\n\t// The group middleware should run exactly once per HandleContext call,\n\t// not accumulate across redirects.\n\n\tvar middlewareCount, handlerCount int64\n\n\tr := New()\n\tgrp := r.Group(\"\", func(c *Context) {\n\t\tatomic.AddInt64(&middlewareCount, 1)\n\t\tc.Next()\n\t})\n\tgrp.GET(\"/target\", func(c *Context) {\n\t\tatomic.AddInt64(&handlerCount, 1)\n\t\tc.String(http.StatusOK, \"ok\")\n\t})\n\n\tr.NoRoute(func(c *Context) {\n\t\tc.Request.URL.Path = \"/target\"\n\t\tr.HandleContext(c)\n\t})\n\n\t// Access a non-existent route to trigger NoRoute -> HandleContext\n\tw := PerformRequest(r, \"GET\", \"/nonexistent\")\n\tassert.Equal(t, http.StatusOK, w.Code)\n\tassert.Equal(t, \"ok\", w.Body.String())\n\t// Middleware and handler should each run exactly once\n\tassert.Equal(t, int64(1), atomic.LoadInt64(&middlewareCount))\n\tassert.Equal(t, int64(1), atomic.LoadInt64(&handlerCount))\n}\n\nfunc TestEngineHandleContextNoRouteWithEngineMiddleware(t *testing.T) {\n\t// When engine middleware exists and NoRoute redirects via HandleContext,\n\t// verify the handlers run the expected number of times.\n\n\tvar engineMwCount, groupMwCount, handlerCount int64\n\n\tr := New()\n\tr.Use(func(c *Context) {\n\t\tatomic.AddInt64(&engineMwCount, 1)\n\t\tc.Next()\n\t})\n\n\tgrp := r.Group(\"\", func(c *Context) {\n\t\tatomic.AddInt64(&groupMwCount, 1)\n\t\tc.Next()\n\t})\n\tgrp.GET(\"/target\", func(c *Context) {\n\t\tatomic.AddInt64(&handlerCount, 1)\n\t\tc.String(http.StatusOK, \"ok\")\n\t})\n\n\tr.NoRoute(func(c *Context) {\n\t\tc.Request.URL.Path = \"/target\"\n\t\tr.HandleContext(c)\n\t})\n\n\tw := PerformRequest(r, \"GET\", \"/nonexistent\")\n\tassert.Equal(t, http.StatusOK, w.Code)\n\tassert.Equal(t, \"ok\", w.Body.String())\n\t// Handler and group middleware should each run once (from HandleContext)\n\tassert.Equal(t, int64(1), atomic.LoadInt64(&handlerCount))\n\tassert.Equal(t, int64(1), atomic.LoadInt64(&groupMwCount))\n\t// Engine middleware runs twice: once for the NoRoute chain, once for the HandleContext chain\n\t// This is expected behavior since HandleContext re-enters the full handler chain\n\tassert.Equal(t, int64(2), atomic.LoadInt64(&engineMwCount))\n}\n\nfunc TestEngineHandleContextUseEscapedPathPercentEncoded(t *testing.T) {\n\tr := New()\n\tr.UseEscapedPath = true\n\tr.UnescapePathValues = false\n\n\tr.GET(\"/v1/:path\", func(c *Context) {\n\t\t// Path is Escaped, the %25 is not interpreted as %\n\t\tassert.Equal(t, \"foo%252Fbar\", c.Param(\"path\"))\n\t\tc.Status(http.StatusOK)\n\t})\n\n\treq := httptest.NewRequest(http.MethodGet, \"/v1/foo%252Fbar\", nil)\n\tw := httptest.NewRecorder()\n\tr.ServeHTTP(w, req)\n}\n\nfunc TestEngineHandleContextUseRawPathPercentEncoded(t *testing.T) {\n\tr := New()\n\tr.UseRawPath = true\n\tr.UnescapePathValues = false\n\n\tr.GET(\"/v1/:path\", func(c *Context) {\n\t\t// Path is used, the %25 is interpreted as %\n\t\tassert.Equal(t, \"foo%2Fbar\", c.Param(\"path\"))\n\t\tc.Status(http.StatusOK)\n\t})\n\n\treq := httptest.NewRequest(http.MethodGet, \"/v1/foo%252Fbar\", nil)\n\tw := httptest.NewRecorder()\n\tr.ServeHTTP(w, req)\n}\n\nfunc TestEngineHandleContextUseEscapedPathOverride(t *testing.T) {\n\tr := New()\n\tr.UseEscapedPath = true\n\tr.UseRawPath = true\n\tr.UnescapePathValues = false\n\n\tr.GET(\"/v1/:path\", func(c *Context) {\n\t\tassert.Equal(t, \"foo%25bar\", c.Param(\"path\"))\n\t\tc.Status(http.StatusOK)\n\t})\n\n\tassert.NotPanics(t, func() {\n\t\tw := PerformRequest(r, http.MethodGet, \"/v1/foo%25bar\")\n\t\tassert.Equal(t, 200, w.Code)\n\t})\n}\n\nfunc TestPrepareTrustedCIRDsWith(t *testing.T) {\n\tr := New()\n\n\t// valid ipv4 cidr\n\t{\n\t\texpectedTrustedCIDRs := []*net.IPNet{parseCIDR(\"0.0.0.0/0\")}\n\t\terr := r.SetTrustedProxies([]string{\"0.0.0.0/0\"})\n\n\t\trequire.NoError(t, err)\n\t\tassert.Equal(t, expectedTrustedCIDRs, r.trustedCIDRs)\n\t}\n\n\t// invalid ipv4 cidr\n\t{\n\t\terr := r.SetTrustedProxies([]string{\"192.168.1.33/33\"})\n\n\t\trequire.Error(t, err)\n\t}\n\n\t// valid ipv4 address\n\t{\n\t\texpectedTrustedCIDRs := []*net.IPNet{parseCIDR(\"192.168.1.33/32\")}\n\n\t\terr := r.SetTrustedProxies([]string{\"192.168.1.33\"})\n\n\t\trequire.NoError(t, err)\n\t\tassert.Equal(t, expectedTrustedCIDRs, r.trustedCIDRs)\n\t}\n\n\t// invalid ipv4 address\n\t{\n\t\terr := r.SetTrustedProxies([]string{\"192.168.1.256\"})\n\n\t\trequire.Error(t, err)\n\t}\n\n\t// valid ipv6 address\n\t{\n\t\texpectedTrustedCIDRs := []*net.IPNet{parseCIDR(\"2002:0000:0000:1234:abcd:ffff:c0a8:0101/128\")}\n\t\terr := r.SetTrustedProxies([]string{\"2002:0000:0000:1234:abcd:ffff:c0a8:0101\"})\n\n\t\trequire.NoError(t, err)\n\t\tassert.Equal(t, expectedTrustedCIDRs, r.trustedCIDRs)\n\t}\n\n\t// invalid ipv6 address\n\t{\n\t\terr := r.SetTrustedProxies([]string{\"gggg:0000:0000:1234:abcd:ffff:c0a8:0101\"})\n\n\t\trequire.Error(t, err)\n\t}\n\n\t// valid ipv6 cidr\n\t{\n\t\texpectedTrustedCIDRs := []*net.IPNet{parseCIDR(\"::/0\")}\n\t\terr := r.SetTrustedProxies([]string{\"::/0\"})\n\n\t\trequire.NoError(t, err)\n\t\tassert.Equal(t, expectedTrustedCIDRs, r.trustedCIDRs)\n\t}\n\n\t// invalid ipv6 cidr\n\t{\n\t\terr := r.SetTrustedProxies([]string{\"gggg:0000:0000:1234:abcd:ffff:c0a8:0101/129\"})\n\n\t\trequire.Error(t, err)\n\t}\n\n\t// valid combination\n\t{\n\t\texpectedTrustedCIDRs := []*net.IPNet{\n\t\t\tparseCIDR(\"::/0\"),\n\t\t\tparseCIDR(\"192.168.0.0/16\"),\n\t\t\tparseCIDR(\"172.16.0.1/32\"),\n\t\t}\n\t\terr := r.SetTrustedProxies([]string{\n\t\t\t\"::/0\",\n\t\t\t\"192.168.0.0/16\",\n\t\t\t\"172.16.0.1\",\n\t\t})\n\n\t\trequire.NoError(t, err)\n\t\tassert.Equal(t, expectedTrustedCIDRs, r.trustedCIDRs)\n\t}\n\n\t// invalid combination\n\t{\n\t\terr := r.SetTrustedProxies([]string{\n\t\t\t\"::/0\",\n\t\t\t\"192.168.0.0/16\",\n\t\t\t\"172.16.0.256\",\n\t\t})\n\n\t\trequire.Error(t, err)\n\t}\n\n\t// nil value\n\t{\n\t\terr := r.SetTrustedProxies(nil)\n\n\t\tassert.Nil(t, r.trustedCIDRs)\n\t\trequire.NoError(t, err)\n\t}\n}\n\nfunc parseCIDR(cidr string) *net.IPNet {\n\t_, parsedCIDR, err := net.ParseCIDR(cidr)\n\tif err != nil {\n\t\tfmt.Println(err)\n\t}\n\treturn parsedCIDR\n}\n\nfunc assertRoutePresent(t *testing.T, gotRoutes RoutesInfo, wantRoute RouteInfo) {\n\tfor _, gotRoute := range gotRoutes {\n\t\tif gotRoute.Path == wantRoute.Path && gotRoute.Method == wantRoute.Method {\n\t\t\tassert.Regexp(t, wantRoute.Handler, gotRoute.Handler)\n\t\t\treturn\n\t\t}\n\t}\n\tt.Errorf(\"route not found: %v\", wantRoute)\n}\n\nfunc handlerTest1(c *Context) {}\nfunc handlerTest2(c *Context) {}\n\nfunc TestNewOptionFunc(t *testing.T) {\n\tfc := func(e *Engine) {\n\t\te.GET(\"/test1\", handlerTest1)\n\t\te.GET(\"/test2\", handlerTest2)\n\n\t\te.Use(func(c *Context) {\n\t\t\tc.Next()\n\t\t})\n\t}\n\n\tr := New(fc)\n\n\troutes := r.Routes()\n\tassertRoutePresent(t, routes, RouteInfo{Path: \"/test1\", Method: http.MethodGet, Handler: \"github.com/gin-gonic/gin.handlerTest1\"})\n\tassertRoutePresent(t, routes, RouteInfo{Path: \"/test2\", Method: http.MethodGet, Handler: \"github.com/gin-gonic/gin.handlerTest2\"})\n}\n\nfunc TestWithOptionFunc(t *testing.T) {\n\tr := New()\n\n\tr.With(func(e *Engine) {\n\t\te.GET(\"/test1\", handlerTest1)\n\t\te.GET(\"/test2\", handlerTest2)\n\n\t\te.Use(func(c *Context) {\n\t\t\tc.Next()\n\t\t})\n\t})\n\n\troutes := r.Routes()\n\tassertRoutePresent(t, routes, RouteInfo{Path: \"/test1\", Method: http.MethodGet, Handler: \"github.com/gin-gonic/gin.handlerTest1\"})\n\tassertRoutePresent(t, routes, RouteInfo{Path: \"/test2\", Method: http.MethodGet, Handler: \"github.com/gin-gonic/gin.handlerTest2\"})\n}\n\ntype Birthday string\n\nfunc (b *Birthday) UnmarshalParam(param string) error {\n\t*b = Birthday(strings.ReplaceAll(param, \"-\", \"/\"))\n\treturn nil\n}\n\nfunc TestCustomUnmarshalStruct(t *testing.T) {\n\troute := Default()\n\tvar request struct {\n\t\tBirthday Birthday `form:\"birthday\"`\n\t}\n\troute.GET(\"/test\", func(ctx *Context) {\n\t\t_ = ctx.BindQuery(&request)\n\t\tctx.JSON(200, request.Birthday)\n\t})\n\treq := httptest.NewRequest(http.MethodGet, \"/test?birthday=2000-01-01\", nil)\n\tw := httptest.NewRecorder()\n\troute.ServeHTTP(w, req)\n\tassert.Equal(t, 200, w.Code)\n\tassert.Equal(t, `\"2000/01/01\"`, w.Body.String())\n}\n\n// Test the fix for https://github.com/gin-gonic/gin/issues/4002\nfunc TestMethodNotAllowedNoRoute(t *testing.T) {\n\tg := New()\n\tg.HandleMethodNotAllowed = true\n\n\treq := httptest.NewRequest(http.MethodGet, \"/\", nil)\n\tresp := httptest.NewRecorder()\n\tassert.NotPanics(t, func() { g.ServeHTTP(resp, req) })\n\tassert.Equal(t, http.StatusNotFound, resp.Code)\n}\n\n// Test the fix for https://github.com/gin-gonic/gin/pull/4415\nfunc TestLiteralColonWithRun(t *testing.T) {\n\tSetMode(TestMode)\n\trouter := New()\n\n\trouter.GET(`/test\\:action`, func(c *Context) {\n\t\tc.JSON(http.StatusOK, H{\"path\": \"literal_colon\"})\n\t})\n\n\trouter.updateRouteTrees()\n\n\tw := httptest.NewRecorder()\n\n\treq, _ := http.NewRequest(http.MethodGet, \"/test:action\", nil)\n\trouter.ServeHTTP(w, req)\n\n\tassert.Equal(t, http.StatusOK, w.Code)\n\tassert.Contains(t, w.Body.String(), \"literal_colon\")\n}\n\nfunc TestLiteralColonWithDirectServeHTTP(t *testing.T) {\n\tSetMode(TestMode)\n\trouter := New()\n\n\trouter.GET(`/test\\:action`, func(c *Context) {\n\t\tc.JSON(http.StatusOK, H{\"path\": \"literal_colon\"})\n\t})\n\n\tw := httptest.NewRecorder()\n\treq, _ := http.NewRequest(http.MethodGet, \"/test:action\", nil)\n\trouter.ServeHTTP(w, req)\n\n\tassert.Equal(t, http.StatusOK, w.Code)\n\tassert.Contains(t, w.Body.String(), \"literal_colon\")\n}\n\nfunc TestLiteralColonWithHandler(t *testing.T) {\n\tSetMode(TestMode)\n\trouter := New()\n\n\trouter.GET(`/test\\:action`, func(c *Context) {\n\t\tc.JSON(http.StatusOK, H{\"path\": \"literal_colon\"})\n\t})\n\n\thandler := router.Handler()\n\n\tw := httptest.NewRecorder()\n\treq, _ := http.NewRequest(http.MethodGet, \"/test:action\", nil)\n\thandler.ServeHTTP(w, req)\n\n\tassert.Equal(t, http.StatusOK, w.Code)\n\tassert.Contains(t, w.Body.String(), \"literal_colon\")\n}\n\nfunc TestLiteralColonWithHTTPServer(t *testing.T) {\n\tSetMode(TestMode)\n\trouter := New()\n\n\trouter.GET(`/test\\:action`, func(c *Context) {\n\t\tc.JSON(http.StatusOK, H{\"path\": \"literal_colon\"})\n\t})\n\n\trouter.GET(\"/test/:param\", func(c *Context) {\n\t\tc.JSON(http.StatusOK, H{\"param\": c.Param(\"param\")})\n\t})\n\n\tw := httptest.NewRecorder()\n\treq, _ := http.NewRequest(http.MethodGet, \"/test:action\", nil)\n\trouter.ServeHTTP(w, req)\n\n\tassert.Equal(t, http.StatusOK, w.Code)\n\tassert.Contains(t, w.Body.String(), \"literal_colon\")\n\n\tw2 := httptest.NewRecorder()\n\treq2, _ := http.NewRequest(http.MethodGet, \"/test/foo\", nil)\n\trouter.ServeHTTP(w2, req2)\n\n\tassert.Equal(t, http.StatusOK, w2.Code)\n\tassert.Contains(t, w2.Body.String(), \"foo\")\n}\n\n// Test that updateRouteTrees is called only once\nfunc TestUpdateRouteTreesCalledOnce(t *testing.T) {\n\tSetMode(TestMode)\n\trouter := New()\n\n\trouter.GET(`/test\\:action`, func(c *Context) {\n\t\tc.String(http.StatusOK, \"ok\")\n\t})\n\n\tfor range 5 {\n\t\tw := httptest.NewRecorder()\n\t\treq, _ := http.NewRequest(http.MethodGet, \"/test:action\", nil)\n\t\trouter.ServeHTTP(w, req)\n\t\tassert.Equal(t, http.StatusOK, w.Code)\n\t\tassert.Equal(t, \"ok\", w.Body.String())\n\t}\n}\n"
  },
  {
    "path": "githubapi_test.go",
    "content": "// Copyright 2014 Manu Martinez-Almeida. All rights reserved.\n// Use of this source code is governed by a MIT style\n// license that can be found in the LICENSE file.\n\npackage gin\n\nimport (\n\t\"fmt\"\n\t\"math/rand\"\n\t\"net/http\"\n\t\"net/http/httptest\"\n\t\"os\"\n\t\"strconv\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n)\n\ntype route struct {\n\tmethod string\n\tpath   string\n}\n\n// http://developer.github.com/v3/\nvar githubAPI = []route{\n\t// OAuth Authorizations\n\t{http.MethodGet, \"/authorizations\"},\n\t{http.MethodGet, \"/authorizations/:id\"},\n\t{http.MethodPost, \"/authorizations\"},\n\t//{http.MethodPut, \"/authorizations/clients/:client_id\"},\n\t//{http.MethodPatch, \"/authorizations/:id\"},\n\t{http.MethodDelete, \"/authorizations/:id\"},\n\t{http.MethodGet, \"/applications/:client_id/tokens/:access_token\"},\n\t{http.MethodDelete, \"/applications/:client_id/tokens\"},\n\t{http.MethodDelete, \"/applications/:client_id/tokens/:access_token\"},\n\n\t// Activity\n\t{http.MethodGet, \"/events\"},\n\t{http.MethodGet, \"/repos/:owner/:repo/events\"},\n\t{http.MethodGet, \"/networks/:owner/:repo/events\"},\n\t{http.MethodGet, \"/orgs/:org/events\"},\n\t{http.MethodGet, \"/users/:user/received_events\"},\n\t{http.MethodGet, \"/users/:user/received_events/public\"},\n\t{http.MethodGet, \"/users/:user/events\"},\n\t{http.MethodGet, \"/users/:user/events/public\"},\n\t{http.MethodGet, \"/users/:user/events/orgs/:org\"},\n\t{http.MethodGet, \"/feeds\"},\n\t{http.MethodGet, \"/notifications\"},\n\t{http.MethodGet, \"/repos/:owner/:repo/notifications\"},\n\t{http.MethodPut, \"/notifications\"},\n\t{http.MethodPut, \"/repos/:owner/:repo/notifications\"},\n\t{http.MethodGet, \"/notifications/threads/:id\"},\n\t//{http.MethodPatch, \"/notifications/threads/:id\"},\n\t{http.MethodGet, \"/notifications/threads/:id/subscription\"},\n\t{http.MethodPut, \"/notifications/threads/:id/subscription\"},\n\t{http.MethodDelete, \"/notifications/threads/:id/subscription\"},\n\t{http.MethodGet, \"/repos/:owner/:repo/stargazers\"},\n\t{http.MethodGet, \"/users/:user/starred\"},\n\t{http.MethodGet, \"/user/starred\"},\n\t{http.MethodGet, \"/user/starred/:owner/:repo\"},\n\t{http.MethodPut, \"/user/starred/:owner/:repo\"},\n\t{http.MethodDelete, \"/user/starred/:owner/:repo\"},\n\t{http.MethodGet, \"/repos/:owner/:repo/subscribers\"},\n\t{http.MethodGet, \"/users/:user/subscriptions\"},\n\t{http.MethodGet, \"/user/subscriptions\"},\n\t{http.MethodGet, \"/repos/:owner/:repo/subscription\"},\n\t{http.MethodPut, \"/repos/:owner/:repo/subscription\"},\n\t{http.MethodDelete, \"/repos/:owner/:repo/subscription\"},\n\t{http.MethodGet, \"/user/subscriptions/:owner/:repo\"},\n\t{http.MethodPut, \"/user/subscriptions/:owner/:repo\"},\n\t{http.MethodDelete, \"/user/subscriptions/:owner/:repo\"},\n\n\t// Gists\n\t{http.MethodGet, \"/users/:user/gists\"},\n\t{http.MethodGet, \"/gists\"},\n\t//{http.MethodGet, \"/gists/public\"},\n\t//{http.MethodGet, \"/gists/starred\"},\n\t{http.MethodGet, \"/gists/:id\"},\n\t{http.MethodPost, \"/gists\"},\n\t//{http.MethodPatch, \"/gists/:id\"},\n\t{http.MethodPut, \"/gists/:id/star\"},\n\t{http.MethodDelete, \"/gists/:id/star\"},\n\t{http.MethodGet, \"/gists/:id/star\"},\n\t{http.MethodPost, \"/gists/:id/forks\"},\n\t{http.MethodDelete, \"/gists/:id\"},\n\n\t// Git Data\n\t{http.MethodGet, \"/repos/:owner/:repo/git/blobs/:sha\"},\n\t{http.MethodPost, \"/repos/:owner/:repo/git/blobs\"},\n\t{http.MethodGet, \"/repos/:owner/:repo/git/commits/:sha\"},\n\t{http.MethodPost, \"/repos/:owner/:repo/git/commits\"},\n\t//{http.MethodGet, \"/repos/:owner/:repo/git/refs/*ref\"},\n\t{http.MethodGet, \"/repos/:owner/:repo/git/refs\"},\n\t{http.MethodPost, \"/repos/:owner/:repo/git/refs\"},\n\t//{http.MethodPatch, \"/repos/:owner/:repo/git/refs/*ref\"},\n\t//{http.MethodDelete, \"/repos/:owner/:repo/git/refs/*ref\"},\n\t{http.MethodGet, \"/repos/:owner/:repo/git/tags/:sha\"},\n\t{http.MethodPost, \"/repos/:owner/:repo/git/tags\"},\n\t{http.MethodGet, \"/repos/:owner/:repo/git/trees/:sha\"},\n\t{http.MethodPost, \"/repos/:owner/:repo/git/trees\"},\n\n\t// Issues\n\t{http.MethodGet, \"/issues\"},\n\t{http.MethodGet, \"/user/issues\"},\n\t{http.MethodGet, \"/orgs/:org/issues\"},\n\t{http.MethodGet, \"/repos/:owner/:repo/issues\"},\n\t{http.MethodGet, \"/repos/:owner/:repo/issues/:number\"},\n\t{http.MethodPost, \"/repos/:owner/:repo/issues\"},\n\t//{http.MethodPatch, \"/repos/:owner/:repo/issues/:number\"},\n\t{http.MethodGet, \"/repos/:owner/:repo/assignees\"},\n\t{http.MethodGet, \"/repos/:owner/:repo/assignees/:assignee\"},\n\t{http.MethodGet, \"/repos/:owner/:repo/issues/:number/comments\"},\n\t//{http.MethodGet, \"/repos/:owner/:repo/issues/comments\"},\n\t//{http.MethodGet, \"/repos/:owner/:repo/issues/comments/:id\"},\n\t{http.MethodPost, \"/repos/:owner/:repo/issues/:number/comments\"},\n\t//{http.MethodPatch, \"/repos/:owner/:repo/issues/comments/:id\"},\n\t//{http.MethodDelete, \"/repos/:owner/:repo/issues/comments/:id\"},\n\t{http.MethodGet, \"/repos/:owner/:repo/issues/:number/events\"},\n\t//{http.MethodGet, \"/repos/:owner/:repo/issues/events\"},\n\t//{http.MethodGet, \"/repos/:owner/:repo/issues/events/:id\"},\n\t{http.MethodGet, \"/repos/:owner/:repo/labels\"},\n\t{http.MethodGet, \"/repos/:owner/:repo/labels/:name\"},\n\t{http.MethodPost, \"/repos/:owner/:repo/labels\"},\n\t//{http.MethodPatch, \"/repos/:owner/:repo/labels/:name\"},\n\t{http.MethodDelete, \"/repos/:owner/:repo/labels/:name\"},\n\t{http.MethodGet, \"/repos/:owner/:repo/issues/:number/labels\"},\n\t{http.MethodPost, \"/repos/:owner/:repo/issues/:number/labels\"},\n\t{http.MethodDelete, \"/repos/:owner/:repo/issues/:number/labels/:name\"},\n\t{http.MethodPut, \"/repos/:owner/:repo/issues/:number/labels\"},\n\t{http.MethodDelete, \"/repos/:owner/:repo/issues/:number/labels\"},\n\t{http.MethodGet, \"/repos/:owner/:repo/milestones/:number/labels\"},\n\t{http.MethodGet, \"/repos/:owner/:repo/milestones\"},\n\t{http.MethodGet, \"/repos/:owner/:repo/milestones/:number\"},\n\t{http.MethodPost, \"/repos/:owner/:repo/milestones\"},\n\t//{http.MethodPatch, \"/repos/:owner/:repo/milestones/:number\"},\n\t{http.MethodDelete, \"/repos/:owner/:repo/milestones/:number\"},\n\n\t// Miscellaneous\n\t{http.MethodGet, \"/emojis\"},\n\t{http.MethodGet, \"/gitignore/templates\"},\n\t{http.MethodGet, \"/gitignore/templates/:name\"},\n\t{http.MethodPost, \"/markdown\"},\n\t{http.MethodPost, \"/markdown/raw\"},\n\t{http.MethodGet, \"/meta\"},\n\t{http.MethodGet, \"/rate_limit\"},\n\n\t// Organizations\n\t{http.MethodGet, \"/users/:user/orgs\"},\n\t{http.MethodGet, \"/user/orgs\"},\n\t{http.MethodGet, \"/orgs/:org\"},\n\t//{http.MethodPatch, \"/orgs/:org\"},\n\t{http.MethodGet, \"/orgs/:org/members\"},\n\t{http.MethodGet, \"/orgs/:org/members/:user\"},\n\t{http.MethodDelete, \"/orgs/:org/members/:user\"},\n\t{http.MethodGet, \"/orgs/:org/public_members\"},\n\t{http.MethodGet, \"/orgs/:org/public_members/:user\"},\n\t{http.MethodPut, \"/orgs/:org/public_members/:user\"},\n\t{http.MethodDelete, \"/orgs/:org/public_members/:user\"},\n\t{http.MethodGet, \"/orgs/:org/teams\"},\n\t{http.MethodGet, \"/teams/:id\"},\n\t{http.MethodPost, \"/orgs/:org/teams\"},\n\t//{http.MethodPatch, \"/teams/:id\"},\n\t{http.MethodDelete, \"/teams/:id\"},\n\t{http.MethodGet, \"/teams/:id/members\"},\n\t{http.MethodGet, \"/teams/:id/members/:user\"},\n\t{http.MethodPut, \"/teams/:id/members/:user\"},\n\t{http.MethodDelete, \"/teams/:id/members/:user\"},\n\t{http.MethodGet, \"/teams/:id/repos\"},\n\t{http.MethodGet, \"/teams/:id/repos/:owner/:repo\"},\n\t{http.MethodPut, \"/teams/:id/repos/:owner/:repo\"},\n\t{http.MethodDelete, \"/teams/:id/repos/:owner/:repo\"},\n\t{http.MethodGet, \"/user/teams\"},\n\n\t// Pull Requests\n\t{http.MethodGet, \"/repos/:owner/:repo/pulls\"},\n\t{http.MethodGet, \"/repos/:owner/:repo/pulls/:number\"},\n\t{http.MethodPost, \"/repos/:owner/:repo/pulls\"},\n\t//{http.MethodPatch, \"/repos/:owner/:repo/pulls/:number\"},\n\t{http.MethodGet, \"/repos/:owner/:repo/pulls/:number/commits\"},\n\t{http.MethodGet, \"/repos/:owner/:repo/pulls/:number/files\"},\n\t{http.MethodGet, \"/repos/:owner/:repo/pulls/:number/merge\"},\n\t{http.MethodPut, \"/repos/:owner/:repo/pulls/:number/merge\"},\n\t{http.MethodGet, \"/repos/:owner/:repo/pulls/:number/comments\"},\n\t//{http.MethodGet, \"/repos/:owner/:repo/pulls/comments\"},\n\t//{http.MethodGet, \"/repos/:owner/:repo/pulls/comments/:number\"},\n\t{http.MethodPut, \"/repos/:owner/:repo/pulls/:number/comments\"},\n\t//{http.MethodPatch, \"/repos/:owner/:repo/pulls/comments/:number\"},\n\t//{http.MethodDelete, \"/repos/:owner/:repo/pulls/comments/:number\"},\n\n\t// Repositories\n\t{http.MethodGet, \"/user/repos\"},\n\t{http.MethodGet, \"/users/:user/repos\"},\n\t{http.MethodGet, \"/orgs/:org/repos\"},\n\t{http.MethodGet, \"/repositories\"},\n\t{http.MethodPost, \"/user/repos\"},\n\t{http.MethodPost, \"/orgs/:org/repos\"},\n\t{http.MethodGet, \"/repos/:owner/:repo\"},\n\t//{http.MethodPatch, \"/repos/:owner/:repo\"},\n\t{http.MethodGet, \"/repos/:owner/:repo/contributors\"},\n\t{http.MethodGet, \"/repos/:owner/:repo/languages\"},\n\t{http.MethodGet, \"/repos/:owner/:repo/teams\"},\n\t{http.MethodGet, \"/repos/:owner/:repo/tags\"},\n\t{http.MethodGet, \"/repos/:owner/:repo/branches\"},\n\t{http.MethodGet, \"/repos/:owner/:repo/branches/:branch\"},\n\t{http.MethodDelete, \"/repos/:owner/:repo\"},\n\t{http.MethodGet, \"/repos/:owner/:repo/collaborators\"},\n\t{http.MethodGet, \"/repos/:owner/:repo/collaborators/:user\"},\n\t{http.MethodPut, \"/repos/:owner/:repo/collaborators/:user\"},\n\t{http.MethodDelete, \"/repos/:owner/:repo/collaborators/:user\"},\n\t{http.MethodGet, \"/repos/:owner/:repo/comments\"},\n\t{http.MethodGet, \"/repos/:owner/:repo/commits/:sha/comments\"},\n\t{http.MethodPost, \"/repos/:owner/:repo/commits/:sha/comments\"},\n\t{http.MethodGet, \"/repos/:owner/:repo/comments/:id\"},\n\t//{http.MethodPatch, \"/repos/:owner/:repo/comments/:id\"},\n\t{http.MethodDelete, \"/repos/:owner/:repo/comments/:id\"},\n\t{http.MethodGet, \"/repos/:owner/:repo/commits\"},\n\t{http.MethodGet, \"/repos/:owner/:repo/commits/:sha\"},\n\t{http.MethodGet, \"/repos/:owner/:repo/readme\"},\n\t//{http.MethodGet, \"/repos/:owner/:repo/contents/*path\"},\n\t//{http.MethodPut, \"/repos/:owner/:repo/contents/*path\"},\n\t//{http.MethodDelete, \"/repos/:owner/:repo/contents/*path\"},\n\t//{http.MethodGet, \"/repos/:owner/:repo/:archive_format/:ref\"},\n\t{http.MethodGet, \"/repos/:owner/:repo/keys\"},\n\t{http.MethodGet, \"/repos/:owner/:repo/keys/:id\"},\n\t{http.MethodPost, \"/repos/:owner/:repo/keys\"},\n\t//{http.MethodPatch, \"/repos/:owner/:repo/keys/:id\"},\n\t{http.MethodDelete, \"/repos/:owner/:repo/keys/:id\"},\n\t{http.MethodGet, \"/repos/:owner/:repo/downloads\"},\n\t{http.MethodGet, \"/repos/:owner/:repo/downloads/:id\"},\n\t{http.MethodDelete, \"/repos/:owner/:repo/downloads/:id\"},\n\t{http.MethodGet, \"/repos/:owner/:repo/forks\"},\n\t{http.MethodPost, \"/repos/:owner/:repo/forks\"},\n\t{http.MethodGet, \"/repos/:owner/:repo/hooks\"},\n\t{http.MethodGet, \"/repos/:owner/:repo/hooks/:id\"},\n\t{http.MethodPost, \"/repos/:owner/:repo/hooks\"},\n\t//{http.MethodPatch, \"/repos/:owner/:repo/hooks/:id\"},\n\t{http.MethodPost, \"/repos/:owner/:repo/hooks/:id/tests\"},\n\t{http.MethodDelete, \"/repos/:owner/:repo/hooks/:id\"},\n\t{http.MethodPost, \"/repos/:owner/:repo/merges\"},\n\t{http.MethodGet, \"/repos/:owner/:repo/releases\"},\n\t{http.MethodGet, \"/repos/:owner/:repo/releases/:id\"},\n\t{http.MethodPost, \"/repos/:owner/:repo/releases\"},\n\t//{http.MethodPatch, \"/repos/:owner/:repo/releases/:id\"},\n\t{http.MethodDelete, \"/repos/:owner/:repo/releases/:id\"},\n\t{http.MethodGet, \"/repos/:owner/:repo/releases/:id/assets\"},\n\t{http.MethodGet, \"/repos/:owner/:repo/stats/contributors\"},\n\t{http.MethodGet, \"/repos/:owner/:repo/stats/commit_activity\"},\n\t{http.MethodGet, \"/repos/:owner/:repo/stats/code_frequency\"},\n\t{http.MethodGet, \"/repos/:owner/:repo/stats/participation\"},\n\t{http.MethodGet, \"/repos/:owner/:repo/stats/punch_card\"},\n\t{http.MethodGet, \"/repos/:owner/:repo/statuses/:ref\"},\n\t{http.MethodPost, \"/repos/:owner/:repo/statuses/:ref\"},\n\n\t// Search\n\t{http.MethodGet, \"/search/repositories\"},\n\t{http.MethodGet, \"/search/code\"},\n\t{http.MethodGet, \"/search/issues\"},\n\t{http.MethodGet, \"/search/users\"},\n\t{http.MethodGet, \"/legacy/issues/search/:owner/:repository/:state/:keyword\"},\n\t{http.MethodGet, \"/legacy/repos/search/:keyword\"},\n\t{http.MethodGet, \"/legacy/user/search/:keyword\"},\n\t{http.MethodGet, \"/legacy/user/email/:email\"},\n\n\t// Users\n\t{http.MethodGet, \"/users/:user\"},\n\t{http.MethodGet, \"/user\"},\n\t//{http.MethodPatch, \"/user\"},\n\t{http.MethodGet, \"/users\"},\n\t{http.MethodGet, \"/user/emails\"},\n\t{http.MethodPost, \"/user/emails\"},\n\t{http.MethodDelete, \"/user/emails\"},\n\t{http.MethodGet, \"/users/:user/followers\"},\n\t{http.MethodGet, \"/user/followers\"},\n\t{http.MethodGet, \"/users/:user/following\"},\n\t{http.MethodGet, \"/user/following\"},\n\t{http.MethodGet, \"/user/following/:user\"},\n\t{http.MethodGet, \"/users/:user/following/:target_user\"},\n\t{http.MethodPut, \"/user/following/:user\"},\n\t{http.MethodDelete, \"/user/following/:user\"},\n\t{http.MethodGet, \"/users/:user/keys\"},\n\t{http.MethodGet, \"/user/keys\"},\n\t{http.MethodGet, \"/user/keys/:id\"},\n\t{http.MethodPost, \"/user/keys\"},\n\t//{http.MethodPatch, \"/user/keys/:id\"},\n\t{http.MethodDelete, \"/user/keys/:id\"},\n}\n\nfunc TestShouldBindUri(t *testing.T) {\n\tDefaultWriter = os.Stdout\n\trouter := New()\n\n\ttype Person struct {\n\t\tName string `uri:\"name\" binding:\"required\"`\n\t\tID   string `uri:\"id\" binding:\"required\"`\n\t}\n\trouter.Handle(http.MethodGet, \"/rest/:name/:id\", func(c *Context) {\n\t\tvar person Person\n\t\trequire.NoError(t, c.ShouldBindUri(&person))\n\t\tassert.NotEmpty(t, person.Name)\n\t\tassert.NotEmpty(t, person.ID)\n\t\tc.String(http.StatusOK, \"ShouldBindUri test OK\")\n\t})\n\n\tpath, _ := exampleFromPath(\"/rest/:name/:id\")\n\tw := PerformRequest(router, http.MethodGet, path)\n\tassert.Equal(t, \"ShouldBindUri test OK\", w.Body.String())\n\tassert.Equal(t, http.StatusOK, w.Code)\n}\n\nfunc TestBindUri(t *testing.T) {\n\tDefaultWriter = os.Stdout\n\trouter := New()\n\n\ttype Person struct {\n\t\tName string `uri:\"name\" binding:\"required\"`\n\t\tID   string `uri:\"id\" binding:\"required\"`\n\t}\n\trouter.Handle(http.MethodGet, \"/rest/:name/:id\", func(c *Context) {\n\t\tvar person Person\n\t\trequire.NoError(t, c.BindUri(&person))\n\t\tassert.NotEmpty(t, person.Name)\n\t\tassert.NotEmpty(t, person.ID)\n\t\tc.String(http.StatusOK, \"BindUri test OK\")\n\t})\n\n\tpath, _ := exampleFromPath(\"/rest/:name/:id\")\n\tw := PerformRequest(router, http.MethodGet, path)\n\tassert.Equal(t, \"BindUri test OK\", w.Body.String())\n\tassert.Equal(t, http.StatusOK, w.Code)\n}\n\nfunc TestBindUriError(t *testing.T) {\n\tDefaultWriter = os.Stdout\n\trouter := New()\n\n\ttype Member struct {\n\t\tNumber string `uri:\"num\" binding:\"required,uuid\"`\n\t}\n\trouter.Handle(http.MethodGet, \"/new/rest/:num\", func(c *Context) {\n\t\tvar m Member\n\t\trequire.Error(t, c.BindUri(&m))\n\t})\n\n\tpath1, _ := exampleFromPath(\"/new/rest/:num\")\n\tw1 := PerformRequest(router, http.MethodGet, path1)\n\tassert.Equal(t, http.StatusBadRequest, w1.Code)\n}\n\nfunc TestRaceContextCopy(t *testing.T) {\n\tDefaultWriter = os.Stdout\n\trouter := Default()\n\trouter.GET(\"/test/copy/race\", func(c *Context) {\n\t\tc.Set(\"1\", 0)\n\t\tc.Set(\"2\", 0)\n\n\t\t// Sending a copy of the Context to two separate routines\n\t\tgo readWriteKeys(c.Copy())\n\t\tgo readWriteKeys(c.Copy())\n\t\tc.String(http.StatusOK, \"run OK, no panics\")\n\t})\n\tw := PerformRequest(router, http.MethodGet, \"/test/copy/race\")\n\tassert.Equal(t, \"run OK, no panics\", w.Body.String())\n}\n\nfunc readWriteKeys(c *Context) {\n\tfor {\n\t\tc.Set(\"1\", rand.Int())\n\t\tc.Set(\"2\", c.Value(\"1\"))\n\t}\n}\n\nfunc githubConfigRouter(router *Engine) {\n\tfor _, route := range githubAPI {\n\t\trouter.Handle(route.method, route.path, func(c *Context) {\n\t\t\toutput := make(map[string]string, len(c.Params)+1)\n\t\t\toutput[\"status\"] = \"good\"\n\t\t\tfor _, param := range c.Params {\n\t\t\t\toutput[param.Key] = param.Value\n\t\t\t}\n\t\t\tc.JSON(http.StatusOK, output)\n\t\t})\n\t}\n}\n\nfunc TestGithubAPI(t *testing.T) {\n\tDefaultWriter = os.Stdout\n\trouter := New()\n\tgithubConfigRouter(router)\n\n\tfor _, route := range githubAPI {\n\t\tpath, values := exampleFromPath(route.path)\n\t\tw := PerformRequest(router, route.method, path)\n\n\t\t// TEST\n\t\tassert.Contains(t, w.Body.String(), \"\\\"status\\\":\\\"good\\\"\")\n\t\tfor _, value := range values {\n\t\t\tstr := fmt.Sprintf(\"\\\"%s\\\":\\\"%s\\\"\", value.Key, value.Value)\n\t\t\tassert.Contains(t, w.Body.String(), str)\n\t\t}\n\t}\n}\n\nfunc exampleFromPath(path string) (string, Params) {\n\toutput := new(strings.Builder)\n\tparams := make(Params, 0, 6)\n\tstart := -1\n\tfor i, c := range path {\n\t\tif c == ':' {\n\t\t\tstart = i + 1\n\t\t}\n\t\tif start >= 0 {\n\t\t\tif c == '/' {\n\t\t\t\tvalue := strconv.Itoa(rand.Intn(100000))\n\t\t\t\tparams = append(params, Param{\n\t\t\t\t\tKey:   path[start:i],\n\t\t\t\t\tValue: value,\n\t\t\t\t})\n\t\t\t\toutput.WriteString(value)\n\t\t\t\toutput.WriteRune(c)\n\t\t\t\tstart = -1\n\t\t\t}\n\t\t} else {\n\t\t\toutput.WriteRune(c)\n\t\t}\n\t}\n\tif start >= 0 {\n\t\tvalue := strconv.Itoa(rand.Intn(100000))\n\t\tparams = append(params, Param{\n\t\t\tKey:   path[start:],\n\t\t\tValue: value,\n\t\t})\n\t\toutput.WriteString(value)\n\t}\n\n\treturn output.String(), params\n}\n\nfunc BenchmarkGithub(b *testing.B) {\n\trouter := New()\n\tgithubConfigRouter(router)\n\trunRequest(b, router, http.MethodGet, \"/legacy/issues/search/:owner/:repository/:state/:keyword\")\n}\n\nfunc BenchmarkParallelGithub(b *testing.B) {\n\tDefaultWriter = os.Stdout\n\trouter := New()\n\tgithubConfigRouter(router)\n\n\treq, _ := http.NewRequest(http.MethodPost, \"/repos/manucorporat/sse/git/blobs\", nil)\n\n\tb.RunParallel(func(pb *testing.PB) {\n\t\t// Each goroutine has its own bytes.Buffer.\n\t\tfor pb.Next() {\n\t\t\tw := httptest.NewRecorder()\n\t\t\trouter.ServeHTTP(w, req)\n\t\t}\n\t})\n}\n\nfunc BenchmarkParallelGithubDefault(b *testing.B) {\n\tDefaultWriter = os.Stdout\n\trouter := New()\n\tgithubConfigRouter(router)\n\n\treq, _ := http.NewRequest(http.MethodPost, \"/repos/manucorporat/sse/git/blobs\", nil)\n\n\tb.RunParallel(func(pb *testing.PB) {\n\t\t// Each goroutine has its own bytes.Buffer.\n\t\tfor pb.Next() {\n\t\t\tw := httptest.NewRecorder()\n\t\t\trouter.ServeHTTP(w, req)\n\t\t}\n\t})\n}\n"
  },
  {
    "path": "go.mod",
    "content": "module github.com/gin-gonic/gin\n\ngo 1.25.0\n\nrequire (\n\tgithub.com/bytedance/sonic v1.15.0\n\tgithub.com/gin-contrib/sse v1.1.0\n\tgithub.com/go-playground/validator/v10 v10.30.1\n\tgithub.com/goccy/go-json v0.10.6\n\tgithub.com/goccy/go-yaml v1.19.2\n\tgithub.com/json-iterator/go v1.1.12\n\tgithub.com/mattn/go-isatty v0.0.20\n\tgithub.com/modern-go/reflect2 v1.0.2\n\tgithub.com/pelletier/go-toml/v2 v2.2.4\n\tgithub.com/quic-go/quic-go v0.59.0\n\tgithub.com/stretchr/testify v1.11.1\n\tgithub.com/ugorji/go/codec v1.3.1\n\tgo.mongodb.org/mongo-driver/v2 v2.5.0\n\tgolang.org/x/net v0.52.0\n\tgoogle.golang.org/protobuf v1.36.11\n)\n\nrequire gopkg.in/yaml.v3 v3.0.1 // indirect\n\nrequire (\n\tgithub.com/bytedance/gopkg v0.1.3 // indirect\n\tgithub.com/bytedance/sonic/loader v0.5.0 // indirect\n\tgithub.com/cloudwego/base64x v0.1.6 // indirect\n\tgithub.com/davecgh/go-spew v1.1.1 // indirect\n\tgithub.com/gabriel-vasile/mimetype v1.4.13 // indirect\n\tgithub.com/go-playground/locales v0.14.1 // indirect\n\tgithub.com/go-playground/universal-translator v0.18.1 // indirect\n\tgithub.com/klauspost/cpuid/v2 v2.3.0 // indirect\n\tgithub.com/kr/text v0.2.0 // indirect\n\tgithub.com/leodido/go-urn v1.4.0 // indirect\n\tgithub.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect\n\tgithub.com/pmezard/go-difflib v1.0.0 // indirect\n\tgithub.com/quic-go/qpack v0.6.0 // indirect\n\tgithub.com/twitchyliquid64/golang-asm v0.15.1 // indirect\n\tgo.uber.org/mock v0.6.0 // indirect\n\tgolang.org/x/arch v0.25.0 // indirect\n\tgolang.org/x/crypto v0.49.0 // indirect\n\tgolang.org/x/sys v0.42.0 // indirect\n\tgolang.org/x/text v0.35.0 // indirect\n)\n"
  },
  {
    "path": "go.sum",
    "content": "github.com/bytedance/gopkg v0.1.3 h1:TPBSwH8RsouGCBcMBktLt1AymVo2TVsBVCY4b6TnZ/M=\ngithub.com/bytedance/gopkg v0.1.3/go.mod h1:576VvJ+eJgyCzdjS+c4+77QF3p7ubbtiKARP3TxducM=\ngithub.com/bytedance/sonic v1.15.0 h1:/PXeWFaR5ElNcVE84U0dOHjiMHQOwNIx3K4ymzh/uSE=\ngithub.com/bytedance/sonic v1.15.0/go.mod h1:tFkWrPz0/CUCLEF4ri4UkHekCIcdnkqXw9VduqpJh0k=\ngithub.com/bytedance/sonic/loader v0.5.0 h1:gXH3KVnatgY7loH5/TkeVyXPfESoqSBSBEiDd5VjlgE=\ngithub.com/bytedance/sonic/loader v0.5.0/go.mod h1:AR4NYCk5DdzZizZ5djGqQ92eEhCCcdf5x77udYiSJRo=\ngithub.com/cloudwego/base64x v0.1.6 h1:t11wG9AECkCDk5fMSoxmufanudBtJ+/HemLstXDLI2M=\ngithub.com/cloudwego/base64x v0.1.6/go.mod h1:OFcloc187FXDaYHvrNIjxSe8ncn0OOM8gEHfghB2IPU=\ngithub.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=\ngithub.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=\ngithub.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=\ngithub.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=\ngithub.com/gabriel-vasile/mimetype v1.4.13 h1:46nXokslUBsAJE/wMsp5gtO500a4F3Nkz9Ufpk2AcUM=\ngithub.com/gabriel-vasile/mimetype v1.4.13/go.mod h1:d+9Oxyo1wTzWdyVUPMmXFvp4F9tea18J8ufA774AB3s=\ngithub.com/gin-contrib/sse v1.1.0 h1:n0w2GMuUpWDVp7qSpvze6fAu9iRxJY4Hmj6AmBOU05w=\ngithub.com/gin-contrib/sse v1.1.0/go.mod h1:hxRZ5gVpWMT7Z0B0gSNYqqsSCNIJMjzvm6fqCz9vjwM=\ngithub.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=\ngithub.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=\ngithub.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=\ngithub.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=\ngithub.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=\ngithub.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=\ngithub.com/go-playground/validator/v10 v10.30.1 h1:f3zDSN/zOma+w6+1Wswgd9fLkdwy06ntQJp0BBvFG0w=\ngithub.com/go-playground/validator/v10 v10.30.1/go.mod h1:oSuBIQzuJxL//3MelwSLD5hc2Tu889bF0Idm9Dg26cM=\ngithub.com/goccy/go-json v0.10.6 h1:p8HrPJzOakx/mn/bQtjgNjdTcN+/S6FcG2CTtQOrHVU=\ngithub.com/goccy/go-json v0.10.6/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=\ngithub.com/goccy/go-yaml v1.19.2 h1:PmFC1S6h8ljIz6gMRBopkjP1TVT7xuwrButHID66PoM=\ngithub.com/goccy/go-yaml v1.19.2/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA=\ngithub.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=\ngithub.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=\ngithub.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=\ngithub.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=\ngithub.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=\ngithub.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y=\ngithub.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=\ngithub.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=\ngithub.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=\ngithub.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=\ngithub.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=\ngithub.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=\ngithub.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=\ngithub.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=\ngithub.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=\ngithub.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=\ngithub.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=\ngithub.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=\ngithub.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=\ngithub.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=\ngithub.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4=\ngithub.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY=\ngithub.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=\ngithub.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=\ngithub.com/quic-go/qpack v0.6.0 h1:g7W+BMYynC1LbYLSqRt8PBg5Tgwxn214ZZR34VIOjz8=\ngithub.com/quic-go/qpack v0.6.0/go.mod h1:lUpLKChi8njB4ty2bFLX2x4gzDqXwUpaO1DP9qMDZII=\ngithub.com/quic-go/quic-go v0.59.0 h1:OLJkp1Mlm/aS7dpKgTc6cnpynnD2Xg7C1pwL6vy/SAw=\ngithub.com/quic-go/quic-go v0.59.0/go.mod h1:upnsH4Ju1YkqpLXC305eW3yDZ4NfnNbmQRCMWS58IKU=\ngithub.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=\ngithub.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog=\ngithub.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=\ngithub.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=\ngithub.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=\ngithub.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=\ngithub.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=\ngithub.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=\ngithub.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=\ngithub.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=\ngithub.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=\ngithub.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=\ngithub.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=\ngithub.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=\ngithub.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=\ngithub.com/ugorji/go/codec v1.3.1 h1:waO7eEiFDwidsBN6agj1vJQ4AG7lh2yqXyOXqhgQuyY=\ngithub.com/ugorji/go/codec v1.3.1/go.mod h1:pRBVtBSKl77K30Bv8R2P+cLSGaTtex6fsA2Wjqmfxj4=\ngo.mongodb.org/mongo-driver/v2 v2.5.0 h1:yXUhImUjjAInNcpTcAlPHiT7bIXhshCTL3jVBkF3xaE=\ngo.mongodb.org/mongo-driver/v2 v2.5.0/go.mod h1:yOI9kBsufol30iFsl1slpdq1I0eHPzybRWdyYUs8K/0=\ngo.uber.org/mock v0.6.0 h1:hyF9dfmbgIX5EfOdasqLsWD6xqpNZlXblLB/Dbnwv3Y=\ngo.uber.org/mock v0.6.0/go.mod h1:KiVJ4BqZJaMj4svdfmHM0AUx4NJYO8ZNpPnZn1Z+BBU=\ngolang.org/x/arch v0.25.0 h1:qnk6Ksugpi5Bz32947rkUgDt9/s5qvqDPl/gBKdMJLE=\ngolang.org/x/arch v0.25.0/go.mod h1:0X+GdSIP+kL5wPmpK7sdkEVTt2XoYP0cSjQSbZBwOi8=\ngolang.org/x/crypto v0.49.0 h1:+Ng2ULVvLHnJ/ZFEq4KdcDd/cfjrrjjNSXNzxg0Y4U4=\ngolang.org/x/crypto v0.49.0/go.mod h1:ErX4dUh2UM+CFYiXZRTcMpEcN8b/1gxEuv3nODoYtCA=\ngolang.org/x/net v0.52.0 h1:He/TN1l0e4mmR3QqHMT2Xab3Aj3L9qjbhRm78/6jrW0=\ngolang.org/x/net v0.52.0/go.mod h1:R1MAz7uMZxVMualyPXb+VaqGSa3LIaUqk0eEt3w36Sw=\ngolang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.42.0 h1:omrd2nAlyT5ESRdCLYdm3+fMfNFE/+Rf4bDIQImRJeo=\ngolang.org/x/sys v0.42.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=\ngolang.org/x/text v0.35.0 h1:JOVx6vVDFokkpaq1AEptVzLTpDe9KGpj5tR4/X+ybL8=\ngolang.org/x/text v0.35.0/go.mod h1:khi/HExzZJ2pGnjenulevKNX1W67CUy0AsXcNubPGCA=\ngoogle.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=\ngoogle.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=\ngopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=\ngopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=\ngopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=\ngopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=\ngopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=\ngopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=\n"
  },
  {
    "path": "internal/bytesconv/bytesconv.go",
    "content": "// Copyright 2023 Gin Core Team. All rights reserved.\n// Use of this source code is governed by a MIT style\n// license that can be found in the LICENSE file.\n\npackage bytesconv\n\nimport (\n\t\"unsafe\"\n)\n\n// StringToBytes converts string to byte slice without a memory allocation.\n// For more details, see https://github.com/golang/go/issues/53003#issuecomment-1140276077.\nfunc StringToBytes(s string) []byte {\n\treturn unsafe.Slice(unsafe.StringData(s), len(s))\n}\n\n// BytesToString converts byte slice to string without a memory allocation.\n// For more details, see https://github.com/golang/go/issues/53003#issuecomment-1140276077.\nfunc BytesToString(b []byte) string {\n\treturn unsafe.String(unsafe.SliceData(b), len(b))\n}\n"
  },
  {
    "path": "internal/bytesconv/bytesconv_test.go",
    "content": "// Copyright 2020 Gin Core Team. All rights reserved.\n// Use of this source code is governed by a MIT style\n// license that can be found in the LICENSE file.\n\npackage bytesconv\n\nimport (\n\t\"bytes\"\n\tcRand \"crypto/rand\"\n\t\"math/rand\"\n\t\"strings\"\n\t\"testing\"\n\t\"time\"\n)\n\nvar (\n\ttestString = \"Albert Einstein: Logic will get you from A to B. Imagination will take you everywhere.\"\n\ttestBytes  = []byte(testString)\n)\n\nfunc rawBytesToStr(b []byte) string {\n\treturn string(b)\n}\n\nfunc rawStrToBytes(s string) []byte {\n\treturn []byte(s)\n}\n\n// go test -v\n\nfunc TestBytesToString(t *testing.T) {\n\tdata := make([]byte, 1024)\n\tfor range 100 {\n\t\t_, err := cRand.Read(data)\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t\tif rawBytesToStr(data) != BytesToString(data) {\n\t\t\tt.Fatal(\"don't match\")\n\t\t}\n\t}\n}\n\nfunc TestBytesToStringEmpty(t *testing.T) {\n\tif got := BytesToString([]byte{}); got != \"\" {\n\t\tt.Fatalf(\"BytesToString([]byte{}) = %q; want empty string\", got)\n\t}\n\tif got := BytesToString(nil); got != \"\" {\n\t\tt.Fatalf(\"BytesToString(nil) = %q; want empty string\", got)\n\t}\n}\n\nconst letterBytes = \"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ\"\nconst (\n\tletterIdxBits = 6                    // 6 bits to represent a letter index\n\tletterIdxMask = 1<<letterIdxBits - 1 // All 1-bits, as many as letterIdxBits\n\tletterIdxMax  = 63 / letterIdxBits   // # of letter indices fitting in 63 bits\n)\n\nvar src = rand.New(rand.NewSource(time.Now().UnixNano()))\n\nfunc RandStringBytesMaskImprSrcSB(n int) string {\n\tsb := strings.Builder{}\n\tsb.Grow(n)\n\t// A src.Int63() generates 63 random bits, enough for letterIdxMax characters!\n\tfor i, cache, remain := n-1, src.Int63(), letterIdxMax; i >= 0; {\n\t\tif remain == 0 {\n\t\t\tcache, remain = src.Int63(), letterIdxMax\n\t\t}\n\t\tif idx := int(cache & letterIdxMask); idx < len(letterBytes) {\n\t\t\tsb.WriteByte(letterBytes[idx])\n\t\t\ti--\n\t\t}\n\t\tcache >>= letterIdxBits\n\t\tremain--\n\t}\n\n\treturn sb.String()\n}\n\nfunc TestStringToBytes(t *testing.T) {\n\tfor range 100 {\n\t\ts := RandStringBytesMaskImprSrcSB(64)\n\t\tif !bytes.Equal(rawStrToBytes(s), StringToBytes(s)) {\n\t\t\tt.Fatal(\"don't match\")\n\t\t}\n\t}\n}\n\nfunc TestStringToBytesEmpty(t *testing.T) {\n\tb := StringToBytes(\"\")\n\tif len(b) != 0 {\n\t\tt.Fatalf(`StringToBytes(\"\") length = %d; want 0`, len(b))\n\t}\n\tif !bytes.Equal(b, []byte(\"\")) {\n\t\tt.Fatalf(`StringToBytes(\"\") = %v; want []byte(\"\")`, b)\n\t}\n}\n\n// go test -v -run=none -bench=^BenchmarkBytesConv -benchmem=true\n\nfunc BenchmarkBytesConvBytesToStrRaw(b *testing.B) {\n\tfor b.Loop() {\n\t\trawBytesToStr(testBytes)\n\t}\n}\n\nfunc BenchmarkBytesConvBytesToStr(b *testing.B) {\n\tfor b.Loop() {\n\t\tBytesToString(testBytes)\n\t}\n}\n\nfunc BenchmarkBytesConvStrToBytesRaw(b *testing.B) {\n\tfor b.Loop() {\n\t\trawStrToBytes(testString)\n\t}\n}\n\nfunc BenchmarkBytesConvStrToBytes(b *testing.B) {\n\tfor b.Loop() {\n\t\tStringToBytes(testString)\n\t}\n}\n"
  },
  {
    "path": "internal/fs/fs.go",
    "content": "package fs\n\nimport (\n\t\"io/fs\"\n\t\"net/http\"\n)\n\n// FileSystem implements an [fs.FS].\ntype FileSystem struct {\n\thttp.FileSystem\n}\n\n// Open passes `Open` to the upstream implementation and return an [fs.File].\nfunc (o FileSystem) Open(name string) (fs.File, error) {\n\tf, err := o.FileSystem.Open(name)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn fs.File(f), nil\n}\n"
  },
  {
    "path": "internal/fs/fs_test.go",
    "content": "package fs\n\nimport (\n\t\"errors\"\n\t\"net/http\"\n\t\"os\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n)\n\ntype mockFileSystem struct {\n\topen func(name string) (http.File, error)\n}\n\nfunc (m *mockFileSystem) Open(name string) (http.File, error) {\n\treturn m.open(name)\n}\n\nfunc TestFileSystem_Open(t *testing.T) {\n\tvar testFile *os.File\n\tmockFS := &mockFileSystem{\n\t\topen: func(name string) (http.File, error) {\n\t\t\treturn testFile, nil\n\t\t},\n\t}\n\tfs := &FileSystem{mockFS}\n\n\tfile, err := fs.Open(\"foo\")\n\n\trequire.NoError(t, err)\n\tassert.Equal(t, testFile, file)\n}\n\nfunc TestFileSystem_Open_err(t *testing.T) {\n\ttestError := errors.New(\"mock\")\n\tmockFS := &mockFileSystem{\n\t\topen: func(_ string) (http.File, error) {\n\t\t\treturn nil, testError\n\t\t},\n\t}\n\tfs := &FileSystem{mockFS}\n\n\tfile, err := fs.Open(\"foo\")\n\n\trequire.ErrorIs(t, err, testError)\n\tassert.Nil(t, file)\n}\n"
  },
  {
    "path": "logger.go",
    "content": "// Copyright 2014 Manu Martinez-Almeida. All rights reserved.\n// Use of this source code is governed by a MIT style\n// license that can be found in the LICENSE file.\n\npackage gin\n\nimport (\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"os\"\n\t\"time\"\n\n\t\"github.com/mattn/go-isatty\"\n)\n\ntype consoleColorModeValue int\n\nconst (\n\tautoColor consoleColorModeValue = iota\n\tdisableColor\n\tforceColor\n)\n\nconst (\n\tgreen   = \"\\033[97;42m\"\n\twhite   = \"\\033[90;47m\"\n\tyellow  = \"\\033[90;43m\"\n\tred     = \"\\033[97;41m\"\n\tblue    = \"\\033[97;44m\"\n\tmagenta = \"\\033[97;45m\"\n\tcyan    = \"\\033[97;46m\"\n\treset   = \"\\033[0m\"\n)\n\nvar consoleColorMode = autoColor\n\n// LoggerConfig defines the config for Logger middleware.\ntype LoggerConfig struct {\n\t// Optional. Default value is gin.defaultLogFormatter\n\tFormatter LogFormatter\n\n\t// Output is a writer where logs are written.\n\t// Optional. Default value is gin.DefaultWriter.\n\tOutput io.Writer\n\n\t// SkipPaths is a URL path array which logs are not written.\n\t// Optional.\n\tSkipPaths []string\n\n\t// SkipQueryString indicates that query strings should not be written\n\t// for cases such as when API keys are passed via query strings.\n\t// Optional. Default value is false.\n\tSkipQueryString bool\n\n\t// Skip is a Skipper that indicates which logs should not be written.\n\t// Optional.\n\tSkip Skipper\n}\n\n// Skipper is a function to skip logs based on provided Context\ntype Skipper func(c *Context) bool\n\n// LogFormatter gives the signature of the formatter function passed to LoggerWithFormatter\ntype LogFormatter func(params LogFormatterParams) string\n\n// LogFormatterParams is the structure any formatter will be handed when time to log comes\ntype LogFormatterParams struct {\n\tRequest *http.Request\n\n\t// TimeStamp shows the time after the server returns a response.\n\tTimeStamp time.Time\n\t// StatusCode is HTTP response code.\n\tStatusCode int\n\t// Latency is how much time the server cost to process a certain request.\n\tLatency time.Duration\n\t// ClientIP equals Context's ClientIP method.\n\tClientIP string\n\t// Method is the HTTP method given to the request.\n\tMethod string\n\t// Path is a path the client requests.\n\tPath string\n\t// ErrorMessage is set if error has occurred in processing the request.\n\tErrorMessage string\n\t// isTerm shows whether gin's output descriptor refers to a terminal.\n\tisTerm bool\n\t// BodySize is the size of the Response Body\n\tBodySize int\n\t// Keys are the keys set on the request's context.\n\tKeys map[any]any\n}\n\n// StatusCodeColor is the ANSI color for appropriately logging http status code to a terminal.\nfunc (p *LogFormatterParams) StatusCodeColor() string {\n\tcode := p.StatusCode\n\n\tswitch {\n\tcase code >= http.StatusContinue && code < http.StatusOK:\n\t\treturn white\n\tcase code >= http.StatusOK && code < http.StatusMultipleChoices:\n\t\treturn green\n\tcase code >= http.StatusMultipleChoices && code < http.StatusBadRequest:\n\t\treturn white\n\tcase code >= http.StatusBadRequest && code < http.StatusInternalServerError:\n\t\treturn yellow\n\tdefault:\n\t\treturn red\n\t}\n}\n\n// LatencyColor is the ANSI color for latency\nfunc (p *LogFormatterParams) LatencyColor() string {\n\tlatency := p.Latency\n\tswitch {\n\tcase latency < time.Millisecond*100:\n\t\treturn white\n\tcase latency < time.Millisecond*200:\n\t\treturn green\n\tcase latency < time.Millisecond*300:\n\t\treturn cyan\n\tcase latency < time.Millisecond*500:\n\t\treturn blue\n\tcase latency < time.Second:\n\t\treturn yellow\n\tcase latency < time.Second*2:\n\t\treturn magenta\n\tdefault:\n\t\treturn red\n\t}\n}\n\n// MethodColor is the ANSI color for appropriately logging http method to a terminal.\nfunc (p *LogFormatterParams) MethodColor() string {\n\tmethod := p.Method\n\n\tswitch method {\n\tcase http.MethodGet:\n\t\treturn blue\n\tcase http.MethodPost:\n\t\treturn cyan\n\tcase http.MethodPut:\n\t\treturn yellow\n\tcase http.MethodDelete:\n\t\treturn red\n\tcase http.MethodPatch:\n\t\treturn green\n\tcase http.MethodHead:\n\t\treturn magenta\n\tcase http.MethodOptions:\n\t\treturn white\n\tdefault:\n\t\treturn reset\n\t}\n}\n\n// ResetColor resets all escape attributes.\nfunc (p *LogFormatterParams) ResetColor() string {\n\treturn reset\n}\n\n// IsOutputColor indicates whether can colors be outputted to the log.\nfunc (p *LogFormatterParams) IsOutputColor() bool {\n\treturn consoleColorMode == forceColor || (consoleColorMode == autoColor && p.isTerm)\n}\n\n// defaultLogFormatter is the default log format function Logger middleware uses.\nvar defaultLogFormatter = func(param LogFormatterParams) string {\n\tvar statusColor, methodColor, resetColor, latencyColor string\n\tif param.IsOutputColor() {\n\t\tstatusColor = param.StatusCodeColor()\n\t\tmethodColor = param.MethodColor()\n\t\tresetColor = param.ResetColor()\n\t\tlatencyColor = param.LatencyColor()\n\t}\n\n\tswitch {\n\tcase param.Latency > time.Minute:\n\t\tparam.Latency = param.Latency.Truncate(time.Second * 10)\n\tcase param.Latency > time.Second:\n\t\tparam.Latency = param.Latency.Truncate(time.Millisecond * 10)\n\tcase param.Latency > time.Millisecond:\n\t\tparam.Latency = param.Latency.Truncate(time.Microsecond * 10)\n\t}\n\n\treturn fmt.Sprintf(\"[GIN] %v |%s %3d %s|%s %8v %s| %15s |%s %-7s %s %#v\\n%s\",\n\t\tparam.TimeStamp.Format(\"2006/01/02 - 15:04:05\"),\n\t\tstatusColor, param.StatusCode, resetColor,\n\t\tlatencyColor, param.Latency, resetColor,\n\t\tparam.ClientIP,\n\t\tmethodColor, param.Method, resetColor,\n\t\tparam.Path,\n\t\tparam.ErrorMessage,\n\t)\n}\n\n// DisableConsoleColor disables color output in the console.\nfunc DisableConsoleColor() {\n\tconsoleColorMode = disableColor\n}\n\n// ForceConsoleColor force color output in the console.\nfunc ForceConsoleColor() {\n\tconsoleColorMode = forceColor\n}\n\n// ErrorLogger returns a HandlerFunc for any error type.\nfunc ErrorLogger() HandlerFunc {\n\treturn ErrorLoggerT(ErrorTypeAny)\n}\n\n// ErrorLoggerT returns a HandlerFunc for a given error type.\nfunc ErrorLoggerT(typ ErrorType) HandlerFunc {\n\treturn func(c *Context) {\n\t\tc.Next()\n\t\terrors := c.Errors.ByType(typ)\n\t\tif len(errors) > 0 {\n\t\t\tc.JSON(-1, errors)\n\t\t}\n\t}\n}\n\n// Logger instances a Logger middleware that will write the logs to gin.DefaultWriter.\n// By default, gin.DefaultWriter = os.Stdout.\nfunc Logger() HandlerFunc {\n\treturn LoggerWithConfig(LoggerConfig{})\n}\n\n// LoggerWithFormatter instance a Logger middleware with the specified log format function.\nfunc LoggerWithFormatter(f LogFormatter) HandlerFunc {\n\treturn LoggerWithConfig(LoggerConfig{\n\t\tFormatter: f,\n\t})\n}\n\n// LoggerWithWriter instance a Logger middleware with the specified writer buffer.\n// Example: os.Stdout, a file opened in write mode, a socket...\nfunc LoggerWithWriter(out io.Writer, notlogged ...string) HandlerFunc {\n\treturn LoggerWithConfig(LoggerConfig{\n\t\tOutput:    out,\n\t\tSkipPaths: notlogged,\n\t})\n}\n\n// LoggerWithConfig instance a Logger middleware with config.\nfunc LoggerWithConfig(conf LoggerConfig) HandlerFunc {\n\tformatter := conf.Formatter\n\tif formatter == nil {\n\t\tformatter = defaultLogFormatter\n\t}\n\n\tout := conf.Output\n\tif out == nil {\n\t\tout = DefaultWriter\n\t}\n\n\tnotlogged := conf.SkipPaths\n\n\tisTerm := true\n\n\tif w, ok := out.(*os.File); !ok || os.Getenv(\"TERM\") == \"dumb\" ||\n\t\t(!isatty.IsTerminal(w.Fd()) && !isatty.IsCygwinTerminal(w.Fd())) {\n\t\tisTerm = false\n\t}\n\n\tvar skip map[string]struct{}\n\n\tif length := len(notlogged); length > 0 {\n\t\tskip = make(map[string]struct{}, length)\n\n\t\tfor _, path := range notlogged {\n\t\t\tskip[path] = struct{}{}\n\t\t}\n\t}\n\n\treturn func(c *Context) {\n\t\t// Start timer\n\t\tstart := time.Now()\n\t\tpath := c.Request.URL.Path\n\t\traw := c.Request.URL.RawQuery\n\n\t\t// Process request\n\t\tc.Next()\n\n\t\t// Log only when it is not being skipped\n\t\tif _, ok := skip[path]; ok || (conf.Skip != nil && conf.Skip(c)) {\n\t\t\treturn\n\t\t}\n\n\t\tparam := LogFormatterParams{\n\t\t\tRequest: c.Request,\n\t\t\tisTerm:  isTerm,\n\t\t\tKeys:    c.Keys,\n\t\t}\n\n\t\t// Stop timer\n\t\tparam.TimeStamp = time.Now()\n\t\tparam.Latency = param.TimeStamp.Sub(start)\n\n\t\tparam.ClientIP = c.ClientIP()\n\t\tparam.Method = c.Request.Method\n\t\tparam.StatusCode = c.Writer.Status()\n\t\tparam.ErrorMessage = c.Errors.ByType(ErrorTypePrivate).String()\n\n\t\tparam.BodySize = c.Writer.Size()\n\n\t\tif raw != \"\" && !conf.SkipQueryString {\n\t\t\tpath = path + \"?\" + raw\n\t\t}\n\n\t\tparam.Path = path\n\n\t\tfmt.Fprint(out, formatter(param))\n\t}\n}\n"
  },
  {
    "path": "logger_test.go",
    "content": "// Copyright 2014 Manu Martinez-Almeida. All rights reserved.\n// Use of this source code is governed by a MIT style\n// license that can be found in the LICENSE file.\n\npackage gin\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"strings\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/stretchr/testify/assert\"\n)\n\nfunc init() {\n\tSetMode(TestMode)\n}\n\nfunc TestLogger(t *testing.T) {\n\tbuffer := new(strings.Builder)\n\trouter := New()\n\trouter.Use(LoggerWithWriter(buffer))\n\trouter.GET(\"/example\", func(c *Context) {})\n\trouter.POST(\"/example\", func(c *Context) {})\n\trouter.PUT(\"/example\", func(c *Context) {})\n\trouter.DELETE(\"/example\", func(c *Context) {})\n\trouter.PATCH(\"/example\", func(c *Context) {})\n\trouter.HEAD(\"/example\", func(c *Context) {})\n\trouter.OPTIONS(\"/example\", func(c *Context) {})\n\n\tPerformRequest(router, http.MethodGet, \"/example?a=100\")\n\tassert.Contains(t, buffer.String(), \"200\")\n\tassert.Contains(t, buffer.String(), http.MethodGet)\n\tassert.Contains(t, buffer.String(), \"/example\")\n\tassert.Contains(t, buffer.String(), \"a=100\")\n\n\t// I wrote these first (extending the above) but then realized they are more\n\t// like integration tests because they test the whole logging process rather\n\t// than individual functions. I'm not sure where these should go.\n\tbuffer.Reset()\n\tPerformRequest(router, http.MethodPost, \"/example\")\n\tassert.Contains(t, buffer.String(), \"200\")\n\tassert.Contains(t, buffer.String(), http.MethodPost)\n\tassert.Contains(t, buffer.String(), \"/example\")\n\n\tbuffer.Reset()\n\tPerformRequest(router, http.MethodPut, \"/example\")\n\tassert.Contains(t, buffer.String(), \"200\")\n\tassert.Contains(t, buffer.String(), http.MethodPut)\n\tassert.Contains(t, buffer.String(), \"/example\")\n\n\tbuffer.Reset()\n\tPerformRequest(router, http.MethodDelete, \"/example\")\n\tassert.Contains(t, buffer.String(), \"200\")\n\tassert.Contains(t, buffer.String(), http.MethodDelete)\n\tassert.Contains(t, buffer.String(), \"/example\")\n\n\tbuffer.Reset()\n\tPerformRequest(router, \"PATCH\", \"/example\")\n\tassert.Contains(t, buffer.String(), \"200\")\n\tassert.Contains(t, buffer.String(), \"PATCH\")\n\tassert.Contains(t, buffer.String(), \"/example\")\n\n\tbuffer.Reset()\n\tPerformRequest(router, \"HEAD\", \"/example\")\n\tassert.Contains(t, buffer.String(), \"200\")\n\tassert.Contains(t, buffer.String(), \"HEAD\")\n\tassert.Contains(t, buffer.String(), \"/example\")\n\n\tbuffer.Reset()\n\tPerformRequest(router, \"OPTIONS\", \"/example\")\n\tassert.Contains(t, buffer.String(), \"200\")\n\tassert.Contains(t, buffer.String(), \"OPTIONS\")\n\tassert.Contains(t, buffer.String(), \"/example\")\n\n\tbuffer.Reset()\n\tPerformRequest(router, http.MethodGet, \"/notfound\")\n\tassert.Contains(t, buffer.String(), \"404\")\n\tassert.Contains(t, buffer.String(), http.MethodGet)\n\tassert.Contains(t, buffer.String(), \"/notfound\")\n}\n\nfunc TestLoggerWithConfig(t *testing.T) {\n\tbuffer := new(strings.Builder)\n\trouter := New()\n\trouter.Use(LoggerWithConfig(LoggerConfig{Output: buffer}))\n\trouter.GET(\"/example\", func(c *Context) {})\n\trouter.POST(\"/example\", func(c *Context) {})\n\trouter.PUT(\"/example\", func(c *Context) {})\n\trouter.DELETE(\"/example\", func(c *Context) {})\n\trouter.PATCH(\"/example\", func(c *Context) {})\n\trouter.HEAD(\"/example\", func(c *Context) {})\n\trouter.OPTIONS(\"/example\", func(c *Context) {})\n\n\tPerformRequest(router, http.MethodGet, \"/example?a=100\")\n\tassert.Contains(t, buffer.String(), \"200\")\n\tassert.Contains(t, buffer.String(), http.MethodGet)\n\tassert.Contains(t, buffer.String(), \"/example\")\n\tassert.Contains(t, buffer.String(), \"a=100\")\n\n\t// I wrote these first (extending the above) but then realized they are more\n\t// like integration tests because they test the whole logging process rather\n\t// than individual functions. I'm not sure where these should go.\n\tbuffer.Reset()\n\tPerformRequest(router, http.MethodPost, \"/example\")\n\tassert.Contains(t, buffer.String(), \"200\")\n\tassert.Contains(t, buffer.String(), http.MethodPost)\n\tassert.Contains(t, buffer.String(), \"/example\")\n\n\tbuffer.Reset()\n\tPerformRequest(router, http.MethodPut, \"/example\")\n\tassert.Contains(t, buffer.String(), \"200\")\n\tassert.Contains(t, buffer.String(), http.MethodPut)\n\tassert.Contains(t, buffer.String(), \"/example\")\n\n\tbuffer.Reset()\n\tPerformRequest(router, http.MethodDelete, \"/example\")\n\tassert.Contains(t, buffer.String(), \"200\")\n\tassert.Contains(t, buffer.String(), http.MethodDelete)\n\tassert.Contains(t, buffer.String(), \"/example\")\n\n\tbuffer.Reset()\n\tPerformRequest(router, \"PATCH\", \"/example\")\n\tassert.Contains(t, buffer.String(), \"200\")\n\tassert.Contains(t, buffer.String(), \"PATCH\")\n\tassert.Contains(t, buffer.String(), \"/example\")\n\n\tbuffer.Reset()\n\tPerformRequest(router, \"HEAD\", \"/example\")\n\tassert.Contains(t, buffer.String(), \"200\")\n\tassert.Contains(t, buffer.String(), \"HEAD\")\n\tassert.Contains(t, buffer.String(), \"/example\")\n\n\tbuffer.Reset()\n\tPerformRequest(router, \"OPTIONS\", \"/example\")\n\tassert.Contains(t, buffer.String(), \"200\")\n\tassert.Contains(t, buffer.String(), \"OPTIONS\")\n\tassert.Contains(t, buffer.String(), \"/example\")\n\n\tbuffer.Reset()\n\tPerformRequest(router, http.MethodGet, \"/notfound\")\n\tassert.Contains(t, buffer.String(), \"404\")\n\tassert.Contains(t, buffer.String(), http.MethodGet)\n\tassert.Contains(t, buffer.String(), \"/notfound\")\n}\n\nfunc TestLoggerWithFormatter(t *testing.T) {\n\tbuffer := new(strings.Builder)\n\n\td := DefaultWriter\n\tDefaultWriter = buffer\n\tdefer func() {\n\t\tDefaultWriter = d\n\t}()\n\n\trouter := New()\n\trouter.Use(LoggerWithFormatter(func(param LogFormatterParams) string {\n\t\treturn fmt.Sprintf(\"[FORMATTER TEST] %v | %3d | %13v | %15s | %-7s %#v\\n%s\",\n\t\t\tparam.TimeStamp.Format(\"2006/01/02 - 15:04:05\"),\n\t\t\tparam.StatusCode,\n\t\t\tparam.Latency,\n\t\t\tparam.ClientIP,\n\t\t\tparam.Method,\n\t\t\tparam.Path,\n\t\t\tparam.ErrorMessage,\n\t\t)\n\t}))\n\trouter.GET(\"/example\", func(c *Context) {})\n\tPerformRequest(router, http.MethodGet, \"/example?a=100\")\n\n\t// output test\n\tassert.Contains(t, buffer.String(), \"[FORMATTER TEST]\")\n\tassert.Contains(t, buffer.String(), \"200\")\n\tassert.Contains(t, buffer.String(), http.MethodGet)\n\tassert.Contains(t, buffer.String(), \"/example\")\n\tassert.Contains(t, buffer.String(), \"a=100\")\n}\n\nfunc TestLoggerWithConfigFormatting(t *testing.T) {\n\tvar gotParam LogFormatterParams\n\tvar gotKeys map[any]any\n\tbuffer := new(strings.Builder)\n\n\trouter := New()\n\trouter.engine.trustedCIDRs, _ = router.engine.prepareTrustedCIDRs()\n\n\trouter.Use(LoggerWithConfig(LoggerConfig{\n\t\tOutput: buffer,\n\t\tFormatter: func(param LogFormatterParams) string {\n\t\t\t// for assert test\n\t\t\tgotParam = param\n\n\t\t\treturn fmt.Sprintf(\"[FORMATTER TEST] %v | %3d | %13v | %15s | %-7s %s\\n%s\",\n\t\t\t\tparam.TimeStamp.Format(\"2006/01/02 - 15:04:05\"),\n\t\t\t\tparam.StatusCode,\n\t\t\t\tparam.Latency,\n\t\t\t\tparam.ClientIP,\n\t\t\t\tparam.Method,\n\t\t\t\tparam.Path,\n\t\t\t\tparam.ErrorMessage,\n\t\t\t)\n\t\t},\n\t}))\n\trouter.GET(\"/example\", func(c *Context) {\n\t\t// set dummy ClientIP\n\t\tc.Request.Header.Set(\"X-Forwarded-For\", \"20.20.20.20\")\n\t\tgotKeys = c.Keys\n\t\ttime.Sleep(time.Millisecond)\n\t})\n\tPerformRequest(router, http.MethodGet, \"/example?a=100\")\n\n\t// output test\n\tassert.Contains(t, buffer.String(), \"[FORMATTER TEST]\")\n\tassert.Contains(t, buffer.String(), \"200\")\n\tassert.Contains(t, buffer.String(), http.MethodGet)\n\tassert.Contains(t, buffer.String(), \"/example\")\n\tassert.Contains(t, buffer.String(), \"a=100\")\n\n\t// LogFormatterParams test\n\tassert.NotNil(t, gotParam.Request)\n\tassert.NotEmpty(t, gotParam.TimeStamp)\n\tassert.Equal(t, 200, gotParam.StatusCode)\n\tassert.NotEmpty(t, gotParam.Latency)\n\tassert.Equal(t, \"20.20.20.20\", gotParam.ClientIP)\n\tassert.Equal(t, http.MethodGet, gotParam.Method)\n\tassert.Equal(t, \"/example?a=100\", gotParam.Path)\n\tassert.Empty(t, gotParam.ErrorMessage)\n\tassert.Equal(t, gotKeys, gotParam.Keys)\n}\n\nfunc TestDefaultLogFormatter(t *testing.T) {\n\ttimeStamp := time.Unix(1544173902, 0).UTC()\n\n\ttermFalseParam := LogFormatterParams{\n\t\tTimeStamp:    timeStamp,\n\t\tStatusCode:   200,\n\t\tLatency:      time.Second * 5,\n\t\tClientIP:     \"20.20.20.20\",\n\t\tMethod:       http.MethodGet,\n\t\tPath:         \"/\",\n\t\tErrorMessage: \"\",\n\t\tisTerm:       false,\n\t}\n\n\ttermTrueParam := LogFormatterParams{\n\t\tTimeStamp:    timeStamp,\n\t\tStatusCode:   200,\n\t\tLatency:      time.Second * 5,\n\t\tClientIP:     \"20.20.20.20\",\n\t\tMethod:       http.MethodGet,\n\t\tPath:         \"/\",\n\t\tErrorMessage: \"\",\n\t\tisTerm:       true,\n\t}\n\ttermTrueLongDurationParam := LogFormatterParams{\n\t\tTimeStamp:    timeStamp,\n\t\tStatusCode:   200,\n\t\tLatency:      time.Millisecond * 9876543210,\n\t\tClientIP:     \"20.20.20.20\",\n\t\tMethod:       http.MethodGet,\n\t\tPath:         \"/\",\n\t\tErrorMessage: \"\",\n\t\tisTerm:       true,\n\t}\n\n\ttermFalseLongDurationParam := LogFormatterParams{\n\t\tTimeStamp:    timeStamp,\n\t\tStatusCode:   200,\n\t\tLatency:      time.Millisecond * 9876543210,\n\t\tClientIP:     \"20.20.20.20\",\n\t\tMethod:       http.MethodGet,\n\t\tPath:         \"/\",\n\t\tErrorMessage: \"\",\n\t\tisTerm:       false,\n\t}\n\n\tassert.Equal(t, \"[GIN] 2018/12/07 - 09:11:42 | 200 |       5s |     20.20.20.20 | GET      \\\"/\\\"\\n\", defaultLogFormatter(termFalseParam))\n\tassert.Equal(t, \"[GIN] 2018/12/07 - 09:11:42 | 200 | 2743h29m0s |     20.20.20.20 | GET      \\\"/\\\"\\n\", defaultLogFormatter(termFalseLongDurationParam))\n\n\tassert.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))\n\tassert.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))\n}\n\nfunc TestColorForMethod(t *testing.T) {\n\tcolorForMethod := func(method string) string {\n\t\tp := LogFormatterParams{\n\t\t\tMethod: method,\n\t\t}\n\t\treturn p.MethodColor()\n\t}\n\n\tassert.Equal(t, blue, colorForMethod(http.MethodGet), \"get should be blue\")\n\tassert.Equal(t, cyan, colorForMethod(http.MethodPost), \"post should be cyan\")\n\tassert.Equal(t, yellow, colorForMethod(http.MethodPut), \"put should be yellow\")\n\tassert.Equal(t, red, colorForMethod(http.MethodDelete), \"delete should be red\")\n\tassert.Equal(t, green, colorForMethod(\"PATCH\"), \"patch should be green\")\n\tassert.Equal(t, magenta, colorForMethod(\"HEAD\"), \"head should be magenta\")\n\tassert.Equal(t, white, colorForMethod(\"OPTIONS\"), \"options should be white\")\n\tassert.Equal(t, reset, colorForMethod(\"TRACE\"), \"trace is not defined and should be the reset color\")\n}\n\nfunc TestColorForStatus(t *testing.T) {\n\tcolorForStatus := func(code int) string {\n\t\tp := LogFormatterParams{\n\t\t\tStatusCode: code,\n\t\t}\n\t\treturn p.StatusCodeColor()\n\t}\n\n\tassert.Equal(t, white, colorForStatus(http.StatusContinue), \"1xx should be white\")\n\tassert.Equal(t, green, colorForStatus(http.StatusOK), \"2xx should be green\")\n\tassert.Equal(t, white, colorForStatus(http.StatusMovedPermanently), \"3xx should be white\")\n\tassert.Equal(t, yellow, colorForStatus(http.StatusNotFound), \"4xx should be yellow\")\n\tassert.Equal(t, red, colorForStatus(2), \"other things should be red\")\n}\n\nfunc TestColorForLatency(t *testing.T) {\n\tcolorForLatency := func(latency time.Duration) string {\n\t\tp := LogFormatterParams{\n\t\t\tLatency: latency,\n\t\t}\n\t\treturn p.LatencyColor()\n\t}\n\n\tassert.Equal(t, white, colorForLatency(time.Duration(0)), \"0 should be white\")\n\tassert.Equal(t, white, colorForLatency(time.Millisecond*20), \"20ms should be white\")\n\tassert.Equal(t, green, colorForLatency(time.Millisecond*150), \"150ms should be green\")\n\tassert.Equal(t, cyan, colorForLatency(time.Millisecond*250), \"250ms should be cyan\")\n\tassert.Equal(t, blue, colorForLatency(time.Millisecond*400), \"400ms should be blue\")\n\tassert.Equal(t, yellow, colorForLatency(time.Millisecond*600), \"600ms should be yellow\")\n\tassert.Equal(t, magenta, colorForLatency(time.Millisecond*1500), \"1.5s should be magenta\")\n\tassert.Equal(t, red, colorForLatency(time.Second*3), \"other things should be red\")\n}\n\nfunc TestResetColor(t *testing.T) {\n\tp := LogFormatterParams{}\n\tassert.Equal(t, string([]byte{27, 91, 48, 109}), p.ResetColor())\n}\n\nfunc TestIsOutputColor(t *testing.T) {\n\t// test with isTerm flag true.\n\tp := LogFormatterParams{\n\t\tisTerm: true,\n\t}\n\n\tconsoleColorMode = autoColor\n\tassert.True(t, p.IsOutputColor())\n\n\tForceConsoleColor()\n\tassert.True(t, p.IsOutputColor())\n\n\tDisableConsoleColor()\n\tassert.False(t, p.IsOutputColor())\n\n\t// test with isTerm flag false.\n\tp = LogFormatterParams{\n\t\tisTerm: false,\n\t}\n\n\tconsoleColorMode = autoColor\n\tassert.False(t, p.IsOutputColor())\n\n\tForceConsoleColor()\n\tassert.True(t, p.IsOutputColor())\n\n\tDisableConsoleColor()\n\tassert.False(t, p.IsOutputColor())\n\n\t// reset console color mode.\n\tconsoleColorMode = autoColor\n}\n\nfunc TestErrorLogger(t *testing.T) {\n\trouter := New()\n\trouter.Use(ErrorLogger())\n\trouter.GET(\"/error\", func(c *Context) {\n\t\tc.Error(errors.New(\"this is an error\")) //nolint: errcheck\n\t})\n\trouter.GET(\"/abort\", func(c *Context) {\n\t\tc.AbortWithError(http.StatusUnauthorized, errors.New(\"no authorized\")) //nolint: errcheck\n\t})\n\trouter.GET(\"/print\", func(c *Context) {\n\t\tc.Error(errors.New(\"this is an error\")) //nolint: errcheck\n\t\tc.String(http.StatusInternalServerError, \"hola!\")\n\t})\n\n\tw := PerformRequest(router, http.MethodGet, \"/error\")\n\tassert.Equal(t, http.StatusOK, w.Code)\n\tassert.JSONEq(t, \"{\\\"error\\\":\\\"this is an error\\\"}\", w.Body.String())\n\n\tw = PerformRequest(router, http.MethodGet, \"/abort\")\n\tassert.Equal(t, http.StatusUnauthorized, w.Code)\n\tassert.JSONEq(t, \"{\\\"error\\\":\\\"no authorized\\\"}\", w.Body.String())\n\n\tw = PerformRequest(router, http.MethodGet, \"/print\")\n\tassert.Equal(t, http.StatusInternalServerError, w.Code)\n\tassert.Equal(t, \"hola!{\\\"error\\\":\\\"this is an error\\\"}\", w.Body.String())\n}\n\nfunc TestLoggerWithWriterSkippingPaths(t *testing.T) {\n\tbuffer := new(strings.Builder)\n\trouter := New()\n\trouter.Use(LoggerWithWriter(buffer, \"/skipped\"))\n\trouter.GET(\"/logged\", func(c *Context) {})\n\trouter.GET(\"/skipped\", func(c *Context) {})\n\n\tPerformRequest(router, http.MethodGet, \"/logged\")\n\tassert.Contains(t, buffer.String(), \"200\")\n\n\tbuffer.Reset()\n\tPerformRequest(router, http.MethodGet, \"/skipped\")\n\tassert.Contains(t, buffer.String(), \"\")\n}\n\nfunc TestLoggerWithConfigSkippingPaths(t *testing.T) {\n\tbuffer := new(strings.Builder)\n\trouter := New()\n\trouter.Use(LoggerWithConfig(LoggerConfig{\n\t\tOutput:    buffer,\n\t\tSkipPaths: []string{\"/skipped\"},\n\t}))\n\trouter.GET(\"/logged\", func(c *Context) {})\n\trouter.GET(\"/skipped\", func(c *Context) {})\n\n\tPerformRequest(router, http.MethodGet, \"/logged\")\n\tassert.Contains(t, buffer.String(), \"200\")\n\n\tbuffer.Reset()\n\tPerformRequest(router, http.MethodGet, \"/skipped\")\n\tassert.Contains(t, buffer.String(), \"\")\n}\n\nfunc TestLoggerWithConfigSkipper(t *testing.T) {\n\tbuffer := new(strings.Builder)\n\trouter := New()\n\trouter.Use(LoggerWithConfig(LoggerConfig{\n\t\tOutput: buffer,\n\t\tSkip: func(c *Context) bool {\n\t\t\treturn c.Writer.Status() == http.StatusNoContent\n\t\t},\n\t}))\n\trouter.GET(\"/logged\", func(c *Context) { c.Status(http.StatusOK) })\n\trouter.GET(\"/skipped\", func(c *Context) { c.Status(http.StatusNoContent) })\n\n\tPerformRequest(router, http.MethodGet, \"/logged\")\n\tassert.Contains(t, buffer.String(), \"200\")\n\n\tbuffer.Reset()\n\tPerformRequest(router, http.MethodGet, \"/skipped\")\n\tassert.Contains(t, buffer.String(), \"\")\n}\n\nfunc TestDisableConsoleColor(t *testing.T) {\n\tNew()\n\tassert.Equal(t, autoColor, consoleColorMode)\n\tDisableConsoleColor()\n\tassert.Equal(t, disableColor, consoleColorMode)\n\n\t// reset console color mode.\n\tconsoleColorMode = autoColor\n}\n\nfunc TestForceConsoleColor(t *testing.T) {\n\tNew()\n\tassert.Equal(t, autoColor, consoleColorMode)\n\tForceConsoleColor()\n\tassert.Equal(t, forceColor, consoleColorMode)\n\n\t// reset console color mode.\n\tconsoleColorMode = autoColor\n}\n\nfunc TestLoggerWithConfigSkipQueryString(t *testing.T) {\n\tbuffer := new(strings.Builder)\n\trouter := New()\n\trouter.Use(LoggerWithConfig(LoggerConfig{\n\t\tOutput:          buffer,\n\t\tSkipQueryString: true,\n\t}))\n\trouter.GET(\"/logged\", func(c *Context) { c.Status(http.StatusOK) })\n\n\tPerformRequest(router, \"GET\", \"/logged?a=21\")\n\tassert.Contains(t, buffer.String(), \"200\")\n\tassert.NotContains(t, buffer.String(), \"a=21\")\n}\n"
  },
  {
    "path": "middleware_test.go",
    "content": "// Copyright 2014 Manu Martinez-Almeida. All rights reserved.\n// Use of this source code is governed by a MIT style\n// license that can be found in the LICENSE file.\n\npackage gin\n\nimport (\n\t\"errors\"\n\t\"net/http\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"github.com/gin-contrib/sse\"\n\t\"github.com/stretchr/testify/assert\"\n)\n\nfunc TestMiddlewareGeneralCase(t *testing.T) {\n\tsignature := \"\"\n\trouter := New()\n\trouter.Use(func(c *Context) {\n\t\tsignature += \"A\"\n\t\tc.Next()\n\t\tsignature += \"B\"\n\t})\n\trouter.Use(func(c *Context) {\n\t\tsignature += \"C\"\n\t})\n\trouter.GET(\"/\", func(c *Context) {\n\t\tsignature += \"D\"\n\t})\n\trouter.NoRoute(func(c *Context) {\n\t\tsignature += \" X \"\n\t})\n\trouter.NoMethod(func(c *Context) {\n\t\tsignature += \" XX \"\n\t})\n\t// RUN\n\tw := PerformRequest(router, http.MethodGet, \"/\")\n\n\t// TEST\n\tassert.Equal(t, http.StatusOK, w.Code)\n\tassert.Equal(t, \"ACDB\", signature)\n}\n\nfunc TestMiddlewareNoRoute(t *testing.T) {\n\tsignature := \"\"\n\trouter := New()\n\trouter.Use(func(c *Context) {\n\t\tsignature += \"A\"\n\t\tc.Next()\n\t\tsignature += \"B\"\n\t})\n\trouter.Use(func(c *Context) {\n\t\tsignature += \"C\"\n\t\tc.Next()\n\t\tc.Next()\n\t\tc.Next()\n\t\tc.Next()\n\t\tsignature += \"D\"\n\t})\n\trouter.NoRoute(func(c *Context) {\n\t\tsignature += \"E\"\n\t\tc.Next()\n\t\tsignature += \"F\"\n\t}, func(c *Context) {\n\t\tsignature += \"G\"\n\t\tc.Next()\n\t\tsignature += \"H\"\n\t})\n\trouter.NoMethod(func(c *Context) {\n\t\tsignature += \" X \"\n\t})\n\t// RUN\n\tw := PerformRequest(router, http.MethodGet, \"/\")\n\n\t// TEST\n\tassert.Equal(t, http.StatusNotFound, w.Code)\n\tassert.Equal(t, \"ACEGHFDB\", signature)\n}\n\nfunc TestMiddlewareNoMethodEnabled(t *testing.T) {\n\tsignature := \"\"\n\trouter := New()\n\trouter.HandleMethodNotAllowed = true\n\trouter.Use(func(c *Context) {\n\t\tsignature += \"A\"\n\t\tc.Next()\n\t\tsignature += \"B\"\n\t})\n\trouter.Use(func(c *Context) {\n\t\tsignature += \"C\"\n\t\tc.Next()\n\t\tsignature += \"D\"\n\t})\n\trouter.NoMethod(func(c *Context) {\n\t\tsignature += \"E\"\n\t\tc.Next()\n\t\tsignature += \"F\"\n\t}, func(c *Context) {\n\t\tsignature += \"G\"\n\t\tc.Next()\n\t\tsignature += \"H\"\n\t})\n\trouter.NoRoute(func(c *Context) {\n\t\tsignature += \" X \"\n\t})\n\trouter.POST(\"/\", func(c *Context) {\n\t\tsignature += \" XX \"\n\t})\n\t// RUN\n\tw := PerformRequest(router, http.MethodGet, \"/\")\n\n\t// TEST\n\tassert.Equal(t, http.StatusMethodNotAllowed, w.Code)\n\tassert.Equal(t, \"ACEGHFDB\", signature)\n}\n\nfunc TestMiddlewareNoMethodDisabled(t *testing.T) {\n\tsignature := \"\"\n\trouter := New()\n\n\t// NoMethod disabled\n\trouter.HandleMethodNotAllowed = false\n\n\trouter.Use(func(c *Context) {\n\t\tsignature += \"A\"\n\t\tc.Next()\n\t\tsignature += \"B\"\n\t})\n\trouter.Use(func(c *Context) {\n\t\tsignature += \"C\"\n\t\tc.Next()\n\t\tsignature += \"D\"\n\t})\n\trouter.NoMethod(func(c *Context) {\n\t\tsignature += \"E\"\n\t\tc.Next()\n\t\tsignature += \"F\"\n\t}, func(c *Context) {\n\t\tsignature += \"G\"\n\t\tc.Next()\n\t\tsignature += \"H\"\n\t})\n\trouter.NoRoute(func(c *Context) {\n\t\tsignature += \" X \"\n\t})\n\trouter.POST(\"/\", func(c *Context) {\n\t\tsignature += \" XX \"\n\t})\n\n\t// RUN\n\tw := PerformRequest(router, http.MethodGet, \"/\")\n\n\t// TEST\n\tassert.Equal(t, http.StatusNotFound, w.Code)\n\tassert.Equal(t, \"AC X DB\", signature)\n}\n\nfunc TestMiddlewareAbort(t *testing.T) {\n\tsignature := \"\"\n\trouter := New()\n\trouter.Use(func(c *Context) {\n\t\tsignature += \"A\"\n\t})\n\trouter.Use(func(c *Context) {\n\t\tsignature += \"C\"\n\t\tc.AbortWithStatus(http.StatusUnauthorized)\n\t\tc.Next()\n\t\tsignature += \"D\"\n\t})\n\trouter.GET(\"/\", func(c *Context) {\n\t\tsignature += \" X \"\n\t\tc.Next()\n\t\tsignature += \" XX \"\n\t})\n\n\t// RUN\n\tw := PerformRequest(router, http.MethodGet, \"/\")\n\n\t// TEST\n\tassert.Equal(t, http.StatusUnauthorized, w.Code)\n\tassert.Equal(t, \"ACD\", signature)\n}\n\nfunc TestMiddlewareAbortHandlersChainAndNext(t *testing.T) {\n\tsignature := \"\"\n\trouter := New()\n\trouter.Use(func(c *Context) {\n\t\tsignature += \"A\"\n\t\tc.Next()\n\t\tc.AbortWithStatus(http.StatusGone)\n\t\tsignature += \"B\"\n\t})\n\trouter.GET(\"/\", func(c *Context) {\n\t\tsignature += \"C\"\n\t\tc.Next()\n\t})\n\t// RUN\n\tw := PerformRequest(router, http.MethodGet, \"/\")\n\n\t// TEST\n\tassert.Equal(t, http.StatusGone, w.Code)\n\tassert.Equal(t, \"ACB\", signature)\n}\n\n// TestMiddlewareFailHandlersChain - ensure that Fail interrupt used middleware in fifo order as\n// as well as Abort\nfunc TestMiddlewareFailHandlersChain(t *testing.T) {\n\t// SETUP\n\tsignature := \"\"\n\trouter := New()\n\trouter.Use(func(context *Context) {\n\t\tsignature += \"A\"\n\t\tcontext.AbortWithError(http.StatusInternalServerError, errors.New(\"foo\")) //nolint: errcheck\n\t})\n\trouter.Use(func(context *Context) {\n\t\tsignature += \"B\"\n\t\tcontext.Next()\n\t\tsignature += \"C\"\n\t})\n\t// RUN\n\tw := PerformRequest(router, http.MethodGet, \"/\")\n\n\t// TEST\n\tassert.Equal(t, http.StatusInternalServerError, w.Code)\n\tassert.Equal(t, \"A\", signature)\n}\n\nfunc TestMiddlewareWrite(t *testing.T) {\n\trouter := New()\n\trouter.Use(func(c *Context) {\n\t\tc.String(http.StatusBadRequest, \"hola\\n\")\n\t})\n\trouter.Use(func(c *Context) {\n\t\tc.XML(http.StatusBadRequest, H{\"foo\": \"bar\"})\n\t})\n\trouter.Use(func(c *Context) {\n\t\tc.JSON(http.StatusBadRequest, H{\"foo\": \"bar\"})\n\t})\n\trouter.GET(\"/\", func(c *Context) {\n\t\tc.JSON(http.StatusBadRequest, H{\"foo\": \"bar\"})\n\t}, func(c *Context) {\n\t\tc.Render(http.StatusBadRequest, sse.Event{\n\t\t\tEvent: \"test\",\n\t\t\tData:  \"message\",\n\t\t})\n\t})\n\n\tw := PerformRequest(router, http.MethodGet, \"/\")\n\n\tassert.Equal(t, http.StatusBadRequest, w.Code)\n\tassert.Equal(t, strings.ReplaceAll(\"hola\\n<map><foo>bar</foo></map>{\\\"foo\\\":\\\"bar\\\"}{\\\"foo\\\":\\\"bar\\\"}event:test\\ndata:message\\n\\n\", \" \", \"\"), strings.ReplaceAll(w.Body.String(), \" \", \"\"))\n}\n"
  },
  {
    "path": "mode.go",
    "content": "// Copyright 2014 Manu Martinez-Almeida. All rights reserved.\n// Use of this source code is governed by a MIT style\n// license that can be found in the LICENSE file.\n\npackage gin\n\nimport (\n\t\"flag\"\n\t\"io\"\n\t\"os\"\n\t\"sync/atomic\"\n\n\t\"github.com/gin-gonic/gin/binding\"\n)\n\n// EnvGinMode indicates environment name for gin mode.\nconst EnvGinMode = \"GIN_MODE\"\n\nconst (\n\t// DebugMode indicates gin mode is debug.\n\tDebugMode = \"debug\"\n\t// ReleaseMode indicates gin mode is release.\n\tReleaseMode = \"release\"\n\t// TestMode indicates gin mode is test.\n\tTestMode = \"test\"\n)\n\nconst (\n\tdebugCode = iota\n\treleaseCode\n\ttestCode\n)\n\n// DefaultWriter is the default io.Writer used by Gin for debug output and\n// middleware output like Logger() or Recovery().\n// Note that both Logger and Recovery provides custom ways to configure their\n// output io.Writer.\n// To support coloring in Windows use:\n//\n//\timport \"github.com/mattn/go-colorable\"\n//\tgin.DefaultWriter = colorable.NewColorableStdout()\nvar DefaultWriter io.Writer = os.Stdout\n\n// DefaultErrorWriter is the default io.Writer used by Gin to debug errors\nvar DefaultErrorWriter io.Writer = os.Stderr\n\nvar (\n\tginMode  int32 = debugCode\n\tmodeName atomic.Value\n)\n\nfunc init() {\n\tmode := os.Getenv(EnvGinMode)\n\tSetMode(mode)\n}\n\n// SetMode sets gin mode according to input string.\nfunc SetMode(value string) {\n\tif value == \"\" {\n\t\tif flag.Lookup(\"test.v\") != nil {\n\t\t\tvalue = TestMode\n\t\t} else {\n\t\t\tvalue = DebugMode\n\t\t}\n\t}\n\n\tswitch value {\n\tcase DebugMode:\n\t\tatomic.StoreInt32(&ginMode, debugCode)\n\tcase ReleaseMode:\n\t\tatomic.StoreInt32(&ginMode, releaseCode)\n\tcase TestMode:\n\t\tatomic.StoreInt32(&ginMode, testCode)\n\tdefault:\n\t\tpanic(\"gin mode unknown: \" + value + \" (available mode: debug release test)\")\n\t}\n\tmodeName.Store(value)\n}\n\n// DisableBindValidation closes the default validator.\nfunc DisableBindValidation() {\n\tbinding.Validator = nil\n}\n\n// EnableJsonDecoderUseNumber sets true for binding.EnableDecoderUseNumber to\n// call the UseNumber method on the JSON Decoder instance.\nfunc EnableJsonDecoderUseNumber() {\n\tbinding.EnableDecoderUseNumber = true\n}\n\n// EnableJsonDecoderDisallowUnknownFields sets true for binding.EnableDecoderDisallowUnknownFields to\n// call the DisallowUnknownFields method on the JSON Decoder instance.\nfunc EnableJsonDecoderDisallowUnknownFields() {\n\tbinding.EnableDecoderDisallowUnknownFields = true\n}\n\n// Mode returns current gin mode.\nfunc Mode() string {\n\treturn modeName.Load().(string)\n}\n"
  },
  {
    "path": "mode_test.go",
    "content": "// Copyright 2014 Manu Martinez-Almeida. All rights reserved.\n// Use of this source code is governed by a MIT style\n// license that can be found in the LICENSE file.\n\npackage gin\n\nimport (\n\t\"os\"\n\t\"sync/atomic\"\n\t\"testing\"\n\n\t\"github.com/gin-gonic/gin/binding\"\n\t\"github.com/stretchr/testify/assert\"\n)\n\nfunc init() {\n\tos.Setenv(EnvGinMode, TestMode)\n}\n\nfunc TestSetMode(t *testing.T) {\n\tassert.Equal(t, int32(testCode), atomic.LoadInt32(&ginMode))\n\tassert.Equal(t, TestMode, Mode())\n\tos.Unsetenv(EnvGinMode)\n\n\tSetMode(\"\")\n\tassert.Equal(t, int32(testCode), atomic.LoadInt32(&ginMode))\n\tassert.Equal(t, TestMode, Mode())\n\n\tSetMode(DebugMode)\n\tassert.Equal(t, int32(debugCode), atomic.LoadInt32(&ginMode))\n\tassert.Equal(t, DebugMode, Mode())\n\n\tSetMode(ReleaseMode)\n\tassert.Equal(t, int32(releaseCode), atomic.LoadInt32(&ginMode))\n\tassert.Equal(t, ReleaseMode, Mode())\n\n\tSetMode(TestMode)\n\tassert.Equal(t, int32(testCode), atomic.LoadInt32(&ginMode))\n\tassert.Equal(t, TestMode, Mode())\n\n\tassert.Panics(t, func() { SetMode(\"unknown\") })\n}\n\nfunc TestDisableBindValidation(t *testing.T) {\n\tv := binding.Validator\n\tassert.NotNil(t, binding.Validator)\n\tDisableBindValidation()\n\tassert.Nil(t, binding.Validator)\n\tbinding.Validator = v\n}\n\nfunc TestEnableJsonDecoderUseNumber(t *testing.T) {\n\tassert.False(t, binding.EnableDecoderUseNumber)\n\tEnableJsonDecoderUseNumber()\n\tassert.True(t, binding.EnableDecoderUseNumber)\n}\n\nfunc TestEnableJsonDecoderDisallowUnknownFields(t *testing.T) {\n\tassert.False(t, binding.EnableDecoderDisallowUnknownFields)\n\tEnableJsonDecoderDisallowUnknownFields()\n\tassert.True(t, binding.EnableDecoderDisallowUnknownFields)\n}\n"
  },
  {
    "path": "path.go",
    "content": "// Copyright 2013 Julien Schmidt. All rights reserved.\n// Based on the path package, Copyright 2009 The Go Authors.\n// Use of this source code is governed by a BSD-style license that can be found\n// at https://github.com/julienschmidt/httprouter/blob/master/LICENSE.\n\npackage gin\n\nconst stackBufSize = 128\n\n// cleanPath is the URL version of path.Clean, it returns a canonical URL path\n// for p, eliminating . and .. elements.\n//\n// The following rules are applied iteratively until no further processing can\n// be done:\n//  1. Replace multiple slashes with a single slash.\n//  2. Eliminate each . path name element (the current directory).\n//  3. Eliminate each inner .. path name element (the parent directory)\n//     along with the non-.. element that precedes it.\n//  4. Eliminate .. elements that begin a rooted path:\n//     that is, replace \"/..\" by \"/\" at the beginning of a path.\n//\n// If the result of this process is an empty string, \"/\" is returned.\nfunc cleanPath(p string) string {\n\t// Turn empty string into \"/\"\n\tif p == \"\" {\n\t\treturn \"/\"\n\t}\n\n\t// Reasonably sized buffer on stack to avoid allocations in the common case.\n\t// If a larger buffer is required, it gets allocated dynamically.\n\tbuf := make([]byte, 0, stackBufSize)\n\n\tn := len(p)\n\n\t// Invariants:\n\t//      reading from path; r is index of next byte to process.\n\t//      writing to buf; w is index of next byte to write.\n\n\t// path must start with '/'\n\tr := 1\n\tw := 1\n\n\tif p[0] != '/' {\n\t\tr = 0\n\n\t\tif n+1 > stackBufSize {\n\t\t\tbuf = make([]byte, n+1)\n\t\t} else {\n\t\t\tbuf = buf[:n+1]\n\t\t}\n\t\tbuf[0] = '/'\n\t}\n\n\ttrailing := n > 1 && p[n-1] == '/'\n\n\t// A bit more clunky without a 'lazybuf' like the path package, but the loop\n\t// gets completely inlined (bufApp calls).\n\t// loop has no expensive function calls (except 1x make)\t\t// So in contrast to the path package this loop has no expensive function\n\t// calls (except make, if needed).\n\n\tfor r < n {\n\t\tswitch {\n\t\tcase p[r] == '/':\n\t\t\t// empty path element, trailing slash is added after the end\n\t\t\tr++\n\n\t\tcase p[r] == '.' && r+1 == n:\n\t\t\ttrailing = true\n\t\t\tr++\n\n\t\tcase p[r] == '.' && p[r+1] == '/':\n\t\t\t// . element\n\t\t\tr += 2\n\n\t\tcase p[r] == '.' && p[r+1] == '.' && (r+2 == n || p[r+2] == '/'):\n\t\t\t// .. element: remove to last /\n\t\t\tr += 3\n\n\t\t\tif w > 1 {\n\t\t\t\t// can backtrack\n\t\t\t\tw--\n\n\t\t\t\tif len(buf) == 0 {\n\t\t\t\t\tfor w > 1 && p[w] != '/' {\n\t\t\t\t\t\tw--\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\tfor w > 1 && buf[w] != '/' {\n\t\t\t\t\t\tw--\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\tdefault:\n\t\t\t// Real path element.\n\t\t\t// Add slash if needed\n\t\t\tif w > 1 {\n\t\t\t\tbufApp(&buf, p, w, '/')\n\t\t\t\tw++\n\t\t\t}\n\n\t\t\t// Copy element\n\t\t\tfor r < n && p[r] != '/' {\n\t\t\t\tbufApp(&buf, p, w, p[r])\n\t\t\t\tw++\n\t\t\t\tr++\n\t\t\t}\n\t\t}\n\t}\n\n\t// Re-append trailing slash\n\tif trailing && w > 1 {\n\t\tbufApp(&buf, p, w, '/')\n\t\tw++\n\t}\n\n\t// If the original string was not modified (or only shortened at the end),\n\t// return the respective substring of the original string.\n\t// Otherwise return a new string from the buffer.\n\tif len(buf) == 0 {\n\t\treturn p[:w]\n\t}\n\treturn string(buf[:w])\n}\n\n// Internal helper to lazily create a buffer if necessary.\n// Calls to this function get inlined.\nfunc bufApp(buf *[]byte, s string, w int, c byte) {\n\tb := *buf\n\tif len(b) == 0 {\n\t\t// No modification of the original string so far.\n\t\t// If the next character is the same as in the original string, we do\n\t\t// not yet have to allocate a buffer.\n\t\tif s[w] == c {\n\t\t\treturn\n\t\t}\n\n\t\t// Otherwise use either the stack buffer, if it is large enough, or\n\t\t// allocate a new buffer on the heap, and copy all previous characters.\n\t\tlength := len(s)\n\t\tif length > cap(b) {\n\t\t\t*buf = make([]byte, length)\n\t\t} else {\n\t\t\t*buf = (*buf)[:length]\n\t\t}\n\t\tb = *buf\n\n\t\tcopy(b, s[:w])\n\t}\n\tb[w] = c\n}\n\n// removeRepeatedChar removes multiple consecutive 'char's from a string.\n// if s == \"/a//b///c////\" && char == '/', it returns \"/a/b/c/\"\nfunc removeRepeatedChar(s string, char byte) string {\n\t// Check if there are any consecutive chars\n\thasRepeatedChar := false\n\tfor i := 1; i < len(s); i++ {\n\t\tif s[i] == char && s[i-1] == char {\n\t\t\thasRepeatedChar = true\n\t\t\tbreak\n\t\t}\n\t}\n\tif !hasRepeatedChar {\n\t\treturn s\n\t}\n\n\t// Reasonably sized buffer on stack to avoid allocations in the common case.\n\tbuf := make([]byte, 0, stackBufSize)\n\n\t// Invariants:\n\t//      reading from s; r is index of next byte to process.\n\t//      writing to buf; w is index of next byte to write.\n\tr := 0\n\tw := 0\n\n\tfor n := len(s); r < n; {\n\t\tif s[r] == char {\n\t\t\t// Write the first char\n\t\t\tbufApp(&buf, s, w, char)\n\t\t\tw++\n\t\t\tr++\n\n\t\t\t// Skip all consecutive chars\n\t\t\tfor r < n && s[r] == char {\n\t\t\t\tr++\n\t\t\t}\n\t\t} else {\n\t\t\t// Copy non-char character\n\t\t\tbufApp(&buf, s, w, s[r])\n\t\t\tw++\n\t\t\tr++\n\t\t}\n\t}\n\n\t// If the original string was not modified (or only shortened at the end),\n\t// return the respective substring of the original string.\n\t// Otherwise, return a new string from the buffer.\n\tif len(buf) == 0 {\n\t\treturn s[:w]\n\t}\n\treturn string(buf[:w])\n}\n"
  },
  {
    "path": "path_test.go",
    "content": "// Copyright 2013 Julien Schmidt. All rights reserved.\n// Based on the path package, Copyright 2009 The Go Authors.\n// Use of this source code is governed by a BSD-style license that can be found\n// at https://github.com/julienschmidt/httprouter/blob/master/LICENSE\n\npackage gin\n\nimport (\n\t\"runtime\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n)\n\ntype cleanPathTest struct {\n\tpath, result string\n}\n\nvar cleanTests = []cleanPathTest{\n\t// Already clean\n\t{\"/\", \"/\"},\n\t{\"/abc\", \"/abc\"},\n\t{\"/a/b/c\", \"/a/b/c\"},\n\t{\"/abc/\", \"/abc/\"},\n\t{\"/a/b/c/\", \"/a/b/c/\"},\n\n\t// missing root\n\t{\"\", \"/\"},\n\t{\"a/\", \"/a/\"},\n\t{\"abc\", \"/abc\"},\n\t{\"abc/def\", \"/abc/def\"},\n\t{\"a/b/c\", \"/a/b/c\"},\n\n\t// Remove doubled slash\n\t{\"//\", \"/\"},\n\t{\"/abc//\", \"/abc/\"},\n\t{\"/abc/def//\", \"/abc/def/\"},\n\t{\"/a/b/c//\", \"/a/b/c/\"},\n\t{\"/abc//def//ghi\", \"/abc/def/ghi\"},\n\t{\"//abc\", \"/abc\"},\n\t{\"///abc\", \"/abc\"},\n\t{\"//abc//\", \"/abc/\"},\n\n\t// Remove . elements\n\t{\".\", \"/\"},\n\t{\"./\", \"/\"},\n\t{\"/abc/./def\", \"/abc/def\"},\n\t{\"/./abc/def\", \"/abc/def\"},\n\t{\"/abc/.\", \"/abc/\"},\n\n\t// Remove .. elements\n\t{\"..\", \"/\"},\n\t{\"../\", \"/\"},\n\t{\"../../\", \"/\"},\n\t{\"../..\", \"/\"},\n\t{\"../../abc\", \"/abc\"},\n\t{\"/abc/def/ghi/../jkl\", \"/abc/def/jkl\"},\n\t{\"/abc/def/../ghi/../jkl\", \"/abc/jkl\"},\n\t{\"/abc/def/..\", \"/abc\"},\n\t{\"/abc/def/../..\", \"/\"},\n\t{\"/abc/def/../../..\", \"/\"},\n\t{\"/abc/def/../../..\", \"/\"},\n\t{\"/abc/def/../../../ghi/jkl/../../../mno\", \"/mno\"},\n\n\t// Combinations\n\t{\"abc/./../def\", \"/def\"},\n\t{\"abc//./../def\", \"/def\"},\n\t{\"abc/../../././../def\", \"/def\"},\n}\n\nfunc TestPathClean(t *testing.T) {\n\tfor _, test := range cleanTests {\n\t\tassert.Equal(t, test.result, cleanPath(test.path))\n\t\tassert.Equal(t, test.result, cleanPath(test.result))\n\t}\n}\n\nfunc TestPathCleanMallocs(t *testing.T) {\n\tif testing.Short() {\n\t\tt.Skip(\"skipping malloc count in short mode\")\n\t}\n\n\tif runtime.GOMAXPROCS(0) > 1 {\n\t\tt.Skip(\"skipping malloc count; GOMAXPROCS>1\")\n\t}\n\n\tfor _, test := range cleanTests {\n\t\tallocs := testing.AllocsPerRun(100, func() { cleanPath(test.result) })\n\t\tassert.InDelta(t, 0, allocs, 0.01)\n\t}\n}\n\nfunc BenchmarkPathClean(b *testing.B) {\n\tb.ReportAllocs()\n\n\tfor b.Loop() {\n\t\tfor _, test := range cleanTests {\n\t\t\tcleanPath(test.path)\n\t\t}\n\t}\n}\n\nfunc genLongPaths() (testPaths []cleanPathTest) {\n\tfor i := 1; i <= 1234; i++ {\n\t\tss := strings.Repeat(\"a\", i)\n\n\t\tcorrectPath := \"/\" + ss\n\t\ttestPaths = append(testPaths, cleanPathTest{\n\t\t\tpath:   correctPath,\n\t\t\tresult: correctPath,\n\t\t}, cleanPathTest{\n\t\t\tpath:   ss,\n\t\t\tresult: correctPath,\n\t\t}, cleanPathTest{\n\t\t\tpath:   \"//\" + ss,\n\t\t\tresult: correctPath,\n\t\t}, cleanPathTest{\n\t\t\tpath:   \"/\" + ss + \"/b/..\",\n\t\t\tresult: correctPath,\n\t\t})\n\t}\n\treturn\n}\n\nfunc TestPathCleanLong(t *testing.T) {\n\tcleanTests := genLongPaths()\n\n\tfor _, test := range cleanTests {\n\t\tassert.Equal(t, test.result, cleanPath(test.path))\n\t\tassert.Equal(t, test.result, cleanPath(test.result))\n\t}\n}\n\nfunc BenchmarkPathCleanLong(b *testing.B) {\n\tcleanTests := genLongPaths()\n\n\tb.ReportAllocs()\n\n\tfor b.Loop() {\n\t\tfor _, test := range cleanTests {\n\t\t\tcleanPath(test.path)\n\t\t}\n\t}\n}\n\nfunc TestRemoveRepeatedChar(t *testing.T) {\n\ttestCases := []struct {\n\t\tname string\n\t\tstr  string\n\t\tchar byte\n\t\twant string\n\t}{\n\t\t{\n\t\t\tname: \"empty\",\n\t\t\tstr:  \"\",\n\t\t\tchar: 'a',\n\t\t\twant: \"\",\n\t\t},\n\t\t{\n\t\t\tname: \"noSlash\",\n\t\t\tstr:  \"abc\",\n\t\t\tchar: ',',\n\t\t\twant: \"abc\",\n\t\t},\n\t\t{\n\t\t\tname: \"withSlash\",\n\t\t\tstr:  \"/a/b/c/\",\n\t\t\tchar: '/',\n\t\t\twant: \"/a/b/c/\",\n\t\t},\n\t\t{\n\t\t\tname: \"withRepeatedSlashes\",\n\t\t\tstr:  \"/a//b///c////\",\n\t\t\tchar: '/',\n\t\t\twant: \"/a/b/c/\",\n\t\t},\n\t\t{\n\t\t\tname: \"threeSlashes\",\n\t\t\tstr:  \"///\",\n\t\t\tchar: '/',\n\t\t\twant: \"/\",\n\t\t},\n\t}\n\n\tfor _, tc := range testCases {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\tres := removeRepeatedChar(tc.str, tc.char)\n\t\t\tassert.Equal(t, tc.want, res)\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "recovery.go",
    "content": "// Copyright 2014 Manu Martinez-Almeida. All rights reserved.\n// Use of this source code is governed by a MIT style\n// license that can be found in the LICENSE file.\n\npackage gin\n\nimport (\n\t\"bufio\"\n\t\"bytes\"\n\t\"cmp\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"log\"\n\t\"net/http\"\n\t\"net/http/httputil\"\n\t\"os\"\n\t\"runtime\"\n\t\"strings\"\n\t\"syscall\"\n\t\"time\"\n\n\t\"github.com/gin-gonic/gin/internal/bytesconv\"\n)\n\nconst (\n\tdunno     = \"???\"\n\tstackSkip = 3\n)\n\n// RecoveryFunc defines the function passable to CustomRecovery.\ntype RecoveryFunc func(c *Context, err any)\n\n// Recovery returns a middleware that recovers from any panics and writes a 500 if there was one.\nfunc Recovery() HandlerFunc {\n\treturn RecoveryWithWriter(DefaultErrorWriter)\n}\n\n// CustomRecovery returns a middleware that recovers from any panics and calls the provided handle func to handle it.\nfunc CustomRecovery(handle RecoveryFunc) HandlerFunc {\n\treturn RecoveryWithWriter(DefaultErrorWriter, handle)\n}\n\n// RecoveryWithWriter returns a middleware for a given writer that recovers from any panics and writes a 500 if there was one.\nfunc RecoveryWithWriter(out io.Writer, recovery ...RecoveryFunc) HandlerFunc {\n\tif len(recovery) > 0 {\n\t\treturn CustomRecoveryWithWriter(out, recovery[0])\n\t}\n\treturn CustomRecoveryWithWriter(out, defaultHandleRecovery)\n}\n\n// CustomRecoveryWithWriter returns a middleware for a given writer that recovers from any panics and calls the provided handle func to handle it.\nfunc CustomRecoveryWithWriter(out io.Writer, handle RecoveryFunc) HandlerFunc {\n\tvar logger *log.Logger\n\tif out != nil {\n\t\tlogger = log.New(out, \"\\n\\n\\x1b[31m\", log.LstdFlags)\n\t}\n\treturn func(c *Context) {\n\t\tdefer func() {\n\t\t\tif rec := recover(); rec != nil {\n\t\t\t\t// Check for a broken connection, as it is not really a\n\t\t\t\t// condition that warrants a panic stack trace.\n\t\t\t\tvar isBrokenPipe bool\n\t\t\t\terr, ok := rec.(error)\n\t\t\t\tif ok {\n\t\t\t\t\tisBrokenPipe = errors.Is(err, syscall.EPIPE) ||\n\t\t\t\t\t\terrors.Is(err, syscall.ECONNRESET) ||\n\t\t\t\t\t\terrors.Is(err, http.ErrAbortHandler)\n\t\t\t\t}\n\t\t\t\tif logger != nil {\n\t\t\t\t\tif isBrokenPipe {\n\t\t\t\t\t\tlogger.Printf(\"%s\\n%s%s\", rec, secureRequestDump(c.Request), reset)\n\t\t\t\t\t} else if IsDebugging() {\n\t\t\t\t\t\tlogger.Printf(\"[Recovery] %s panic recovered:\\n%s\\n%s\\n%s%s\",\n\t\t\t\t\t\t\ttimeFormat(time.Now()), secureRequestDump(c.Request), rec, stack(stackSkip), reset)\n\t\t\t\t\t} else {\n\t\t\t\t\t\tlogger.Printf(\"[Recovery] %s panic recovered:\\n%s\\n%s%s\",\n\t\t\t\t\t\t\ttimeFormat(time.Now()), rec, stack(stackSkip), reset)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif isBrokenPipe {\n\t\t\t\t\t// If the connection is dead, we can't write a status to it.\n\t\t\t\t\tc.Error(err) //nolint: errcheck\n\t\t\t\t\tc.Abort()\n\t\t\t\t} else {\n\t\t\t\t\thandle(c, rec)\n\t\t\t\t}\n\t\t\t}\n\t\t}()\n\t\tc.Next()\n\t}\n}\n\n// secureRequestDump returns a sanitized HTTP request dump where the Authorization header,\n// if present, is replaced with a masked value (\"Authorization: *\") to avoid leaking sensitive credentials.\n//\n// Currently, only the Authorization header is sanitized. All other headers and request data remain unchanged.\nfunc secureRequestDump(r *http.Request) string {\n\thttpRequest, _ := httputil.DumpRequest(r, false)\n\tlines := strings.Split(bytesconv.BytesToString(httpRequest), \"\\r\\n\")\n\tfor i, line := range lines {\n\t\tif strings.HasPrefix(line, \"Authorization:\") {\n\t\t\tlines[i] = \"Authorization: *\"\n\t\t}\n\t}\n\treturn strings.Join(lines, \"\\r\\n\")\n}\n\nfunc defaultHandleRecovery(c *Context, _ any) {\n\tc.AbortWithStatus(http.StatusInternalServerError)\n}\n\n// stack returns a nicely formatted stack frame, skipping skip frames.\nfunc stack(skip int) []byte {\n\tbuf := new(bytes.Buffer) // the returned data\n\t// As we loop, we open files and read them. These variables record the currently\n\t// loaded file.\n\tvar (\n\t\tnLine    string\n\t\tlastFile string\n\t\terr      error\n\t)\n\tfor i := skip; ; i++ { // Skip the expected number of frames\n\t\tpc, file, line, ok := runtime.Caller(i)\n\t\tif !ok {\n\t\t\tbreak\n\t\t}\n\t\t// Print this much at least.  If we can't find the source, it won't show.\n\t\tfmt.Fprintf(buf, \"%s:%d (0x%x)\\n\", file, line, pc)\n\t\tif file != lastFile {\n\t\t\tnLine, err = readNthLine(file, line-1)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tlastFile = file\n\t\t}\n\t\tfmt.Fprintf(buf, \"\\t%s: %s\\n\", function(pc), cmp.Or(nLine, dunno))\n\t}\n\treturn buf.Bytes()\n}\n\n// readNthLine reads the nth line from the file.\n// It returns the trimmed content of the line if found,\n// or an empty string if the line doesn't exist.\n// If there's an error opening the file, it returns the error.\nfunc readNthLine(file string, n int) (string, error) {\n\tif n < 0 {\n\t\treturn \"\", nil\n\t}\n\n\tf, err := os.Open(file)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\tdefer f.Close()\n\n\tscanner := bufio.NewScanner(f)\n\tfor i := 0; i < n; i++ {\n\t\tif !scanner.Scan() {\n\t\t\treturn \"\", nil\n\t\t}\n\t}\n\n\tif scanner.Scan() {\n\t\treturn strings.TrimSpace(scanner.Text()), nil\n\t}\n\n\treturn \"\", nil\n}\n\n// function returns, if possible, the name of the function containing the PC.\nfunc function(pc uintptr) string {\n\tfn := runtime.FuncForPC(pc)\n\tif fn == nil {\n\t\treturn dunno\n\t}\n\tname := fn.Name()\n\t// The name includes the path name to the package, which is unnecessary\n\t// since the file name is already included.  Plus, it has center dots.\n\t// That is, we see\n\t//\truntime/debug.*T·ptrmethod\n\t// and want\n\t//\t*T.ptrmethod\n\t// Also the package path might contain dot (e.g. code.google.com/...),\n\t// so first eliminate the path prefix\n\tif lastSlash := strings.LastIndexByte(name, '/'); lastSlash >= 0 {\n\t\tname = name[lastSlash+1:]\n\t}\n\tif period := strings.IndexByte(name, '.'); period >= 0 {\n\t\tname = name[period+1:]\n\t}\n\tname = strings.ReplaceAll(name, \"·\", \".\")\n\treturn name\n}\n\n// timeFormat returns a customized time string for logger.\nfunc timeFormat(t time.Time) string {\n\treturn t.Format(\"2006/01/02 - 15:04:05\")\n}\n"
  },
  {
    "path": "recovery_test.go",
    "content": "// Copyright 2014 Manu Martinez-Almeida. All rights reserved.\n// Use of this source code is governed by a MIT style\n// license that can be found in the LICENSE file.\n\npackage gin\n\nimport (\n\t\"net\"\n\t\"net/http\"\n\t\"os\"\n\t\"strings\"\n\t\"syscall\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n)\n\nfunc TestPanicClean(t *testing.T) {\n\tbuffer := new(strings.Builder)\n\trouter := New()\n\tpassword := \"my-super-secret-password\"\n\trouter.Use(RecoveryWithWriter(buffer))\n\trouter.GET(\"/recovery\", func(c *Context) {\n\t\tc.AbortWithStatus(http.StatusBadRequest)\n\t\tpanic(\"Oops, Houston, we have a problem\")\n\t})\n\t// RUN\n\tw := PerformRequest(router, http.MethodGet, \"/recovery\",\n\t\theader{\n\t\t\tKey:   \"Host\",\n\t\t\tValue: \"www.google.com\",\n\t\t},\n\t\theader{\n\t\t\tKey:   \"Authorization\",\n\t\t\tValue: \"Bearer \" + password,\n\t\t},\n\t\theader{\n\t\t\tKey:   \"Content-Type\",\n\t\t\tValue: \"application/json\",\n\t\t},\n\t)\n\t// TEST\n\tassert.Equal(t, http.StatusBadRequest, w.Code)\n\n\t// Check the buffer does not have the secret key\n\tassert.NotContains(t, buffer.String(), password)\n}\n\n// TestPanicInHandler assert that panic has been recovered.\nfunc TestPanicInHandler(t *testing.T) {\n\tbuffer := new(strings.Builder)\n\trouter := New()\n\trouter.Use(RecoveryWithWriter(buffer))\n\trouter.GET(\"/recovery\", func(_ *Context) {\n\t\tpanic(\"Oops, Houston, we have a problem\")\n\t})\n\t// RUN\n\tw := PerformRequest(router, http.MethodGet, \"/recovery\")\n\t// TEST\n\tassert.Equal(t, http.StatusInternalServerError, w.Code)\n\tassert.Contains(t, buffer.String(), \"panic recovered\")\n\tassert.Contains(t, buffer.String(), \"Oops, Houston, we have a problem\")\n\tassert.Contains(t, buffer.String(), t.Name())\n\tassert.NotContains(t, buffer.String(), \"GET /recovery\")\n\n\t// Debug mode prints the request\n\tSetMode(DebugMode)\n\t// RUN\n\tw = PerformRequest(router, http.MethodGet, \"/recovery\")\n\t// TEST\n\tassert.Equal(t, http.StatusInternalServerError, w.Code)\n\tassert.Contains(t, buffer.String(), \"GET /recovery\")\n\n\tSetMode(TestMode)\n}\n\n// TestPanicWithAbort assert that panic has been recovered even if context.Abort was used.\nfunc TestPanicWithAbort(t *testing.T) {\n\trouter := New()\n\trouter.Use(RecoveryWithWriter(nil))\n\trouter.GET(\"/recovery\", func(c *Context) {\n\t\tc.AbortWithStatus(http.StatusBadRequest)\n\t\tpanic(\"Oops, Houston, we have a problem\")\n\t})\n\t// RUN\n\tw := PerformRequest(router, http.MethodGet, \"/recovery\")\n\t// TEST\n\tassert.Equal(t, http.StatusBadRequest, w.Code)\n}\n\nfunc TestFunction(t *testing.T) {\n\tbs := function(1)\n\tassert.Equal(t, dunno, bs)\n}\n\n// TestPanicWithBrokenPipe asserts that recovery specifically handles\n// writing responses to broken pipes\nfunc TestPanicWithBrokenPipe(t *testing.T) {\n\tconst expectCode = 204\n\n\texpectErrnos := []syscall.Errno{\n\t\tsyscall.EPIPE,\n\t\tsyscall.ECONNRESET,\n\t}\n\n\tfor _, errno := range expectErrnos {\n\t\tt.Run(\"Recovery from \"+errno.Error(), func(t *testing.T) {\n\t\t\tvar buf strings.Builder\n\n\t\t\trouter := New()\n\t\t\trouter.Use(RecoveryWithWriter(&buf))\n\t\t\trouter.GET(\"/recovery\", func(c *Context) {\n\t\t\t\t// Start writing response\n\t\t\t\tc.Header(\"X-Test\", \"Value\")\n\t\t\t\tc.Status(expectCode)\n\n\t\t\t\t// Oops. Client connection closed\n\t\t\t\te := &net.OpError{Err: &os.SyscallError{Err: errno}}\n\t\t\t\tpanic(e)\n\t\t\t})\n\t\t\t// RUN\n\t\t\tw := PerformRequest(router, http.MethodGet, \"/recovery\")\n\t\t\t// TEST\n\t\t\tassert.Equal(t, expectCode, w.Code)\n\t\t\tassert.Contains(t, strings.ToLower(buf.String()), errno.Error())\n\t\t\tassert.NotContains(t, strings.ToLower(buf.String()), \"[Recovery]\")\n\t\t})\n\t}\n}\n\n// TestPanicWithAbortHandler asserts that recovery handles http.ErrAbortHandler as broken pipe\nfunc TestPanicWithAbortHandler(t *testing.T) {\n\tconst expectCode = 204\n\n\tvar buf strings.Builder\n\trouter := New()\n\trouter.Use(RecoveryWithWriter(&buf))\n\trouter.GET(\"/recovery\", func(c *Context) {\n\t\t// Start writing response\n\t\tc.Header(\"X-Test\", \"Value\")\n\t\tc.Status(expectCode)\n\n\t\t// Panic with ErrAbortHandler which should be treated as broken pipe\n\t\tpanic(http.ErrAbortHandler)\n\t})\n\t// RUN\n\tw := PerformRequest(router, http.MethodGet, \"/recovery\")\n\t// TEST\n\tassert.Equal(t, expectCode, w.Code)\n\tout := buf.String()\n\tassert.Contains(t, out, \"net/http: abort Handler\")\n\tassert.NotContains(t, out, \"panic recovered\")\n}\n\nfunc TestCustomRecoveryWithWriter(t *testing.T) {\n\terrBuffer := new(strings.Builder)\n\tbuffer := new(strings.Builder)\n\trouter := New()\n\thandleRecovery := func(c *Context, err any) {\n\t\terrBuffer.WriteString(err.(string))\n\t\tc.AbortWithStatus(http.StatusBadRequest)\n\t}\n\trouter.Use(CustomRecoveryWithWriter(buffer, handleRecovery))\n\trouter.GET(\"/recovery\", func(_ *Context) {\n\t\tpanic(\"Oops, Houston, we have a problem\")\n\t})\n\t// RUN\n\tw := PerformRequest(router, http.MethodGet, \"/recovery\")\n\t// TEST\n\tassert.Equal(t, http.StatusBadRequest, w.Code)\n\tassert.Contains(t, buffer.String(), \"panic recovered\")\n\tassert.Contains(t, buffer.String(), \"Oops, Houston, we have a problem\")\n\tassert.Contains(t, buffer.String(), t.Name())\n\tassert.NotContains(t, buffer.String(), \"GET /recovery\")\n\n\t// Debug mode prints the request\n\tSetMode(DebugMode)\n\t// RUN\n\tw = PerformRequest(router, http.MethodGet, \"/recovery\")\n\t// TEST\n\tassert.Equal(t, http.StatusBadRequest, w.Code)\n\tassert.Contains(t, buffer.String(), \"GET /recovery\")\n\n\tassert.Equal(t, strings.Repeat(\"Oops, Houston, we have a problem\", 2), errBuffer.String())\n\n\tSetMode(TestMode)\n}\n\nfunc TestCustomRecovery(t *testing.T) {\n\terrBuffer := new(strings.Builder)\n\tbuffer := new(strings.Builder)\n\trouter := New()\n\tDefaultErrorWriter = buffer\n\thandleRecovery := func(c *Context, err any) {\n\t\terrBuffer.WriteString(err.(string))\n\t\tc.AbortWithStatus(http.StatusBadRequest)\n\t}\n\trouter.Use(CustomRecovery(handleRecovery))\n\trouter.GET(\"/recovery\", func(_ *Context) {\n\t\tpanic(\"Oops, Houston, we have a problem\")\n\t})\n\t// RUN\n\tw := PerformRequest(router, http.MethodGet, \"/recovery\")\n\t// TEST\n\tassert.Equal(t, http.StatusBadRequest, w.Code)\n\tassert.Contains(t, buffer.String(), \"panic recovered\")\n\tassert.Contains(t, buffer.String(), \"Oops, Houston, we have a problem\")\n\tassert.Contains(t, buffer.String(), t.Name())\n\tassert.NotContains(t, buffer.String(), \"GET /recovery\")\n\n\t// Debug mode prints the request\n\tSetMode(DebugMode)\n\t// RUN\n\tw = PerformRequest(router, http.MethodGet, \"/recovery\")\n\t// TEST\n\tassert.Equal(t, http.StatusBadRequest, w.Code)\n\tassert.Contains(t, buffer.String(), \"GET /recovery\")\n\n\tassert.Equal(t, strings.Repeat(\"Oops, Houston, we have a problem\", 2), errBuffer.String())\n\n\tSetMode(TestMode)\n}\n\nfunc TestRecoveryWithWriterWithCustomRecovery(t *testing.T) {\n\terrBuffer := new(strings.Builder)\n\tbuffer := new(strings.Builder)\n\trouter := New()\n\tDefaultErrorWriter = buffer\n\thandleRecovery := func(c *Context, err any) {\n\t\terrBuffer.WriteString(err.(string))\n\t\tc.AbortWithStatus(http.StatusBadRequest)\n\t}\n\trouter.Use(RecoveryWithWriter(DefaultErrorWriter, handleRecovery))\n\trouter.GET(\"/recovery\", func(_ *Context) {\n\t\tpanic(\"Oops, Houston, we have a problem\")\n\t})\n\t// RUN\n\tw := PerformRequest(router, http.MethodGet, \"/recovery\")\n\t// TEST\n\tassert.Equal(t, http.StatusBadRequest, w.Code)\n\tassert.Contains(t, buffer.String(), \"panic recovered\")\n\tassert.Contains(t, buffer.String(), \"Oops, Houston, we have a problem\")\n\tassert.Contains(t, buffer.String(), t.Name())\n\tassert.NotContains(t, buffer.String(), \"GET /recovery\")\n\n\t// Debug mode prints the request\n\tSetMode(DebugMode)\n\t// RUN\n\tw = PerformRequest(router, http.MethodGet, \"/recovery\")\n\t// TEST\n\tassert.Equal(t, http.StatusBadRequest, w.Code)\n\tassert.Contains(t, buffer.String(), \"GET /recovery\")\n\n\tassert.Equal(t, strings.Repeat(\"Oops, Houston, we have a problem\", 2), errBuffer.String())\n\n\tSetMode(TestMode)\n}\n\nfunc TestSecureRequestDump(t *testing.T) {\n\ttests := []struct {\n\t\tname           string\n\t\treq            *http.Request\n\t\twantContains   string\n\t\twantNotContain string\n\t}{\n\t\t{\n\t\t\tname: \"Authorization header standard case\",\n\t\t\treq: func() *http.Request {\n\t\t\t\tr, _ := http.NewRequest(http.MethodGet, \"http://example.com\", nil)\n\t\t\t\tr.Header.Set(\"Authorization\", \"Bearer secret-token\")\n\t\t\t\treturn r\n\t\t\t}(),\n\t\t\twantContains:   \"Authorization: *\",\n\t\t\twantNotContain: \"Bearer secret-token\",\n\t\t},\n\t\t{\n\t\t\tname: \"authorization header lowercase\",\n\t\t\treq: func() *http.Request {\n\t\t\t\tr, _ := http.NewRequest(http.MethodGet, \"http://example.com\", nil)\n\t\t\t\tr.Header.Set(\"authorization\", \"some-secret\")\n\t\t\t\treturn r\n\t\t\t}(),\n\t\t\twantContains:   \"Authorization: *\",\n\t\t\twantNotContain: \"some-secret\",\n\t\t},\n\t\t{\n\t\t\tname: \"Authorization header mixed case\",\n\t\t\treq: func() *http.Request {\n\t\t\t\tr, _ := http.NewRequest(http.MethodGet, \"http://example.com\", nil)\n\t\t\t\tr.Header.Set(\"AuThOrIzAtIoN\", \"token123\")\n\t\t\t\treturn r\n\t\t\t}(),\n\t\t\twantContains:   \"Authorization: *\",\n\t\t\twantNotContain: \"token123\",\n\t\t},\n\t\t{\n\t\t\tname: \"No Authorization header\",\n\t\t\treq: func() *http.Request {\n\t\t\t\tr, _ := http.NewRequest(http.MethodGet, \"http://example.com\", nil)\n\t\t\t\tr.Header.Set(\"Content-Type\", \"application/json\")\n\t\t\t\treturn r\n\t\t\t}(),\n\t\t\twantContains:   \"\",\n\t\t\twantNotContain: \"Authorization: *\",\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tresult := secureRequestDump(tt.req)\n\t\t\tif tt.wantContains != \"\" && !strings.Contains(result, tt.wantContains) {\n\t\t\t\tt.Errorf(\"maskHeaders() = %q, want contains %q\", result, tt.wantContains)\n\t\t\t}\n\t\t\tif tt.wantNotContain != \"\" && strings.Contains(result, tt.wantNotContain) {\n\t\t\t\tt.Errorf(\"maskHeaders() = %q, want NOT contain %q\", result, tt.wantNotContain)\n\t\t\t}\n\t\t})\n\t}\n}\n\n// TestReadNthLine tests the readNthLine function with various scenarios.\nfunc TestReadNthLine(t *testing.T) {\n\t// Create a temporary test file\n\ttestContent := \"line 0 \\n line 1  \\nline 2 \\nline 3  \\nline 4\"\n\ttempFile, err := os.CreateTemp(\"\", \"testfile*.txt\")\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tdefer os.Remove(tempFile.Name())\n\n\t// Write test content to the temporary file\n\tif _, err := tempFile.WriteString(testContent); err != nil {\n\t\tt.Fatal(err)\n\t}\n\tif err := tempFile.Close(); err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\t// Test cases\n\ttests := []struct {\n\t\tname     string\n\t\tlineNum  int\n\t\tfileName string\n\t\twant     string\n\t\twantErr  bool\n\t}{\n\t\t{name: \"Read first line\", lineNum: 0, fileName: tempFile.Name(), want: \"line 0\", wantErr: false},\n\t\t{name: \"Read middle line\", lineNum: 2, fileName: tempFile.Name(), want: \"line 2\", wantErr: false},\n\t\t{name: \"Read last line\", lineNum: 4, fileName: tempFile.Name(), want: \"line 4\", wantErr: false},\n\t\t{name: \"Line number exceeds file length\", lineNum: 10, fileName: tempFile.Name(), want: \"\", wantErr: false},\n\t\t{name: \"Negative line number\", lineNum: -1, fileName: tempFile.Name(), want: \"\", wantErr: false},\n\t\t{name: \"Non-existent file\", lineNum: 1, fileName: \"/non/existent/file.txt\", want: \"\", wantErr: true},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tgot, err := readNthLine(tt.fileName, tt.lineNum)\n\t\t\tassert.Equal(t, tt.wantErr, err != nil)\n\t\t\tassert.Equal(t, tt.want, got)\n\t\t})\n\t}\n}\n\nfunc BenchmarkStack(b *testing.B) {\n\tb.ReportAllocs()\n\tfor b.Loop() {\n\t\t_ = stack(stackSkip)\n\t}\n}\n"
  },
  {
    "path": "render/bson.go",
    "content": "// Copyright 2025 Gin Core Team. All rights reserved.\n// Use of this source code is governed by a MIT style\n// license that can be found in the LICENSE file.\n\npackage render\n\nimport (\n\t\"net/http\"\n\n\t\"go.mongodb.org/mongo-driver/v2/bson\"\n)\n\n// BSON contains the given interface object.\ntype BSON struct {\n\tData any\n}\n\nvar bsonContentType = []string{\"application/bson\"}\n\n// Render (BSON) marshals the given interface object and writes data with custom ContentType.\nfunc (r BSON) Render(w http.ResponseWriter) error {\n\tr.WriteContentType(w)\n\n\tbytes, err := bson.Marshal(&r.Data)\n\tif err == nil {\n\t\t_, err = w.Write(bytes)\n\t}\n\treturn err\n}\n\n// WriteContentType (BSONBuf) writes BSONBuf ContentType.\nfunc (r BSON) WriteContentType(w http.ResponseWriter) {\n\twriteContentType(w, bsonContentType)\n}\n"
  },
  {
    "path": "render/data.go",
    "content": "// Copyright 2014 Manu Martinez-Almeida. All rights reserved.\n// Use of this source code is governed by a MIT style\n// license that can be found in the LICENSE file.\n\npackage render\n\nimport (\n\t\"net/http\"\n\t\"strconv\"\n)\n\n// Data contains ContentType and bytes data.\ntype Data struct {\n\tContentType string\n\tData        []byte\n}\n\n// Render (Data) writes data with custom ContentType.\nfunc (r Data) Render(w http.ResponseWriter) (err error) {\n\tr.WriteContentType(w)\n\tif len(r.Data) > 0 {\n\t\tw.Header().Set(\"Content-Length\", strconv.Itoa(len(r.Data)))\n\t}\n\t_, err = w.Write(r.Data)\n\treturn\n}\n\n// WriteContentType (Data) writes custom ContentType.\nfunc (r Data) WriteContentType(w http.ResponseWriter) {\n\twriteContentType(w, []string{r.ContentType})\n}\n"
  },
  {
    "path": "render/html.go",
    "content": "// Copyright 2014 Manu Martinez-Almeida. All rights reserved.\n// Use of this source code is governed by a MIT style\n// license that can be found in the LICENSE file.\n\npackage render\n\nimport (\n\t\"html/template\"\n\t\"net/http\"\n\n\t\"github.com/gin-gonic/gin/internal/fs\"\n)\n\n// Delims represents a set of Left and Right delimiters for HTML template rendering.\ntype Delims struct {\n\t// Left delimiter, defaults to {{.\n\tLeft string\n\t// Right delimiter, defaults to }}.\n\tRight string\n}\n\n// HTMLRender interface is to be implemented by HTMLProduction and HTMLDebug.\ntype HTMLRender interface {\n\t// Instance returns an HTML instance.\n\tInstance(string, any) Render\n}\n\n// HTMLProduction contains template reference and its delims.\ntype HTMLProduction struct {\n\tTemplate *template.Template\n\tDelims   Delims\n}\n\n// HTMLDebug contains template delims and pattern and function with file list.\ntype HTMLDebug struct {\n\tFiles      []string\n\tGlob       string\n\tFileSystem http.FileSystem\n\tPatterns   []string\n\tDelims     Delims\n\tFuncMap    template.FuncMap\n}\n\n// HTML contains template reference and its name with given interface object.\ntype HTML struct {\n\tTemplate *template.Template\n\tName     string\n\tData     any\n}\n\nvar htmlContentType = []string{\"text/html; charset=utf-8\"}\n\n// Instance (HTMLProduction) returns an HTML instance which it realizes Render interface.\nfunc (r HTMLProduction) Instance(name string, data any) Render {\n\treturn HTML{\n\t\tTemplate: r.Template,\n\t\tName:     name,\n\t\tData:     data,\n\t}\n}\n\n// Instance (HTMLDebug) returns an HTML instance which it realizes Render interface.\nfunc (r HTMLDebug) Instance(name string, data any) Render {\n\treturn HTML{\n\t\tTemplate: r.loadTemplate(),\n\t\tName:     name,\n\t\tData:     data,\n\t}\n}\n\nfunc (r HTMLDebug) loadTemplate() *template.Template {\n\tif r.FuncMap == nil {\n\t\tr.FuncMap = template.FuncMap{}\n\t}\n\tif len(r.Files) > 0 {\n\t\treturn template.Must(template.New(\"\").Delims(r.Delims.Left, r.Delims.Right).Funcs(r.FuncMap).ParseFiles(r.Files...))\n\t}\n\tif r.Glob != \"\" {\n\t\treturn template.Must(template.New(\"\").Delims(r.Delims.Left, r.Delims.Right).Funcs(r.FuncMap).ParseGlob(r.Glob))\n\t}\n\tif r.FileSystem != nil && len(r.Patterns) > 0 {\n\t\treturn template.Must(template.New(\"\").Delims(r.Delims.Left, r.Delims.Right).Funcs(r.FuncMap).ParseFS(\n\t\t\tfs.FileSystem{FileSystem: r.FileSystem}, r.Patterns...))\n\t}\n\tpanic(\"the HTML debug render was created without files or glob pattern or file system with patterns\")\n}\n\n// Render (HTML) executes template and writes its result with custom ContentType for response.\nfunc (r HTML) Render(w http.ResponseWriter) error {\n\tr.WriteContentType(w)\n\n\tif r.Name == \"\" {\n\t\treturn r.Template.Execute(w, r.Data)\n\t}\n\treturn r.Template.ExecuteTemplate(w, r.Name, r.Data)\n}\n\n// WriteContentType (HTML) writes HTML ContentType.\nfunc (r HTML) WriteContentType(w http.ResponseWriter) {\n\twriteContentType(w, htmlContentType)\n}\n"
  },
  {
    "path": "render/json.go",
    "content": "// Copyright 2014 Manu Martinez-Almeida. All rights reserved.\n// Use of this source code is governed by a MIT style\n// license that can be found in the LICENSE file.\n\npackage render\n\nimport (\n\t\"bytes\"\n\t\"fmt\"\n\t\"html/template\"\n\t\"net/http\"\n\t\"unicode\"\n\n\t\"github.com/gin-gonic/gin/codec/json\"\n\t\"github.com/gin-gonic/gin/internal/bytesconv\"\n)\n\n// JSON contains the given interface object.\ntype JSON struct {\n\tData any\n}\n\n// IndentedJSON contains the given interface object.\ntype IndentedJSON struct {\n\tData any\n}\n\n// SecureJSON contains the given interface object and its prefix.\ntype SecureJSON struct {\n\tPrefix string\n\tData   any\n}\n\n// JsonpJSON contains the given interface object its callback.\ntype JsonpJSON struct {\n\tCallback string\n\tData     any\n}\n\n// AsciiJSON contains the given interface object.\ntype AsciiJSON struct {\n\tData any\n}\n\n// PureJSON contains the given interface object.\ntype PureJSON struct {\n\tData any\n}\n\nvar (\n\tjsonContentType      = []string{\"application/json; charset=utf-8\"}\n\tjsonpContentType     = []string{\"application/javascript; charset=utf-8\"}\n\tjsonASCIIContentType = []string{\"application/json\"}\n)\n\n// Render (JSON) writes data with custom ContentType.\nfunc (r JSON) Render(w http.ResponseWriter) error {\n\treturn WriteJSON(w, r.Data)\n}\n\n// WriteContentType (JSON) writes JSON ContentType.\nfunc (r JSON) WriteContentType(w http.ResponseWriter) {\n\twriteContentType(w, jsonContentType)\n}\n\n// WriteJSON marshals the given interface object and writes it with custom ContentType.\nfunc WriteJSON(w http.ResponseWriter, obj any) error {\n\twriteContentType(w, jsonContentType)\n\tjsonBytes, err := json.API.Marshal(obj)\n\tif err != nil {\n\t\treturn err\n\t}\n\t_, err = w.Write(jsonBytes)\n\treturn err\n}\n\n// Render (IndentedJSON) marshals the given interface object and writes it with custom ContentType.\nfunc (r IndentedJSON) Render(w http.ResponseWriter) error {\n\tr.WriteContentType(w)\n\tjsonBytes, err := json.API.MarshalIndent(r.Data, \"\", \"    \")\n\tif err != nil {\n\t\treturn err\n\t}\n\t_, err = w.Write(jsonBytes)\n\treturn err\n}\n\n// WriteContentType (IndentedJSON) writes JSON ContentType.\nfunc (r IndentedJSON) WriteContentType(w http.ResponseWriter) {\n\twriteContentType(w, jsonContentType)\n}\n\n// Render (SecureJSON) marshals the given interface object and writes it with custom ContentType.\nfunc (r SecureJSON) Render(w http.ResponseWriter) error {\n\tr.WriteContentType(w)\n\tjsonBytes, err := json.API.Marshal(r.Data)\n\tif err != nil {\n\t\treturn err\n\t}\n\t// if the jsonBytes is array values\n\tif bytes.HasPrefix(jsonBytes, bytesconv.StringToBytes(\"[\")) && bytes.HasSuffix(jsonBytes,\n\t\tbytesconv.StringToBytes(\"]\")) {\n\t\tif _, err = w.Write(bytesconv.StringToBytes(r.Prefix)); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\t_, err = w.Write(jsonBytes)\n\treturn err\n}\n\n// WriteContentType (SecureJSON) writes JSON ContentType.\nfunc (r SecureJSON) WriteContentType(w http.ResponseWriter) {\n\twriteContentType(w, jsonContentType)\n}\n\n// Render (JsonpJSON) marshals the given interface object and writes it and its callback with custom ContentType.\nfunc (r JsonpJSON) Render(w http.ResponseWriter) (err error) {\n\tr.WriteContentType(w)\n\tret, err := json.API.Marshal(r.Data)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif r.Callback == \"\" {\n\t\t_, err = w.Write(ret)\n\t\treturn err\n\t}\n\n\tcallback := template.JSEscapeString(r.Callback)\n\tif _, err = w.Write(bytesconv.StringToBytes(callback)); err != nil {\n\t\treturn err\n\t}\n\n\tif _, err = w.Write(bytesconv.StringToBytes(\"(\")); err != nil {\n\t\treturn err\n\t}\n\n\tif _, err = w.Write(ret); err != nil {\n\t\treturn err\n\t}\n\n\tif _, err = w.Write(bytesconv.StringToBytes(\");\")); err != nil {\n\t\treturn err\n\t}\n\n\treturn nil\n}\n\n// WriteContentType (JsonpJSON) writes Javascript ContentType.\nfunc (r JsonpJSON) WriteContentType(w http.ResponseWriter) {\n\twriteContentType(w, jsonpContentType)\n}\n\n// Render (AsciiJSON) marshals the given interface object and writes it with custom ContentType.\nfunc (r AsciiJSON) Render(w http.ResponseWriter) error {\n\tr.WriteContentType(w)\n\tret, err := json.API.Marshal(r.Data)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tvar buffer bytes.Buffer\n\tescapeBuf := make([]byte, 0, 6) // Preallocate 6 bytes for Unicode escape sequences\n\n\tfor _, r := range bytesconv.BytesToString(ret) {\n\t\tif r > unicode.MaxASCII {\n\t\t\tescapeBuf = fmt.Appendf(escapeBuf[:0], \"\\\\u%04x\", r) // Reuse escapeBuf\n\t\t\tbuffer.Write(escapeBuf)\n\t\t} else {\n\t\t\tbuffer.WriteByte(byte(r))\n\t\t}\n\t}\n\n\t_, err = w.Write(buffer.Bytes())\n\treturn err\n}\n\n// WriteContentType (AsciiJSON) writes JSON ContentType.\nfunc (r AsciiJSON) WriteContentType(w http.ResponseWriter) {\n\twriteContentType(w, jsonASCIIContentType)\n}\n\n// Render (PureJSON) writes custom ContentType and encodes the given interface object.\nfunc (r PureJSON) Render(w http.ResponseWriter) error {\n\tr.WriteContentType(w)\n\tencoder := json.API.NewEncoder(w)\n\tencoder.SetEscapeHTML(false)\n\treturn encoder.Encode(r.Data)\n}\n\n// WriteContentType (PureJSON) writes custom ContentType.\nfunc (r PureJSON) WriteContentType(w http.ResponseWriter) {\n\twriteContentType(w, jsonContentType)\n}\n"
  },
  {
    "path": "render/msgpack.go",
    "content": "// Copyright 2017 Manu Martinez-Almeida. All rights reserved.\n// Use of this source code is governed by a MIT style\n// license that can be found in the LICENSE file.\n\n//go:build !nomsgpack\n\npackage render\n\nimport (\n\t\"net/http\"\n\n\t\"github.com/ugorji/go/codec\"\n)\n\n// Check interface implemented here to support go build tag nomsgpack.\n// See: https://github.com/gin-gonic/gin/pull/1852/\nvar (\n\t_ Render = MsgPack{}\n)\n\n// MsgPack contains the given interface object.\ntype MsgPack struct {\n\tData any\n}\n\nvar msgpackContentType = []string{\"application/msgpack; charset=utf-8\"}\n\n// WriteContentType (MsgPack) writes MsgPack ContentType.\nfunc (r MsgPack) WriteContentType(w http.ResponseWriter) {\n\twriteContentType(w, msgpackContentType)\n}\n\n// Render (MsgPack) encodes the given interface object and writes data with custom ContentType.\nfunc (r MsgPack) Render(w http.ResponseWriter) error {\n\treturn WriteMsgPack(w, r.Data)\n}\n\n// WriteMsgPack writes MsgPack ContentType and encodes the given interface object.\nfunc WriteMsgPack(w http.ResponseWriter, obj any) error {\n\twriteContentType(w, msgpackContentType)\n\tvar mh codec.MsgpackHandle\n\treturn codec.NewEncoder(w, &mh).Encode(obj)\n}\n"
  },
  {
    "path": "render/pdf.go",
    "content": "// Copyright 2026 Gin Core Team. All rights reserved.\n// Use of this source code is governed by a MIT style\n// license that can be found in the LICENSE file.\n\npackage render\n\nimport \"net/http\"\n\n// PDF contains the given PDF binary data.\ntype PDF struct {\n\tData []byte\n}\n\nvar pdfContentType = []string{\"application/pdf\"}\n\n// Render (PDF) writes PDF data with custom ContentType.\nfunc (r PDF) Render(w http.ResponseWriter) error {\n\tr.WriteContentType(w)\n\t_, err := w.Write(r.Data)\n\treturn err\n}\n\n// WriteContentType (PDF) writes PDF ContentType for response.\nfunc (r PDF) WriteContentType(w http.ResponseWriter) {\n\twriteContentType(w, pdfContentType)\n}\n"
  },
  {
    "path": "render/protobuf.go",
    "content": "// Copyright 2018 Gin Core Team. All rights reserved.\n// Use of this source code is governed by a MIT style\n// license that can be found in the LICENSE file.\n\npackage render\n\nimport (\n\t\"net/http\"\n\n\t\"google.golang.org/protobuf/proto\"\n)\n\n// ProtoBuf contains the given interface object.\ntype ProtoBuf struct {\n\tData any\n}\n\nvar protobufContentType = []string{\"application/x-protobuf\"}\n\n// Render (ProtoBuf) marshals the given interface object and writes data with custom ContentType.\nfunc (r ProtoBuf) Render(w http.ResponseWriter) error {\n\tr.WriteContentType(w)\n\n\tbytes, err := proto.Marshal(r.Data.(proto.Message))\n\tif err != nil {\n\t\treturn err\n\t}\n\n\t_, err = w.Write(bytes)\n\treturn err\n}\n\n// WriteContentType (ProtoBuf) writes ProtoBuf ContentType.\nfunc (r ProtoBuf) WriteContentType(w http.ResponseWriter) {\n\twriteContentType(w, protobufContentType)\n}\n"
  },
  {
    "path": "render/reader.go",
    "content": "// Copyright 2018 Gin Core Team. All rights reserved.\n// Use of this source code is governed by a MIT style\n// license that can be found in the LICENSE file.\n\npackage render\n\nimport (\n\t\"io\"\n\t\"net/http\"\n\t\"strconv\"\n)\n\n// Reader contains the IO reader and its length, and custom ContentType and other headers.\ntype Reader struct {\n\tContentType   string\n\tContentLength int64\n\tReader        io.Reader\n\tHeaders       map[string]string\n}\n\n// Render (Reader) writes data with custom ContentType and headers.\nfunc (r Reader) Render(w http.ResponseWriter) (err error) {\n\tr.WriteContentType(w)\n\tif r.ContentLength >= 0 {\n\t\tif r.Headers == nil {\n\t\t\tr.Headers = map[string]string{}\n\t\t}\n\t\tr.Headers[\"Content-Length\"] = strconv.FormatInt(r.ContentLength, 10)\n\t}\n\tr.writeHeaders(w)\n\t_, err = io.Copy(w, r.Reader)\n\treturn\n}\n\n// WriteContentType (Reader) writes custom ContentType.\nfunc (r Reader) WriteContentType(w http.ResponseWriter) {\n\twriteContentType(w, []string{r.ContentType})\n}\n\n// writeHeaders writes headers from r.Headers into response.\nfunc (r Reader) writeHeaders(w http.ResponseWriter) {\n\theader := w.Header()\n\tfor k, v := range r.Headers {\n\t\tif header.Get(k) == \"\" {\n\t\t\theader.Set(k, v)\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "render/reader_test.go",
    "content": "// Copyright 2019 Gin Core Team. All rights reserved.\n// Use of this source code is governed by a MIT style\n// license that can be found in the LICENSE file.\n\npackage render\n\nimport (\n\t\"net/http/httptest\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestReaderRenderNoHeaders(t *testing.T) {\n\tcontent := \"test\"\n\tr := Reader{\n\t\tContentLength: int64(len(content)),\n\t\tReader:        strings.NewReader(content),\n\t}\n\terr := r.Render(httptest.NewRecorder())\n\trequire.NoError(t, err)\n}\n"
  },
  {
    "path": "render/redirect.go",
    "content": "// Copyright 2014 Manu Martinez-Almeida. All rights reserved.\n// Use of this source code is governed by a MIT style\n// license that can be found in the LICENSE file.\n\npackage render\n\nimport (\n\t\"fmt\"\n\t\"net/http\"\n)\n\n// Redirect contains the http request reference and redirects status code and location.\ntype Redirect struct {\n\tCode     int\n\tRequest  *http.Request\n\tLocation string\n}\n\n// Render (Redirect) redirects the http request to new location and writes redirect response.\nfunc (r Redirect) Render(w http.ResponseWriter) error {\n\tif (r.Code < http.StatusMultipleChoices || r.Code > http.StatusPermanentRedirect) && r.Code != http.StatusCreated {\n\t\tpanic(fmt.Sprintf(\"Cannot redirect with status code %d\", r.Code))\n\t}\n\thttp.Redirect(w, r.Request, r.Location, r.Code)\n\treturn nil\n}\n\n// WriteContentType (Redirect) don't write any ContentType.\nfunc (r Redirect) WriteContentType(http.ResponseWriter) {}\n"
  },
  {
    "path": "render/render.go",
    "content": "// Copyright 2014 Manu Martinez-Almeida. All rights reserved.\n// Use of this source code is governed by a MIT style\n// license that can be found in the LICENSE file.\n\npackage render\n\nimport \"net/http\"\n\n// Render interface is to be implemented by JSON, XML, HTML, YAML and so on.\ntype Render interface {\n\t// Render writes data with custom ContentType.\n\tRender(http.ResponseWriter) error\n\t// WriteContentType writes custom ContentType.\n\tWriteContentType(w http.ResponseWriter)\n}\n\nvar (\n\t_ Render     = (*JSON)(nil)\n\t_ Render     = (*IndentedJSON)(nil)\n\t_ Render     = (*SecureJSON)(nil)\n\t_ Render     = (*JsonpJSON)(nil)\n\t_ Render     = (*XML)(nil)\n\t_ Render     = (*String)(nil)\n\t_ Render     = (*Redirect)(nil)\n\t_ Render     = (*Data)(nil)\n\t_ Render     = (*HTML)(nil)\n\t_ HTMLRender = (*HTMLDebug)(nil)\n\t_ HTMLRender = (*HTMLProduction)(nil)\n\t_ Render     = (*YAML)(nil)\n\t_ Render     = (*Reader)(nil)\n\t_ Render     = (*AsciiJSON)(nil)\n\t_ Render     = (*ProtoBuf)(nil)\n\t_ Render     = (*TOML)(nil)\n\t_ Render     = (*PDF)(nil)\n)\n\nfunc writeContentType(w http.ResponseWriter, value []string) {\n\theader := w.Header()\n\tif val := header[\"Content-Type\"]; len(val) == 0 {\n\t\theader[\"Content-Type\"] = value\n\t}\n}\n"
  },
  {
    "path": "render/render_msgpack_test.go",
    "content": "// Copyright 2014 Manu Martinez-Almeida. All rights reserved.\n// Use of this source code is governed by a MIT style\n// license that can be found in the LICENSE file.\n\n//go:build !nomsgpack\n\npackage render\n\nimport (\n\t\"errors\"\n\t\"net/http/httptest\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n\t\"github.com/ugorji/go/codec\"\n)\n\nfunc TestRenderMsgPack(t *testing.T) {\n\tw := httptest.NewRecorder()\n\tdata := map[string]any{\n\t\t\"foo\": \"bar\",\n\t}\n\n\t(MsgPack{data}).WriteContentType(w)\n\tassert.Equal(t, \"application/msgpack; charset=utf-8\", w.Header().Get(\"Content-Type\"))\n\n\terr := (MsgPack{data}).Render(w)\n\n\trequire.NoError(t, err)\n\n\tvar decoded map[string]any\n\tvar mh codec.MsgpackHandle\n\tmh.RawToString = true\n\terr = codec.NewDecoderBytes(w.Body.Bytes(), &mh).Decode(&decoded)\n\trequire.NoError(t, err)\n\tassert.Equal(t, data, decoded)\n\tassert.Equal(t, \"application/msgpack; charset=utf-8\", w.Header().Get(\"Content-Type\"))\n}\n\nfunc TestWriteMsgPack(t *testing.T) {\n\tw := httptest.NewRecorder()\n\tdata := map[string]any{\n\t\t\"foo\": \"bar\",\n\t\t\"num\": 42,\n\t}\n\n\terr := WriteMsgPack(w, data)\n\trequire.NoError(t, err)\n\n\tassert.Equal(t, \"application/msgpack; charset=utf-8\", w.Header().Get(\"Content-Type\"))\n\n\tvar decoded map[string]any\n\tvar mh codec.MsgpackHandle\n\tmh.RawToString = true\n\terr = codec.NewDecoderBytes(w.Body.Bytes(), &mh).Decode(&decoded)\n\trequire.NoError(t, err)\n\tassert.Len(t, decoded, 2)\n\tassert.Equal(t, \"bar\", decoded[\"foo\"])\n\tassert.EqualValues(t, 42, decoded[\"num\"])\n}\n\ntype failWriter struct {\n\t*httptest.ResponseRecorder\n}\n\nfunc (w *failWriter) Write(data []byte) (int, error) {\n\treturn 0, errors.New(\"write error\")\n}\n\nfunc TestRenderMsgPackError(t *testing.T) {\n\tw := httptest.NewRecorder()\n\tdata := map[string]any{\n\t\t\"foo\": \"bar\",\n\t}\n\n\terr := (MsgPack{data}).Render(&failWriter{w})\n\trequire.Error(t, err)\n\tassert.Contains(t, err.Error(), \"write error\")\n}\n"
  },
  {
    "path": "render/render_test.go",
    "content": "// Copyright 2014 Manu Martinez-Almeida. All rights reserved.\n// Use of this source code is governed by a MIT style\n// license that can be found in the LICENSE file.\n\npackage render\n\nimport (\n\t\"encoding/xml\"\n\t\"errors\"\n\t\"html/template\"\n\t\"io\"\n\t\"net\"\n\t\"net/http\"\n\t\"net/http/httptest\"\n\t\"strconv\"\n\t\"strings\"\n\t\"testing\"\n\n\ttestdata \"github.com/gin-gonic/gin/testdata/protoexample\"\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n\t\"go.mongodb.org/mongo-driver/v2/bson\"\n\t\"google.golang.org/protobuf/proto\"\n)\n\nfunc TestRenderJSON(t *testing.T) {\n\tw := httptest.NewRecorder()\n\tdata := map[string]any{\n\t\t\"foo\":  \"bar\",\n\t\t\"html\": \"<b>\",\n\t}\n\n\t(JSON{data}).WriteContentType(w)\n\tassert.Equal(t, \"application/json; charset=utf-8\", w.Header().Get(\"Content-Type\"))\n\n\terr := (JSON{data}).Render(w)\n\n\trequire.NoError(t, err)\n\tassert.JSONEq(t, \"{\\\"foo\\\":\\\"bar\\\",\\\"html\\\":\\\"\\\\u003cb\\\\u003e\\\"}\", w.Body.String())\n\tassert.Equal(t, \"application/json; charset=utf-8\", w.Header().Get(\"Content-Type\"))\n}\n\nfunc TestRenderJSONError(t *testing.T) {\n\tw := httptest.NewRecorder()\n\tdata := make(chan int)\n\n\t// json: unsupported type: chan int\n\trequire.Error(t, (JSON{data}).Render(w))\n}\n\nfunc TestRenderIndentedJSON(t *testing.T) {\n\tw := httptest.NewRecorder()\n\tdata := map[string]any{\n\t\t\"foo\": \"bar\",\n\t\t\"bar\": \"foo\",\n\t}\n\n\terr := (IndentedJSON{data}).Render(w)\n\n\trequire.NoError(t, err)\n\tassert.JSONEq(t, \"{\\n    \\\"bar\\\": \\\"foo\\\",\\n    \\\"foo\\\": \\\"bar\\\"\\n}\", w.Body.String())\n\tassert.Equal(t, \"application/json; charset=utf-8\", w.Header().Get(\"Content-Type\"))\n}\n\nfunc TestRenderIndentedJSONPanics(t *testing.T) {\n\tw := httptest.NewRecorder()\n\tdata := make(chan int)\n\n\t// json: unsupported type: chan int\n\terr := (IndentedJSON{data}).Render(w)\n\trequire.Error(t, err)\n}\n\nfunc TestRenderSecureJSON(t *testing.T) {\n\tw1 := httptest.NewRecorder()\n\tdata := map[string]any{\n\t\t\"foo\": \"bar\",\n\t}\n\n\t(SecureJSON{\"while(1);\", data}).WriteContentType(w1)\n\tassert.Equal(t, \"application/json; charset=utf-8\", w1.Header().Get(\"Content-Type\"))\n\n\terr1 := (SecureJSON{\"while(1);\", data}).Render(w1)\n\n\trequire.NoError(t, err1)\n\tassert.JSONEq(t, \"{\\\"foo\\\":\\\"bar\\\"}\", w1.Body.String())\n\tassert.Equal(t, \"application/json; charset=utf-8\", w1.Header().Get(\"Content-Type\"))\n\n\tw2 := httptest.NewRecorder()\n\tdatas := []map[string]any{{\n\t\t\"foo\": \"bar\",\n\t}, {\n\t\t\"bar\": \"foo\",\n\t}}\n\n\terr2 := (SecureJSON{\"while(1);\", datas}).Render(w2)\n\trequire.NoError(t, err2)\n\tassert.Equal(t, \"while(1);[{\\\"foo\\\":\\\"bar\\\"},{\\\"bar\\\":\\\"foo\\\"}]\", w2.Body.String())\n\tassert.Equal(t, \"application/json; charset=utf-8\", w2.Header().Get(\"Content-Type\"))\n}\n\nfunc TestRenderSecureJSONFail(t *testing.T) {\n\tw := httptest.NewRecorder()\n\tdata := make(chan int)\n\n\t// json: unsupported type: chan int\n\terr := (SecureJSON{\"while(1);\", data}).Render(w)\n\trequire.Error(t, err)\n}\n\nfunc TestRenderJsonpJSON(t *testing.T) {\n\tw1 := httptest.NewRecorder()\n\tdata := map[string]any{\n\t\t\"foo\": \"bar\",\n\t}\n\n\t(JsonpJSON{\"x\", data}).WriteContentType(w1)\n\tassert.Equal(t, \"application/javascript; charset=utf-8\", w1.Header().Get(\"Content-Type\"))\n\n\terr1 := (JsonpJSON{\"x\", data}).Render(w1)\n\n\trequire.NoError(t, err1)\n\tassert.Equal(t, \"x({\\\"foo\\\":\\\"bar\\\"});\", w1.Body.String())\n\tassert.Equal(t, \"application/javascript; charset=utf-8\", w1.Header().Get(\"Content-Type\"))\n\n\tw2 := httptest.NewRecorder()\n\tdatas := []map[string]any{{\n\t\t\"foo\": \"bar\",\n\t}, {\n\t\t\"bar\": \"foo\",\n\t}}\n\n\terr2 := (JsonpJSON{\"x\", datas}).Render(w2)\n\trequire.NoError(t, err2)\n\tassert.Equal(t, \"x([{\\\"foo\\\":\\\"bar\\\"},{\\\"bar\\\":\\\"foo\\\"}]);\", w2.Body.String())\n\tassert.Equal(t, \"application/javascript; charset=utf-8\", w2.Header().Get(\"Content-Type\"))\n}\n\ntype errorWriter struct {\n\tbufString    string\n\tErrThreshold int // 1-based threshold. If 1, errors on the 1st Write call.\n\twriteCount   int\n\t*httptest.ResponseRecorder\n}\n\nvar _ http.ResponseWriter = (*errorWriter)(nil)\n\nfunc (w *errorWriter) Header() http.Header {\n\tif w.ResponseRecorder == nil {\n\t\tw.ResponseRecorder = httptest.NewRecorder()\n\t}\n\treturn w.ResponseRecorder.Header()\n}\n\nfunc (w *errorWriter) WriteHeader(statusCode int) {\n\tif w.ResponseRecorder == nil {\n\t\tw.ResponseRecorder = httptest.NewRecorder()\n\t}\n\tw.ResponseRecorder.WriteHeader(statusCode)\n}\n\nfunc (w *errorWriter) Write(buf []byte) (int, error) {\n\tw.writeCount++\n\tif (w.bufString != \"\" && string(buf) == w.bufString) || (w.ErrThreshold > 0 && w.writeCount >= w.ErrThreshold) {\n\t\treturn 0, errors.New(`write error`)\n\t}\n\tif w.ResponseRecorder == nil {\n\t\tw.ResponseRecorder = httptest.NewRecorder()\n\t}\n\treturn w.ResponseRecorder.Write(buf)\n}\n\nfunc (w *errorWriter) reset() {\n\tw.writeCount = 0\n\tw.ResponseRecorder = httptest.NewRecorder()\n}\n\nfunc TestRenderJsonpJSONError(t *testing.T) {\n\tew := &errorWriter{\n\t\tResponseRecorder: httptest.NewRecorder(),\n\t}\n\n\tjsonpJSON := JsonpJSON{\n\t\tCallback: \"foo\",\n\t\tData: map[string]string{\n\t\t\t\"foo\": \"bar\",\n\t\t},\n\t}\n\n\t// error was returned while writing callback\n\tew.reset()\n\tew.ErrThreshold = 1\n\terr := jsonpJSON.Render(ew)\n\trequire.Error(t, err)\n\tassert.Equal(t, \"write error\", err.Error())\n\n\t// error was returned while writing \"(\"\n\tew.reset()\n\tew.ErrThreshold = 2\n\terr = jsonpJSON.Render(ew)\n\trequire.Error(t, err)\n\tassert.Equal(t, \"write error\", err.Error())\n\n\t// error was returned while writing data\n\tew.reset()\n\tew.ErrThreshold = 3\n\terr = jsonpJSON.Render(ew)\n\trequire.Error(t, err)\n\tassert.Equal(t, \"write error\", err.Error())\n\n\t// error was returned while writing \");\"\n\tew.reset()\n\tew.ErrThreshold = 4\n\terr = jsonpJSON.Render(ew)\n\trequire.Error(t, err)\n\tassert.Equal(t, \"write error\", err.Error())\n}\n\nfunc TestRenderJsonpJSONError2(t *testing.T) {\n\tw := httptest.NewRecorder()\n\tdata := map[string]any{\n\t\t\"foo\": \"bar\",\n\t}\n\t(JsonpJSON{\"\", data}).WriteContentType(w)\n\tassert.Equal(t, \"application/javascript; charset=utf-8\", w.Header().Get(\"Content-Type\"))\n\n\te := (JsonpJSON{\"\", data}).Render(w)\n\trequire.NoError(t, e)\n\n\tassert.JSONEq(t, \"{\\\"foo\\\":\\\"bar\\\"}\", w.Body.String())\n\tassert.Equal(t, \"application/javascript; charset=utf-8\", w.Header().Get(\"Content-Type\"))\n}\n\nfunc TestRenderJsonpJSONFail(t *testing.T) {\n\tw := httptest.NewRecorder()\n\tdata := make(chan int)\n\n\t// json: unsupported type: chan int\n\terr := (JsonpJSON{\"x\", data}).Render(w)\n\trequire.Error(t, err)\n}\n\nfunc TestRenderAsciiJSON(t *testing.T) {\n\tw1 := httptest.NewRecorder()\n\tdata1 := map[string]any{\n\t\t\"lang\": \"GO语言\",\n\t\t\"tag\":  \"<br>\",\n\t}\n\n\terr := (AsciiJSON{data1}).Render(w1)\n\n\trequire.NoError(t, err)\n\tassert.JSONEq(t, \"{\\\"lang\\\":\\\"GO\\\\u8bed\\\\u8a00\\\",\\\"tag\\\":\\\"\\\\u003cbr\\\\u003e\\\"}\", w1.Body.String())\n\tassert.Equal(t, \"application/json\", w1.Header().Get(\"Content-Type\"))\n\n\tw2 := httptest.NewRecorder()\n\tdata2 := 3.1415926\n\n\terr = (AsciiJSON{data2}).Render(w2)\n\trequire.NoError(t, err)\n\tassert.Equal(t, \"3.1415926\", w2.Body.String())\n}\n\nfunc TestRenderAsciiJSONFail(t *testing.T) {\n\tw := httptest.NewRecorder()\n\tdata := make(chan int)\n\n\t// json: unsupported type: chan int\n\trequire.Error(t, (AsciiJSON{data}).Render(w))\n}\n\nfunc TestRenderPureJSON(t *testing.T) {\n\tw := httptest.NewRecorder()\n\tdata := map[string]any{\n\t\t\"foo\":  \"bar\",\n\t\t\"html\": \"<b>\",\n\t}\n\terr := (PureJSON{data}).Render(w)\n\trequire.NoError(t, err)\n\tassert.JSONEq(t, \"{\\\"foo\\\":\\\"bar\\\",\\\"html\\\":\\\"<b>\\\"}\\n\", w.Body.String())\n\tassert.Equal(t, \"application/json; charset=utf-8\", w.Header().Get(\"Content-Type\"))\n}\n\ntype xmlmap map[string]any\n\n// Allows type H to be used with xml.Marshal\nfunc (h xmlmap) MarshalXML(e *xml.Encoder, start xml.StartElement) error {\n\tstart.Name = xml.Name{\n\t\tSpace: \"\",\n\t\tLocal: \"map\",\n\t}\n\tif err := e.EncodeToken(start); err != nil {\n\t\treturn err\n\t}\n\tfor key, value := range h {\n\t\telem := xml.StartElement{\n\t\t\tName: xml.Name{Space: \"\", Local: key},\n\t\t\tAttr: []xml.Attr{},\n\t\t}\n\t\tif err := e.EncodeElement(value, elem); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\treturn e.EncodeToken(xml.EndElement{Name: start.Name})\n}\n\nfunc TestRenderYAML(t *testing.T) {\n\tw := httptest.NewRecorder()\n\tdata := `\na : Easy!\nb:\n\tc: 2\n\td: [3, 4]\n\t`\n\t(YAML{data}).WriteContentType(w)\n\tassert.Equal(t, \"application/yaml; charset=utf-8\", w.Header().Get(\"Content-Type\"))\n\n\terr := (YAML{data}).Render(w)\n\trequire.NoError(t, err)\n\n\t// With github.com/goccy/go-yaml, the output format is different from gopkg.in/yaml.v3\n\t// We're checking that the output contains the expected data, not the exact formatting\n\toutput := w.Body.String()\n\tassert.Contains(t, output, \"a : Easy!\")\n\tassert.Contains(t, output, \"b:\")\n\tassert.Contains(t, output, \"c: 2\")\n\tassert.Contains(t, output, \"d: [3, 4]\")\n\tassert.Equal(t, \"application/yaml; charset=utf-8\", w.Header().Get(\"Content-Type\"))\n}\n\ntype fail struct{}\n\n// Hook MarshalYAML\nfunc (ft *fail) MarshalYAML() (any, error) {\n\treturn nil, errors.New(\"fail\")\n}\n\nfunc TestRenderYAMLFail(t *testing.T) {\n\tw := httptest.NewRecorder()\n\terr := (YAML{&fail{}}).Render(w)\n\trequire.Error(t, err)\n}\n\nfunc TestRenderTOML(t *testing.T) {\n\tw := httptest.NewRecorder()\n\tdata := map[string]any{\n\t\t\"foo\":  \"bar\",\n\t\t\"html\": \"<b>\",\n\t}\n\t(TOML{data}).WriteContentType(w)\n\tassert.Equal(t, \"application/toml; charset=utf-8\", w.Header().Get(\"Content-Type\"))\n\n\terr := (TOML{data}).Render(w)\n\trequire.NoError(t, err)\n\tassert.Equal(t, \"foo = 'bar'\\nhtml = '<b>'\\n\", w.Body.String())\n\tassert.Equal(t, \"application/toml; charset=utf-8\", w.Header().Get(\"Content-Type\"))\n}\n\nfunc TestRenderTOMLFail(t *testing.T) {\n\tw := httptest.NewRecorder()\n\terr := (TOML{net.IPv4bcast}).Render(w)\n\trequire.Error(t, err)\n}\n\n// test Protobuf rendering\nfunc TestRenderProtoBuf(t *testing.T) {\n\tw := httptest.NewRecorder()\n\treps := []int64{int64(1), int64(2)}\n\tlabel := \"test\"\n\tdata := &testdata.Test{\n\t\tLabel: &label,\n\t\tReps:  reps,\n\t}\n\n\t(ProtoBuf{data}).WriteContentType(w)\n\tprotoData, err := proto.Marshal(data)\n\trequire.NoError(t, err)\n\tassert.Equal(t, \"application/x-protobuf\", w.Header().Get(\"Content-Type\"))\n\n\terr = (ProtoBuf{data}).Render(w)\n\n\trequire.NoError(t, err)\n\tassert.Equal(t, string(protoData), w.Body.String())\n\tassert.Equal(t, \"application/x-protobuf\", w.Header().Get(\"Content-Type\"))\n}\n\nfunc TestRenderProtoBufFail(t *testing.T) {\n\tw := httptest.NewRecorder()\n\tdata := &testdata.Test{}\n\terr := (ProtoBuf{data}).Render(w)\n\trequire.Error(t, err)\n}\n\nfunc TestRenderBSON(t *testing.T) {\n\tw := httptest.NewRecorder()\n\treps := []int64{int64(1), int64(2)}\n\ttype mystruct struct {\n\t\tLabel string\n\t\tReps  []int64\n\t}\n\n\tdata := &mystruct{\n\t\tLabel: \"test\",\n\t\tReps:  reps,\n\t}\n\n\t(BSON{data}).WriteContentType(w)\n\tbsonData, err := bson.Marshal(data)\n\trequire.NoError(t, err)\n\tassert.Equal(t, \"application/bson\", w.Header().Get(\"Content-Type\"))\n\n\terr = (BSON{data}).Render(w)\n\n\trequire.NoError(t, err)\n\tassert.Equal(t, bsonData, w.Body.Bytes())\n\tassert.Equal(t, \"application/bson\", w.Header().Get(\"Content-Type\"))\n}\n\nfunc TestRenderBSONError(t *testing.T) {\n\tw := httptest.NewRecorder()\n\tdata := make(chan int)\n\n\terr := (BSON{data}).Render(w)\n\trequire.Error(t, err)\n}\n\nfunc TestRenderBSONWriteError(t *testing.T) {\n\ttype testStruct struct {\n\t\tValue string\n\t}\n\tdata := &testStruct{Value: \"test\"}\n\n\tew := &errorWriter{\n\t\tErrThreshold:     1,\n\t\tResponseRecorder: httptest.NewRecorder(),\n\t}\n\n\terr := (BSON{data}).Render(ew)\n\trequire.Error(t, err)\n\tassert.Equal(t, \"write error\", err.Error())\n}\n\nfunc TestRenderXML(t *testing.T) {\n\tw := httptest.NewRecorder()\n\tdata := xmlmap{\n\t\t\"foo\": \"bar\",\n\t}\n\n\t(XML{data}).WriteContentType(w)\n\tassert.Equal(t, \"application/xml; charset=utf-8\", w.Header().Get(\"Content-Type\"))\n\n\terr := (XML{data}).Render(w)\n\n\trequire.NoError(t, err)\n\tassert.Equal(t, \"<map><foo>bar</foo></map>\", w.Body.String())\n\tassert.Equal(t, \"application/xml; charset=utf-8\", w.Header().Get(\"Content-Type\"))\n}\n\nfunc TestRenderXMLError(t *testing.T) {\n\tw := httptest.NewRecorder()\n\tdata := make(chan int)\n\n\terr := (XML{data}).Render(w)\n\trequire.Error(t, err)\n\tassert.Contains(t, err.Error(), \"xml: unsupported type: chan int\")\n}\n\nfunc TestRenderPDF(t *testing.T) {\n\tw := httptest.NewRecorder()\n\tdata := []byte(\"%Test pdf content\")\n\n\tpdf := PDF{data}\n\n\tpdf.WriteContentType(w)\n\tassert.Equal(t, \"application/pdf\", w.Header().Get(\"Content-Type\"))\n\n\terr := pdf.Render(w)\n\trequire.NoError(t, err)\n\n\tassert.Equal(t, data, w.Body.Bytes())\n\tassert.Equal(t, \"application/pdf\", w.Header().Get(\"Content-Type\"))\n}\n\nfunc TestRenderRedirect(t *testing.T) {\n\treq, err := http.NewRequest(http.MethodGet, \"/test-redirect\", nil)\n\trequire.NoError(t, err)\n\n\tdata1 := Redirect{\n\t\tCode:     http.StatusMovedPermanently,\n\t\tRequest:  req,\n\t\tLocation: \"/new/location\",\n\t}\n\n\tw := httptest.NewRecorder()\n\terr = data1.Render(w)\n\trequire.NoError(t, err)\n\n\tdata2 := Redirect{\n\t\tCode:     http.StatusOK,\n\t\tRequest:  req,\n\t\tLocation: \"/new/location\",\n\t}\n\n\tw = httptest.NewRecorder()\n\tassert.PanicsWithValue(t, \"Cannot redirect with status code 200\", func() {\n\t\terr := data2.Render(w)\n\t\trequire.NoError(t, err)\n\t})\n\n\tdata3 := Redirect{\n\t\tCode:     http.StatusCreated,\n\t\tRequest:  req,\n\t\tLocation: \"/new/location\",\n\t}\n\n\tw = httptest.NewRecorder()\n\terr = data3.Render(w)\n\trequire.NoError(t, err)\n\n\t// only improve coverage\n\tdata2.WriteContentType(w)\n}\n\nfunc TestRenderData(t *testing.T) {\n\tw := httptest.NewRecorder()\n\tdata := []byte(\"#!PNG some raw data\")\n\n\terr := (Data{\n\t\tContentType: \"image/png\",\n\t\tData:        data,\n\t}).Render(w)\n\n\trequire.NoError(t, err)\n\tassert.Equal(t, \"#!PNG some raw data\", w.Body.String())\n\tassert.Equal(t, \"image/png\", w.Header().Get(\"Content-Type\"))\n\tassert.Equal(t, \"19\", w.Header().Get(\"Content-Length\"))\n}\n\nfunc TestRenderDataContentLength(t *testing.T) {\n\tsrv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\tsize, err := strconv.Atoi(r.URL.Query().Get(\"size\"))\n\t\tassert.NoError(t, err)\n\n\t\tdata := Data{\n\t\t\tContentType: \"application/octet-stream\",\n\t\t\tData:        make([]byte, size),\n\t\t}\n\t\tassert.NoError(t, data.Render(w))\n\t}))\n\tt.Cleanup(srv.Close)\n\n\tfor _, size := range []int{0, 1, 100, 100_000} {\n\t\tt.Run(strconv.Itoa(size), func(t *testing.T) {\n\t\t\tresp, err := http.Get(srv.URL + \"?size=\" + strconv.Itoa(size))\n\t\t\trequire.NoError(t, err)\n\t\t\tdefer resp.Body.Close()\n\n\t\t\tassert.Equal(t, \"application/octet-stream\", resp.Header.Get(\"Content-Type\"))\n\t\t\tassert.Equal(t, strconv.Itoa(size), resp.Header.Get(\"Content-Length\"))\n\n\t\t\tactual, err := io.Copy(io.Discard, resp.Body)\n\t\t\trequire.NoError(t, err)\n\t\t\tassert.EqualValues(t, size, actual)\n\t\t})\n\t}\n}\n\nfunc TestRenderDataError(t *testing.T) {\n\tew := &errorWriter{\n\t\tErrThreshold:     1,\n\t\tResponseRecorder: httptest.NewRecorder(),\n\t}\n\tdata := []byte(\"#!PNG some raw data\")\n\n\terr := (Data{\n\t\tContentType: \"image/png\",\n\t\tData:        data,\n\t}).Render(ew)\n\n\trequire.Error(t, err)\n\tassert.Equal(t, \"write error\", err.Error())\n}\n\nfunc TestRenderString(t *testing.T) {\n\tw := httptest.NewRecorder()\n\n\t(String{\n\t\tFormat: \"hello %s %d\",\n\t\tData:   []any{},\n\t}).WriteContentType(w)\n\tassert.Equal(t, \"text/plain; charset=utf-8\", w.Header().Get(\"Content-Type\"))\n\n\terr := (String{\n\t\tFormat: \"hola %s %d\",\n\t\tData:   []any{\"manu\", 2},\n\t}).Render(w)\n\n\trequire.NoError(t, err)\n\tassert.Equal(t, \"hola manu 2\", w.Body.String())\n\tassert.Equal(t, \"text/plain; charset=utf-8\", w.Header().Get(\"Content-Type\"))\n}\n\nfunc TestRenderStringLenZero(t *testing.T) {\n\tw := httptest.NewRecorder()\n\n\terr := (String{\n\t\tFormat: \"hola %s %d\",\n\t\tData:   []any{},\n\t}).Render(w)\n\n\trequire.NoError(t, err)\n\tassert.Equal(t, \"hola %s %d\", w.Body.String())\n\tassert.Equal(t, \"text/plain; charset=utf-8\", w.Header().Get(\"Content-Type\"))\n}\n\nfunc TestRenderHTMLTemplate(t *testing.T) {\n\tw := httptest.NewRecorder()\n\ttempl := template.Must(template.New(\"t\").Parse(`Hello {{.name}}`))\n\n\thtmlRender := HTMLProduction{Template: templ}\n\tinstance := htmlRender.Instance(\"t\", map[string]any{\n\t\t\"name\": \"alexandernyquist\",\n\t})\n\n\terr := instance.Render(w)\n\n\trequire.NoError(t, err)\n\tassert.Equal(t, \"Hello alexandernyquist\", w.Body.String())\n\tassert.Equal(t, \"text/html; charset=utf-8\", w.Header().Get(\"Content-Type\"))\n}\n\nfunc TestRenderHTMLTemplateEmptyName(t *testing.T) {\n\tw := httptest.NewRecorder()\n\ttempl := template.Must(template.New(\"\").Parse(`Hello {{.name}}`))\n\n\thtmlRender := HTMLProduction{Template: templ}\n\tinstance := htmlRender.Instance(\"\", map[string]any{\n\t\t\"name\": \"alexandernyquist\",\n\t})\n\n\terr := instance.Render(w)\n\n\trequire.NoError(t, err)\n\tassert.Equal(t, \"Hello alexandernyquist\", w.Body.String())\n\tassert.Equal(t, \"text/html; charset=utf-8\", w.Header().Get(\"Content-Type\"))\n}\n\nfunc TestRenderHTMLDebugFiles(t *testing.T) {\n\tw := httptest.NewRecorder()\n\thtmlRender := HTMLDebug{\n\t\tFiles:      []string{\"../testdata/template/hello.tmpl\"},\n\t\tGlob:       \"\",\n\t\tFileSystem: nil,\n\t\tPatterns:   nil,\n\t\tDelims:     Delims{Left: \"{[{\", Right: \"}]}\"},\n\t\tFuncMap:    nil,\n\t}\n\tinstance := htmlRender.Instance(\"hello.tmpl\", map[string]any{\n\t\t\"name\": \"thinkerou\",\n\t})\n\n\terr := instance.Render(w)\n\n\trequire.NoError(t, err)\n\tassert.Equal(t, \"<h1>Hello thinkerou</h1>\", w.Body.String())\n\tassert.Equal(t, \"text/html; charset=utf-8\", w.Header().Get(\"Content-Type\"))\n}\n\nfunc TestRenderHTMLDebugGlob(t *testing.T) {\n\tw := httptest.NewRecorder()\n\thtmlRender := HTMLDebug{\n\t\tFiles:      nil,\n\t\tGlob:       \"../testdata/template/hello*\",\n\t\tFileSystem: nil,\n\t\tPatterns:   nil,\n\t\tDelims:     Delims{Left: \"{[{\", Right: \"}]}\"},\n\t\tFuncMap:    nil,\n\t}\n\tinstance := htmlRender.Instance(\"hello.tmpl\", map[string]any{\n\t\t\"name\": \"thinkerou\",\n\t})\n\n\terr := instance.Render(w)\n\n\trequire.NoError(t, err)\n\tassert.Equal(t, \"<h1>Hello thinkerou</h1>\", w.Body.String())\n\tassert.Equal(t, \"text/html; charset=utf-8\", w.Header().Get(\"Content-Type\"))\n}\n\nfunc TestRenderHTMLDebugFS(t *testing.T) {\n\tw := httptest.NewRecorder()\n\thtmlRender := HTMLDebug{\n\t\tFiles:      nil,\n\t\tGlob:       \"\",\n\t\tFileSystem: http.Dir(\"../testdata/template\"),\n\t\tPatterns:   []string{\"hello.tmpl\"},\n\t\tDelims:     Delims{Left: \"{[{\", Right: \"}]}\"},\n\t\tFuncMap:    nil,\n\t}\n\tinstance := htmlRender.Instance(\"hello.tmpl\", map[string]any{\n\t\t\"name\": \"thinkerou\",\n\t})\n\n\terr := instance.Render(w)\n\n\trequire.NoError(t, err)\n\tassert.Equal(t, \"<h1>Hello thinkerou</h1>\", w.Body.String())\n\tassert.Equal(t, \"text/html; charset=utf-8\", w.Header().Get(\"Content-Type\"))\n}\n\nfunc TestRenderHTMLDebugPanics(t *testing.T) {\n\thtmlRender := HTMLDebug{\n\t\tFiles:      nil,\n\t\tGlob:       \"\",\n\t\tFileSystem: nil,\n\t\tPatterns:   nil,\n\t\tDelims:     Delims{\"{{\", \"}}\"},\n\t\tFuncMap:    nil,\n\t}\n\tassert.Panics(t, func() { htmlRender.Instance(\"\", nil) })\n}\n\nfunc TestRenderHTMLTemplateError(t *testing.T) {\n\tw := httptest.NewRecorder()\n\ttempl := template.Must(template.New(\"t\").Parse(`Hello {{if .name}}{{.name.DoesNotExist}}{{end}}`))\n\n\thtmlRender := HTMLProduction{Template: templ}\n\tinstance := htmlRender.Instance(\"t\", map[string]any{\n\t\t\"name\": \"alexandernyquist\",\n\t})\n\n\terr := instance.Render(w)\n\trequire.Error(t, err)\n}\n\nfunc TestRenderHTMLTemplateExecuteError(t *testing.T) {\n\tw := httptest.NewRecorder()\n\ttempl := template.Must(template.New(\"t\").Parse(`Hello {{.name.invalid}}`))\n\n\thtmlRender := HTMLProduction{Template: templ}\n\tinstance := htmlRender.Instance(\"t\", map[string]any{\n\t\t\"name\": \"alexandernyquist\",\n\t})\n\n\terr := instance.Render(w)\n\trequire.Error(t, err)\n}\n\nfunc TestRenderReader(t *testing.T) {\n\tw := httptest.NewRecorder()\n\n\tbody := \"#!PNG some raw data\"\n\theaders := make(map[string]string)\n\theaders[\"Content-Disposition\"] = `attachment; filename=\"filename.png\"`\n\theaders[\"x-request-id\"] = \"requestId\"\n\n\terr := (Reader{\n\t\tContentLength: int64(len(body)),\n\t\tContentType:   \"image/png\",\n\t\tReader:        strings.NewReader(body),\n\t\tHeaders:       headers,\n\t}).Render(w)\n\n\trequire.NoError(t, err)\n\tassert.Equal(t, body, w.Body.String())\n\tassert.Equal(t, \"image/png\", w.Header().Get(\"Content-Type\"))\n\tassert.Equal(t, strconv.Itoa(len(body)), w.Header().Get(\"Content-Length\"))\n\tassert.Equal(t, headers[\"Content-Disposition\"], w.Header().Get(\"Content-Disposition\"))\n\tassert.Equal(t, headers[\"x-request-id\"], w.Header().Get(\"x-request-id\"))\n}\n\nfunc TestRenderReaderNoContentLength(t *testing.T) {\n\tw := httptest.NewRecorder()\n\n\tbody := \"#!PNG some raw data\"\n\theaders := make(map[string]string)\n\theaders[\"Content-Disposition\"] = `attachment; filename=\"filename.png\"`\n\theaders[\"x-request-id\"] = \"requestId\"\n\n\terr := (Reader{\n\t\tContentLength: -1,\n\t\tContentType:   \"image/png\",\n\t\tReader:        strings.NewReader(body),\n\t\tHeaders:       headers,\n\t}).Render(w)\n\n\trequire.NoError(t, err)\n\tassert.Equal(t, body, w.Body.String())\n\tassert.Equal(t, \"image/png\", w.Header().Get(\"Content-Type\"))\n\tassert.NotContains(t, \"Content-Length\", w.Header())\n\tassert.Equal(t, headers[\"Content-Disposition\"], w.Header().Get(\"Content-Disposition\"))\n\tassert.Equal(t, headers[\"x-request-id\"], w.Header().Get(\"x-request-id\"))\n}\n\nfunc TestRenderWriteError(t *testing.T) {\n\tdata := []any{\"value1\", \"value2\"}\n\tprefix := \"my-prefix:\"\n\tr := SecureJSON{Data: data, Prefix: prefix}\n\tew := &errorWriter{\n\t\tErrThreshold:     1,\n\t\tResponseRecorder: httptest.NewRecorder(),\n\t}\n\terr := r.Render(ew)\n\trequire.Error(t, err)\n\tassert.Equal(t, \"write error\", err.Error())\n}\n"
  },
  {
    "path": "render/text.go",
    "content": "// Copyright 2014 Manu Martinez-Almeida. All rights reserved.\n// Use of this source code is governed by a MIT style\n// license that can be found in the LICENSE file.\n\npackage render\n\nimport (\n\t\"fmt\"\n\t\"net/http\"\n\n\t\"github.com/gin-gonic/gin/internal/bytesconv\"\n)\n\n// String contains the given interface object slice and its format.\ntype String struct {\n\tFormat string\n\tData   []any\n}\n\nvar plainContentType = []string{\"text/plain; charset=utf-8\"}\n\n// Render (String) writes data with custom ContentType.\nfunc (r String) Render(w http.ResponseWriter) error {\n\treturn WriteString(w, r.Format, r.Data)\n}\n\n// WriteContentType (String) writes Plain ContentType.\nfunc (r String) WriteContentType(w http.ResponseWriter) {\n\twriteContentType(w, plainContentType)\n}\n\n// WriteString writes data according to its format and write custom ContentType.\nfunc WriteString(w http.ResponseWriter, format string, data []any) (err error) {\n\twriteContentType(w, plainContentType)\n\tif len(data) > 0 {\n\t\t_, err = fmt.Fprintf(w, format, data...)\n\t\treturn\n\t}\n\t_, err = w.Write(bytesconv.StringToBytes(format))\n\treturn\n}\n"
  },
  {
    "path": "render/toml.go",
    "content": "// Copyright 2022 Gin Core Team. All rights reserved.\n// Use of this source code is governed by a MIT style\n// license that can be found in the LICENSE file.\n\npackage render\n\nimport (\n\t\"net/http\"\n\n\t\"github.com/pelletier/go-toml/v2\"\n)\n\n// TOML contains the given interface object.\ntype TOML struct {\n\tData any\n}\n\nvar tomlContentType = []string{\"application/toml; charset=utf-8\"}\n\n// Render (TOML) marshals the given interface object and writes data with custom ContentType.\nfunc (r TOML) Render(w http.ResponseWriter) error {\n\tr.WriteContentType(w)\n\n\tbytes, err := toml.Marshal(r.Data)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\t_, err = w.Write(bytes)\n\treturn err\n}\n\n// WriteContentType (TOML) writes TOML ContentType for response.\nfunc (r TOML) WriteContentType(w http.ResponseWriter) {\n\twriteContentType(w, tomlContentType)\n}\n"
  },
  {
    "path": "render/xml.go",
    "content": "// Copyright 2014 Manu Martinez-Almeida. All rights reserved.\n// Use of this source code is governed by a MIT style\n// license that can be found in the LICENSE file.\n\npackage render\n\nimport (\n\t\"encoding/xml\"\n\t\"net/http\"\n)\n\n// XML contains the given interface object.\ntype XML struct {\n\tData any\n}\n\nvar xmlContentType = []string{\"application/xml; charset=utf-8\"}\n\n// Render (XML) encodes the given interface object and writes data with custom ContentType.\nfunc (r XML) Render(w http.ResponseWriter) error {\n\tr.WriteContentType(w)\n\treturn xml.NewEncoder(w).Encode(r.Data)\n}\n\n// WriteContentType (XML) writes XML ContentType for response.\nfunc (r XML) WriteContentType(w http.ResponseWriter) {\n\twriteContentType(w, xmlContentType)\n}\n"
  },
  {
    "path": "render/yaml.go",
    "content": "// Copyright 2014 Manu Martinez-Almeida. All rights reserved.\n// Use of this source code is governed by a MIT style\n// license that can be found in the LICENSE file.\n\npackage render\n\nimport (\n\t\"net/http\"\n\n\t\"github.com/goccy/go-yaml\"\n)\n\n// YAML contains the given interface object.\ntype YAML struct {\n\tData any\n}\n\nvar yamlContentType = []string{\"application/yaml; charset=utf-8\"}\n\n// Render (YAML) marshals the given interface object and writes data with custom ContentType.\nfunc (r YAML) Render(w http.ResponseWriter) error {\n\tr.WriteContentType(w)\n\n\tbytes, err := yaml.Marshal(r.Data)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\t_, err = w.Write(bytes)\n\treturn err\n}\n\n// WriteContentType (YAML) writes YAML ContentType for response.\nfunc (r YAML) WriteContentType(w http.ResponseWriter) {\n\twriteContentType(w, yamlContentType)\n}\n"
  },
  {
    "path": "response_writer.go",
    "content": "// Copyright 2014 Manu Martinez-Almeida. All rights reserved.\n// Use of this source code is governed by a MIT style\n// license that can be found in the LICENSE file.\n\npackage gin\n\nimport (\n\t\"bufio\"\n\t\"errors\"\n\t\"io\"\n\t\"net\"\n\t\"net/http\"\n)\n\nconst (\n\tnoWritten     = -1\n\tdefaultStatus = http.StatusOK\n)\n\nvar errHijackAlreadyWritten = errors.New(\"gin: response body already written\")\n\n// ResponseWriter ...\ntype ResponseWriter interface {\n\thttp.ResponseWriter\n\thttp.Hijacker\n\thttp.Flusher\n\thttp.CloseNotifier\n\n\t// Status returns the HTTP response status code of the current request.\n\tStatus() int\n\n\t// Size returns the number of bytes already written into the response http body.\n\t// See Written()\n\tSize() int\n\n\t// WriteString writes the string into the response body.\n\tWriteString(string) (int, error)\n\n\t// Written returns true if the response body was already written.\n\tWritten() bool\n\n\t// WriteHeaderNow forces to write the http header (status code + headers).\n\tWriteHeaderNow()\n\n\t// Pusher get the http.Pusher for server push\n\tPusher() http.Pusher\n}\n\ntype responseWriter struct {\n\thttp.ResponseWriter\n\tsize   int\n\tstatus int\n}\n\nvar _ ResponseWriter = (*responseWriter)(nil)\n\nfunc (w *responseWriter) Unwrap() http.ResponseWriter {\n\treturn w.ResponseWriter\n}\n\nfunc (w *responseWriter) reset(writer http.ResponseWriter) {\n\tw.ResponseWriter = writer\n\tw.size = noWritten\n\tw.status = defaultStatus\n}\n\nfunc (w *responseWriter) WriteHeader(code int) {\n\tif code > 0 && w.status != code {\n\t\tif w.Written() {\n\t\t\tdebugPrint(\"[WARNING] Headers were already written. Wanted to override status code %d with %d\", w.status, code)\n\t\t\treturn\n\t\t}\n\t\tw.status = code\n\t}\n}\n\nfunc (w *responseWriter) WriteHeaderNow() {\n\tif !w.Written() {\n\t\tw.size = 0\n\t\tw.ResponseWriter.WriteHeader(w.status)\n\t}\n}\n\nfunc (w *responseWriter) Write(data []byte) (n int, err error) {\n\tw.WriteHeaderNow()\n\tn, err = w.ResponseWriter.Write(data)\n\tw.size += n\n\treturn\n}\n\nfunc (w *responseWriter) WriteString(s string) (n int, err error) {\n\tw.WriteHeaderNow()\n\tn, err = io.WriteString(w.ResponseWriter, s)\n\tw.size += n\n\treturn\n}\n\nfunc (w *responseWriter) Status() int {\n\treturn w.status\n}\n\nfunc (w *responseWriter) Size() int {\n\treturn w.size\n}\n\nfunc (w *responseWriter) Written() bool {\n\treturn w.size != noWritten\n}\n\n// Hijack implements the http.Hijacker interface.\nfunc (w *responseWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) {\n\t// Allow hijacking before any data is written (size == -1) or after headers are written (size == 0),\n\t// but not after body data is written (size > 0). For compatibility with websocket libraries (e.g., github.com/coder/websocket)\n\tif w.size > 0 {\n\t\treturn nil, nil, errHijackAlreadyWritten\n\t}\n\tif w.size < 0 {\n\t\tw.size = 0\n\t}\n\treturn w.ResponseWriter.(http.Hijacker).Hijack()\n}\n\n// CloseNotify implements the http.CloseNotifier interface.\nfunc (w *responseWriter) CloseNotify() <-chan bool {\n\treturn w.ResponseWriter.(http.CloseNotifier).CloseNotify()\n}\n\n// Flush implements the http.Flusher interface.\nfunc (w *responseWriter) Flush() {\n\tw.WriteHeaderNow()\n\tif f, ok := w.ResponseWriter.(http.Flusher); ok {\n\t\tf.Flush()\n\t}\n}\n\nfunc (w *responseWriter) Pusher() (pusher http.Pusher) {\n\tif pusher, ok := w.ResponseWriter.(http.Pusher); ok {\n\t\treturn pusher\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "response_writer_test.go",
    "content": "// Copyright 2014 Manu Martinez-Almeida. All rights reserved.\n// Use of this source code is governed by a MIT style\n// license that can be found in the LICENSE file.\n\npackage gin\n\nimport (\n\t\"bufio\"\n\t\"net\"\n\t\"net/http\"\n\t\"net/http/httptest\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n)\n\n// TODO\n// func (w *responseWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) {\n// func (w *responseWriter) CloseNotify() <-chan bool {\n// func (w *responseWriter) Flush() {\n\nvar (\n\t_ ResponseWriter      = &responseWriter{}\n\t_ http.ResponseWriter = &responseWriter{}\n\t_ http.ResponseWriter = ResponseWriter(&responseWriter{})\n\t_ http.Hijacker       = ResponseWriter(&responseWriter{})\n\t_ http.Flusher        = ResponseWriter(&responseWriter{})\n\t_ http.CloseNotifier  = ResponseWriter(&responseWriter{})\n)\n\nfunc init() {\n\tSetMode(TestMode)\n}\n\nfunc TestResponseWriterUnwrap(t *testing.T) {\n\ttestWriter := httptest.NewRecorder()\n\twriter := &responseWriter{ResponseWriter: testWriter}\n\tassert.Same(t, testWriter, writer.Unwrap())\n}\n\nfunc TestResponseWriterReset(t *testing.T) {\n\ttestWriter := httptest.NewRecorder()\n\twriter := &responseWriter{}\n\tvar w ResponseWriter = writer\n\n\twriter.reset(testWriter)\n\tassert.Equal(t, -1, writer.size)\n\tassert.Equal(t, http.StatusOK, writer.status)\n\tassert.Equal(t, testWriter, writer.ResponseWriter)\n\tassert.Equal(t, -1, w.Size())\n\tassert.Equal(t, http.StatusOK, w.Status())\n\tassert.False(t, w.Written())\n}\n\nfunc TestResponseWriterWriteHeader(t *testing.T) {\n\ttestWriter := httptest.NewRecorder()\n\twriter := &responseWriter{}\n\twriter.reset(testWriter)\n\tw := ResponseWriter(writer)\n\n\tw.WriteHeader(http.StatusMultipleChoices)\n\tassert.False(t, w.Written())\n\tassert.Equal(t, http.StatusMultipleChoices, w.Status())\n\tassert.NotEqual(t, http.StatusMultipleChoices, testWriter.Code)\n\n\tw.WriteHeader(-1)\n\tassert.Equal(t, http.StatusMultipleChoices, w.Status())\n}\n\nfunc TestResponseWriterWriteHeadersNow(t *testing.T) {\n\ttestWriter := httptest.NewRecorder()\n\twriter := &responseWriter{}\n\twriter.reset(testWriter)\n\tw := ResponseWriter(writer)\n\n\tw.WriteHeader(http.StatusMultipleChoices)\n\tw.WriteHeaderNow()\n\n\tassert.True(t, w.Written())\n\tassert.Equal(t, 0, w.Size())\n\tassert.Equal(t, http.StatusMultipleChoices, testWriter.Code)\n\n\twriter.size = 10\n\tw.WriteHeaderNow()\n\tassert.Equal(t, 10, w.Size())\n}\n\nfunc TestResponseWriterWrite(t *testing.T) {\n\ttestWriter := httptest.NewRecorder()\n\twriter := &responseWriter{}\n\twriter.reset(testWriter)\n\tw := ResponseWriter(writer)\n\n\tn, err := w.Write([]byte(\"hola\"))\n\tassert.Equal(t, 4, n)\n\tassert.Equal(t, 4, w.Size())\n\tassert.Equal(t, http.StatusOK, w.Status())\n\tassert.Equal(t, http.StatusOK, testWriter.Code)\n\tassert.Equal(t, \"hola\", testWriter.Body.String())\n\trequire.NoError(t, err)\n\n\tn, err = w.Write([]byte(\" adios\"))\n\tassert.Equal(t, 6, n)\n\tassert.Equal(t, 10, w.Size())\n\tassert.Equal(t, \"hola adios\", testWriter.Body.String())\n\trequire.NoError(t, err)\n}\n\nfunc TestResponseWriterHijack(t *testing.T) {\n\ttestWriter := httptest.NewRecorder()\n\twriter := &responseWriter{}\n\twriter.reset(testWriter)\n\tw := ResponseWriter(writer)\n\n\tassert.Panics(t, func() {\n\t\t_, _, err := w.Hijack()\n\t\trequire.NoError(t, err)\n\t})\n\tassert.True(t, w.Written())\n\n\tassert.Panics(t, func() {\n\t\tw.CloseNotify()\n\t})\n\n\tw.Flush()\n}\n\ntype mockHijacker struct {\n\t*httptest.ResponseRecorder\n\thijacked bool\n}\n\n// Hijack implements the http.Hijacker interface. It just records that it was called.\nfunc (m *mockHijacker) Hijack() (net.Conn, *bufio.ReadWriter, error) {\n\tm.hijacked = true\n\treturn nil, nil, nil\n}\n\nfunc TestResponseWriterHijackAfterWrite(t *testing.T) {\n\ttests := []struct {\n\t\tname                      string\n\t\taction                    func(w ResponseWriter) error // Action to perform before hijacking\n\t\texpectWrittenBeforeHijack bool\n\t\texpectHijackSuccess       bool\n\t\texpectWrittenAfterHijack  bool\n\t\texpectError               error\n\t}{\n\t\t{\n\t\t\tname:                      \"hijack before write should succeed\",\n\t\t\taction:                    func(w ResponseWriter) error { return nil },\n\t\t\texpectWrittenBeforeHijack: false,\n\t\t\texpectHijackSuccess:       true,\n\t\t\texpectWrittenAfterHijack:  true, // Hijack itself marks the writer as written\n\t\t\texpectError:               nil,\n\t\t},\n\t\t{\n\t\t\tname: \"hijack after write should fail\",\n\t\t\taction: func(w ResponseWriter) error {\n\t\t\t\t_, err := w.Write([]byte(\"test\"))\n\t\t\t\treturn err\n\t\t\t},\n\t\t\texpectWrittenBeforeHijack: true,\n\t\t\texpectHijackSuccess:       false,\n\t\t\texpectWrittenAfterHijack:  true,\n\t\t\texpectError:               errHijackAlreadyWritten,\n\t\t},\n\t}\n\n\tfor _, tc := range tests {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\thijacker := &mockHijacker{ResponseRecorder: httptest.NewRecorder()}\n\t\t\twriter := &responseWriter{}\n\t\t\twriter.reset(hijacker)\n\t\t\tw := ResponseWriter(writer)\n\n\t\t\t// Check initial state\n\t\t\tassert.False(t, w.Written(), \"should not be written initially\")\n\n\t\t\t// Perform pre-hijack action\n\t\t\trequire.NoError(t, tc.action(w), \"unexpected error during pre-hijack action\")\n\n\t\t\t// Check state before hijacking\n\t\t\tassert.Equal(t, tc.expectWrittenBeforeHijack, w.Written(), \"unexpected w.Written() state before hijack\")\n\n\t\t\t// Attempt to hijack\n\t\t\t_, _, hijackErr := w.Hijack()\n\n\t\t\t// Check results\n\t\t\trequire.ErrorIs(t, hijackErr, tc.expectError, \"unexpected error from Hijack()\")\n\t\t\tassert.Equal(t, tc.expectHijackSuccess, hijacker.hijacked, \"unexpected hijacker.hijacked state\")\n\t\t\tassert.Equal(t, tc.expectWrittenAfterHijack, w.Written(), \"unexpected w.Written() state after hijack\")\n\t\t})\n\t}\n}\n\n// Test: WebSocket compatibility - allow hijack after WriteHeaderNow(), but block after body data.\nfunc TestResponseWriterHijackAfterWriteHeaderNow(t *testing.T) {\n\ttests := []struct {\n\t\tname                      string\n\t\taction                    func(w ResponseWriter) error\n\t\texpectWrittenBeforeHijack bool\n\t\texpectHijackSuccess       bool\n\t\texpectWrittenAfterHijack  bool\n\t\texpectError               error\n\t}{\n\t\t{\n\t\t\tname: \"hijack after WriteHeaderNow only should succeed (websocket pattern)\",\n\t\t\taction: func(w ResponseWriter) error {\n\t\t\t\tw.WriteHeaderNow() // Simulate websocket.Accept() behavior\n\t\t\t\treturn nil\n\t\t\t},\n\t\t\texpectWrittenBeforeHijack: true,\n\t\t\texpectHijackSuccess:       true, // NEW BEHAVIOR: allow hijack after just header write\n\t\t\texpectWrittenAfterHijack:  true,\n\t\t\texpectError:               nil,\n\t\t},\n\t\t{\n\t\t\tname: \"hijack after WriteHeaderNow + Write should fail\",\n\t\t\taction: func(w ResponseWriter) error {\n\t\t\t\tw.WriteHeaderNow()\n\t\t\t\t_, err := w.Write([]byte(\"test\"))\n\t\t\t\treturn err\n\t\t\t},\n\t\t\texpectWrittenBeforeHijack: true,\n\t\t\texpectHijackSuccess:       false,\n\t\t\texpectWrittenAfterHijack:  true,\n\t\t\texpectError:               errHijackAlreadyWritten,\n\t\t},\n\t}\n\tfor _, tc := range tests {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\thijacker := &mockHijacker{ResponseRecorder: httptest.NewRecorder()}\n\t\t\twriter := &responseWriter{}\n\t\t\twriter.reset(hijacker)\n\t\t\tw := ResponseWriter(writer)\n\n\t\t\trequire.NoError(t, tc.action(w), \"unexpected error during pre-hijack action\")\n\n\t\t\tassert.Equal(t, tc.expectWrittenBeforeHijack, w.Written(), \"unexpected w.Written() state before hijack\")\n\n\t\t\t_, _, hijackErr := w.Hijack()\n\n\t\t\tif tc.expectError == nil {\n\t\t\t\trequire.NoError(t, hijackErr, \"expected hijack to succeed\")\n\t\t\t} else {\n\t\t\t\trequire.ErrorIs(t, hijackErr, tc.expectError, \"unexpected error from Hijack()\")\n\t\t\t}\n\t\t\tassert.Equal(t, tc.expectHijackSuccess, hijacker.hijacked, \"unexpected hijacker.hijacked state\")\n\t\t\tassert.Equal(t, tc.expectWrittenAfterHijack, w.Written(), \"unexpected w.Written() state after hijack\")\n\t\t})\n\t}\n}\n\nfunc TestResponseWriterFlush(t *testing.T) {\n\ttestServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\twriter := &responseWriter{}\n\t\twriter.reset(w)\n\n\t\twriter.WriteHeader(http.StatusInternalServerError)\n\t\twriter.Flush()\n\t}))\n\tdefer testServer.Close()\n\n\t// should return 500\n\tresp, err := http.Get(testServer.URL)\n\trequire.NoError(t, err)\n\tassert.Equal(t, http.StatusInternalServerError, resp.StatusCode)\n}\n\nfunc TestResponseWriterStatusCode(t *testing.T) {\n\ttestWriter := httptest.NewRecorder()\n\twriter := &responseWriter{}\n\twriter.reset(testWriter)\n\tw := ResponseWriter(writer)\n\n\tw.WriteHeader(http.StatusOK)\n\tw.WriteHeaderNow()\n\n\tassert.Equal(t, http.StatusOK, w.Status())\n\tassert.True(t, w.Written())\n\n\tw.WriteHeader(http.StatusUnauthorized)\n\n\t// status must be 200 although we tried to change it\n\tassert.Equal(t, http.StatusOK, w.Status())\n}\n\n// mockPusherResponseWriter is an http.ResponseWriter that implements http.Pusher.\ntype mockPusherResponseWriter struct {\n\thttp.ResponseWriter\n}\n\nfunc (m *mockPusherResponseWriter) Push(target string, opts *http.PushOptions) error {\n\treturn nil\n}\n\n// nonPusherResponseWriter is an http.ResponseWriter that does not implement http.Pusher.\ntype nonPusherResponseWriter struct {\n\thttp.ResponseWriter\n}\n\nfunc TestPusherWithPusher(t *testing.T) {\n\trw := &mockPusherResponseWriter{}\n\tw := &responseWriter{ResponseWriter: rw}\n\n\tpusher := w.Pusher()\n\tassert.NotNil(t, pusher, \"Expected pusher to be non-nil\")\n}\n\nfunc TestPusherWithoutPusher(t *testing.T) {\n\trw := &nonPusherResponseWriter{}\n\tw := &responseWriter{ResponseWriter: rw}\n\n\tpusher := w.Pusher()\n\tassert.Nil(t, pusher, \"Expected pusher to be nil\")\n}\n"
  },
  {
    "path": "routergroup.go",
    "content": "// Copyright 2014 Manu Martinez-Almeida. All rights reserved.\n// Use of this source code is governed by a MIT style\n// license that can be found in the LICENSE file.\n\npackage gin\n\nimport (\n\t\"net/http\"\n\t\"path\"\n\t\"regexp\"\n\t\"strings\"\n)\n\nvar (\n\t// regEnLetter matches english letters for http method name\n\tregEnLetter = regexp.MustCompile(\"^[A-Z]+$\")\n\n\t// anyMethods for RouterGroup Any method\n\tanyMethods = []string{\n\t\thttp.MethodGet, http.MethodPost, http.MethodPut, http.MethodPatch,\n\t\thttp.MethodHead, http.MethodOptions, http.MethodDelete, http.MethodConnect,\n\t\thttp.MethodTrace,\n\t}\n)\n\n// IRouter defines all router handle interface includes single and group router.\ntype IRouter interface {\n\tIRoutes\n\tGroup(string, ...HandlerFunc) *RouterGroup\n}\n\n// IRoutes defines all router handle interface.\ntype IRoutes interface {\n\tUse(...HandlerFunc) IRoutes\n\n\tHandle(string, string, ...HandlerFunc) IRoutes\n\tAny(string, ...HandlerFunc) IRoutes\n\tGET(string, ...HandlerFunc) IRoutes\n\tPOST(string, ...HandlerFunc) IRoutes\n\tDELETE(string, ...HandlerFunc) IRoutes\n\tPATCH(string, ...HandlerFunc) IRoutes\n\tPUT(string, ...HandlerFunc) IRoutes\n\tOPTIONS(string, ...HandlerFunc) IRoutes\n\tHEAD(string, ...HandlerFunc) IRoutes\n\tMatch([]string, string, ...HandlerFunc) IRoutes\n\n\tStaticFile(string, string) IRoutes\n\tStaticFileFS(string, string, http.FileSystem) IRoutes\n\tStatic(string, string) IRoutes\n\tStaticFS(string, http.FileSystem) IRoutes\n}\n\n// RouterGroup is used internally to configure router, a RouterGroup is associated with\n// a prefix and an array of handlers (middleware).\ntype RouterGroup struct {\n\tHandlers HandlersChain\n\tbasePath string\n\tengine   *Engine\n\troot     bool\n}\n\nvar _ IRouter = (*RouterGroup)(nil)\n\n// Use adds middleware to the group, see example code in GitHub.\nfunc (group *RouterGroup) Use(middleware ...HandlerFunc) IRoutes {\n\tgroup.Handlers = append(group.Handlers, middleware...)\n\treturn group.returnObj()\n}\n\n// Group creates a new router group. You should add all the routes that have common middlewares or the same path prefix.\n// For example, all the routes that use a common middleware for authorization could be grouped.\nfunc (group *RouterGroup) Group(relativePath string, handlers ...HandlerFunc) *RouterGroup {\n\treturn &RouterGroup{\n\t\tHandlers: group.combineHandlers(handlers),\n\t\tbasePath: group.calculateAbsolutePath(relativePath),\n\t\tengine:   group.engine,\n\t}\n}\n\n// BasePath returns the base path of router group.\n// For example, if v := router.Group(\"/rest/n/v1/api\"), v.BasePath() is \"/rest/n/v1/api\".\nfunc (group *RouterGroup) BasePath() string {\n\treturn group.basePath\n}\n\nfunc (group *RouterGroup) handle(httpMethod, relativePath string, handlers HandlersChain) IRoutes {\n\tabsolutePath := group.calculateAbsolutePath(relativePath)\n\thandlers = group.combineHandlers(handlers)\n\tgroup.engine.addRoute(httpMethod, absolutePath, handlers)\n\treturn group.returnObj()\n}\n\n// Handle registers a new request handle and middleware with the given path and method.\n// The last handler should be the real handler, the other ones should be middleware that can and should be shared among different routes.\n// See the example code in GitHub.\n//\n// For GET, POST, PUT, PATCH and DELETE requests the respective shortcut\n// functions can be used.\n//\n// This function is intended for bulk loading and to allow the usage of less\n// frequently used, non-standardized or custom methods (e.g. for internal\n// communication with a proxy).\nfunc (group *RouterGroup) Handle(httpMethod, relativePath string, handlers ...HandlerFunc) IRoutes {\n\tif matched := regEnLetter.MatchString(httpMethod); !matched {\n\t\tpanic(\"http method \" + httpMethod + \" is not valid\")\n\t}\n\treturn group.handle(httpMethod, relativePath, handlers)\n}\n\n// POST is a shortcut for router.Handle(\"POST\", path, handlers).\nfunc (group *RouterGroup) POST(relativePath string, handlers ...HandlerFunc) IRoutes {\n\treturn group.handle(http.MethodPost, relativePath, handlers)\n}\n\n// GET is a shortcut for router.Handle(\"GET\", path, handlers).\nfunc (group *RouterGroup) GET(relativePath string, handlers ...HandlerFunc) IRoutes {\n\treturn group.handle(http.MethodGet, relativePath, handlers)\n}\n\n// DELETE is a shortcut for router.Handle(\"DELETE\", path, handlers).\nfunc (group *RouterGroup) DELETE(relativePath string, handlers ...HandlerFunc) IRoutes {\n\treturn group.handle(http.MethodDelete, relativePath, handlers)\n}\n\n// PATCH is a shortcut for router.Handle(\"PATCH\", path, handlers).\nfunc (group *RouterGroup) PATCH(relativePath string, handlers ...HandlerFunc) IRoutes {\n\treturn group.handle(http.MethodPatch, relativePath, handlers)\n}\n\n// PUT is a shortcut for router.Handle(\"PUT\", path, handlers).\nfunc (group *RouterGroup) PUT(relativePath string, handlers ...HandlerFunc) IRoutes {\n\treturn group.handle(http.MethodPut, relativePath, handlers)\n}\n\n// OPTIONS is a shortcut for router.Handle(\"OPTIONS\", path, handlers).\nfunc (group *RouterGroup) OPTIONS(relativePath string, handlers ...HandlerFunc) IRoutes {\n\treturn group.handle(http.MethodOptions, relativePath, handlers)\n}\n\n// HEAD is a shortcut for router.Handle(\"HEAD\", path, handlers).\nfunc (group *RouterGroup) HEAD(relativePath string, handlers ...HandlerFunc) IRoutes {\n\treturn group.handle(http.MethodHead, relativePath, handlers)\n}\n\n// Any registers a route that matches all the HTTP methods.\n// GET, POST, PUT, PATCH, HEAD, OPTIONS, DELETE, CONNECT, TRACE.\nfunc (group *RouterGroup) Any(relativePath string, handlers ...HandlerFunc) IRoutes {\n\tfor _, method := range anyMethods {\n\t\tgroup.handle(method, relativePath, handlers)\n\t}\n\n\treturn group.returnObj()\n}\n\n// Match registers a route that matches the specified methods that you declared.\nfunc (group *RouterGroup) Match(methods []string, relativePath string, handlers ...HandlerFunc) IRoutes {\n\tfor _, method := range methods {\n\t\tgroup.handle(method, relativePath, handlers)\n\t}\n\n\treturn group.returnObj()\n}\n\n// StaticFile registers a single route in order to serve a single file of the local filesystem.\n// router.StaticFile(\"favicon.ico\", \"./resources/favicon.ico\")\nfunc (group *RouterGroup) StaticFile(relativePath, filepath string) IRoutes {\n\treturn group.staticFileHandler(relativePath, func(c *Context) {\n\t\tc.File(filepath)\n\t})\n}\n\n// StaticFileFS works just like `StaticFile` but a custom `http.FileSystem` can be used instead.\n// router.StaticFileFS(\"favicon.ico\", \"./resources/favicon.ico\", Dir{\".\", false})\n// Gin by default uses: gin.Dir()\nfunc (group *RouterGroup) StaticFileFS(relativePath, filepath string, fs http.FileSystem) IRoutes {\n\treturn group.staticFileHandler(relativePath, func(c *Context) {\n\t\tc.FileFromFS(filepath, fs)\n\t})\n}\n\nfunc (group *RouterGroup) staticFileHandler(relativePath string, handler HandlerFunc) IRoutes {\n\tif strings.Contains(relativePath, \":\") || strings.Contains(relativePath, \"*\") {\n\t\tpanic(\"URL parameters can not be used when serving a static file\")\n\t}\n\tgroup.GET(relativePath, handler)\n\tgroup.HEAD(relativePath, handler)\n\treturn group.returnObj()\n}\n\n// Static serves files from the given file system root.\n// Internally a http.FileServer is used, therefore http.NotFound is used instead\n// of the Router's NotFound handler.\n// To use the operating system's file system implementation,\n// use :\n//\n//\trouter.Static(\"/static\", \"/var/www\")\nfunc (group *RouterGroup) Static(relativePath, root string) IRoutes {\n\treturn group.StaticFS(relativePath, Dir(root, false))\n}\n\n// StaticFS works just like `Static()` but a custom `http.FileSystem` can be used instead.\n// Gin by default uses: gin.Dir()\nfunc (group *RouterGroup) StaticFS(relativePath string, fs http.FileSystem) IRoutes {\n\tif strings.Contains(relativePath, \":\") || strings.Contains(relativePath, \"*\") {\n\t\tpanic(\"URL parameters can not be used when serving a static folder\")\n\t}\n\thandler := group.createStaticHandler(relativePath, fs)\n\turlPattern := path.Join(relativePath, \"/*filepath\")\n\n\t// Register GET and HEAD handlers\n\tgroup.GET(urlPattern, handler)\n\tgroup.HEAD(urlPattern, handler)\n\treturn group.returnObj()\n}\n\nfunc (group *RouterGroup) createStaticHandler(relativePath string, fs http.FileSystem) HandlerFunc {\n\tabsolutePath := group.calculateAbsolutePath(relativePath)\n\tfileServer := http.StripPrefix(absolutePath, http.FileServer(fs))\n\n\treturn func(c *Context) {\n\t\tif _, noListing := fs.(*OnlyFilesFS); noListing {\n\t\t\tc.Writer.WriteHeader(http.StatusNotFound)\n\t\t}\n\n\t\tfile := c.Param(\"filepath\")\n\t\t// Check if file exists and/or if we have permission to access it\n\t\tf, err := fs.Open(file)\n\t\tif err != nil {\n\t\t\tc.Writer.WriteHeader(http.StatusNotFound)\n\t\t\tc.handlers = group.engine.noRoute\n\t\t\t// Reset index\n\t\t\tc.index = -1\n\t\t\treturn\n\t\t}\n\t\tf.Close()\n\n\t\tfileServer.ServeHTTP(c.Writer, c.Request)\n\t}\n}\n\nfunc (group *RouterGroup) combineHandlers(handlers HandlersChain) HandlersChain {\n\tfinalSize := len(group.Handlers) + len(handlers)\n\tassert1(finalSize < int(abortIndex), \"too many handlers\")\n\tmergedHandlers := make(HandlersChain, finalSize)\n\tcopy(mergedHandlers, group.Handlers)\n\tcopy(mergedHandlers[len(group.Handlers):], handlers)\n\treturn mergedHandlers\n}\n\nfunc (group *RouterGroup) calculateAbsolutePath(relativePath string) string {\n\treturn joinPaths(group.basePath, relativePath)\n}\n\nfunc (group *RouterGroup) returnObj() IRoutes {\n\tif group.root {\n\t\treturn group.engine\n\t}\n\treturn group\n}\n"
  },
  {
    "path": "routergroup_test.go",
    "content": "// Copyright 2014 Manu Martinez-Almeida. All rights reserved.\n// Use of this source code is governed by a MIT style\n// license that can be found in the LICENSE file.\n\npackage gin\n\nimport (\n\t\"net/http\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n)\n\nvar MaxHandlers = 32\n\nfunc init() {\n\tSetMode(TestMode)\n}\n\nfunc TestRouterGroupBasic(t *testing.T) {\n\trouter := New()\n\tgroup := router.Group(\"/hola\", func(c *Context) {})\n\tgroup.Use(func(c *Context) {})\n\n\tassert.Len(t, group.Handlers, 2)\n\tassert.Equal(t, \"/hola\", group.BasePath())\n\tassert.Equal(t, router, group.engine)\n\n\tgroup2 := group.Group(\"manu\")\n\tgroup2.Use(func(c *Context) {}, func(c *Context) {})\n\n\tassert.Len(t, group2.Handlers, 4)\n\tassert.Equal(t, \"/hola/manu\", group2.BasePath())\n\tassert.Equal(t, router, group2.engine)\n}\n\nfunc TestRouterGroupBasicHandle(t *testing.T) {\n\tperformRequestInGroup(t, http.MethodGet)\n\tperformRequestInGroup(t, http.MethodPost)\n\tperformRequestInGroup(t, http.MethodPut)\n\tperformRequestInGroup(t, http.MethodPatch)\n\tperformRequestInGroup(t, http.MethodDelete)\n\tperformRequestInGroup(t, http.MethodHead)\n\tperformRequestInGroup(t, http.MethodOptions)\n}\n\nfunc performRequestInGroup(t *testing.T, method string) {\n\trouter := New()\n\tv1 := router.Group(\"v1\", func(c *Context) {})\n\tassert.Equal(t, \"/v1\", v1.BasePath())\n\n\tlogin := v1.Group(\"/login/\", func(c *Context) {}, func(c *Context) {})\n\tassert.Equal(t, \"/v1/login/\", login.BasePath())\n\n\thandler := func(c *Context) {\n\t\tc.String(http.StatusBadRequest, \"the method was %s and index %d\", c.Request.Method, c.index)\n\t}\n\n\tswitch method {\n\tcase http.MethodGet:\n\t\tv1.GET(\"/test\", handler)\n\t\tlogin.GET(\"/test\", handler)\n\tcase http.MethodPost:\n\t\tv1.POST(\"/test\", handler)\n\t\tlogin.POST(\"/test\", handler)\n\tcase http.MethodPut:\n\t\tv1.PUT(\"/test\", handler)\n\t\tlogin.PUT(\"/test\", handler)\n\tcase http.MethodPatch:\n\t\tv1.PATCH(\"/test\", handler)\n\t\tlogin.PATCH(\"/test\", handler)\n\tcase http.MethodDelete:\n\t\tv1.DELETE(\"/test\", handler)\n\t\tlogin.DELETE(\"/test\", handler)\n\tcase http.MethodHead:\n\t\tv1.HEAD(\"/test\", handler)\n\t\tlogin.HEAD(\"/test\", handler)\n\tcase http.MethodOptions:\n\t\tv1.OPTIONS(\"/test\", handler)\n\t\tlogin.OPTIONS(\"/test\", handler)\n\tdefault:\n\t\tpanic(\"unknown method\")\n\t}\n\n\tw := PerformRequest(router, method, \"/v1/login/test\")\n\tassert.Equal(t, http.StatusBadRequest, w.Code)\n\tassert.Equal(t, \"the method was \"+method+\" and index 3\", w.Body.String())\n\n\tw = PerformRequest(router, method, \"/v1/test\")\n\tassert.Equal(t, http.StatusBadRequest, w.Code)\n\tassert.Equal(t, \"the method was \"+method+\" and index 1\", w.Body.String())\n}\n\nfunc TestRouterGroupInvalidStatic(t *testing.T) {\n\trouter := New()\n\tassert.Panics(t, func() {\n\t\trouter.Static(\"/path/:param\", \"/\")\n\t})\n\n\tassert.Panics(t, func() {\n\t\trouter.Static(\"/path/*param\", \"/\")\n\t})\n}\n\nfunc TestRouterGroupInvalidStaticFile(t *testing.T) {\n\trouter := New()\n\tassert.Panics(t, func() {\n\t\trouter.StaticFile(\"/path/:param\", \"favicon.ico\")\n\t})\n\n\tassert.Panics(t, func() {\n\t\trouter.StaticFile(\"/path/*param\", \"favicon.ico\")\n\t})\n}\n\nfunc TestRouterGroupInvalidStaticFileFS(t *testing.T) {\n\trouter := New()\n\tassert.Panics(t, func() {\n\t\trouter.StaticFileFS(\"/path/:param\", \"favicon.ico\", Dir(\".\", false))\n\t})\n\n\tassert.Panics(t, func() {\n\t\trouter.StaticFileFS(\"/path/*param\", \"favicon.ico\", Dir(\".\", false))\n\t})\n}\n\nfunc TestRouterGroupTooManyHandlers(t *testing.T) {\n\tconst (\n\t\tpanicValue = \"too many handlers\"\n\t\tmaximumCnt = abortIndex\n\t)\n\trouter := New()\n\thandlers1 := make([]HandlerFunc, maximumCnt-1)\n\trouter.Use(handlers1...)\n\n\thandlers2 := make([]HandlerFunc, maximumCnt+1)\n\tassert.PanicsWithValue(t, panicValue, func() {\n\t\trouter.Use(handlers2...)\n\t})\n\tassert.PanicsWithValue(t, panicValue, func() {\n\t\trouter.GET(\"/\", handlers2...)\n\t})\n}\n\nfunc TestRouterGroupBadMethod(t *testing.T) {\n\trouter := New()\n\tassert.Panics(t, func() {\n\t\trouter.Handle(http.MethodGet, \"/\")\n\t})\n\tassert.Panics(t, func() {\n\t\trouter.Handle(\" GET\", \"/\")\n\t})\n\tassert.Panics(t, func() {\n\t\trouter.Handle(\"GET \", \"/\")\n\t})\n\tassert.Panics(t, func() {\n\t\trouter.Handle(\"\", \"/\")\n\t})\n\tassert.Panics(t, func() {\n\t\trouter.Handle(\"PO ST\", \"/\")\n\t})\n\tassert.Panics(t, func() {\n\t\trouter.Handle(\"1GET\", \"/\")\n\t})\n\tassert.Panics(t, func() {\n\t\trouter.Handle(\"PATCh\", \"/\")\n\t})\n}\n\nfunc TestRouterGroupPipeline(t *testing.T) {\n\trouter := New()\n\ttestRoutesInterface(t, router)\n\n\tv1 := router.Group(\"/v1\")\n\ttestRoutesInterface(t, v1)\n}\n\nfunc testRoutesInterface(t *testing.T, r IRoutes) {\n\thandler := func(c *Context) {}\n\tassert.Equal(t, r, r.Use(handler))\n\n\tassert.Equal(t, r, r.Handle(http.MethodGet, \"/handler\", handler))\n\tassert.Equal(t, r, r.Any(\"/any\", handler))\n\tassert.Equal(t, r, r.GET(\"/\", handler))\n\tassert.Equal(t, r, r.POST(\"/\", handler))\n\tassert.Equal(t, r, r.DELETE(\"/\", handler))\n\tassert.Equal(t, r, r.PATCH(\"/\", handler))\n\tassert.Equal(t, r, r.PUT(\"/\", handler))\n\tassert.Equal(t, r, r.OPTIONS(\"/\", handler))\n\tassert.Equal(t, r, r.HEAD(\"/\", handler))\n\tassert.Equal(t, r, r.Match([]string{http.MethodPut, http.MethodPatch}, \"/match\", handler))\n\n\tassert.Equal(t, r, r.StaticFile(\"/file\", \".\"))\n\tassert.Equal(t, r, r.StaticFileFS(\"/static2\", \".\", Dir(\".\", false)))\n\tassert.Equal(t, r, r.Static(\"/static\", \".\"))\n\tassert.Equal(t, r, r.StaticFS(\"/static2\", Dir(\".\", false)))\n}\n\nfunc TestRouterGroupCombineHandlersTooManyHandlers(t *testing.T) {\n\tgroup := &RouterGroup{\n\t\tHandlers: make(HandlersChain, MaxHandlers), // Assume group already has MaxHandlers middleware\n\t}\n\ttooManyHandlers := make(HandlersChain, MaxHandlers) // Add MaxHandlers more, total 2 * MaxHandlers\n\n\t// This should trigger panic\n\tassert.Panics(t, func() {\n\t\tgroup.combineHandlers(tooManyHandlers)\n\t}, \"should panic due to too many handlers\")\n}\n\nfunc TestRouterGroupCombineHandlersEmptySliceNotNil(t *testing.T) {\n\tgroup := &RouterGroup{\n\t\tHandlers: HandlersChain{},\n\t}\n\n\tresult := group.combineHandlers(HandlersChain{})\n\tassert.NotNil(t, result, \"result should not be nil even with empty handlers\")\n\tassert.Empty(t, result, \"empty handlers should return empty chain\")\n}\n"
  },
  {
    "path": "routes_test.go",
    "content": "// Copyright 2014 Manu Martinez-Almeida. All rights reserved.\n// Use of this source code is governed by a MIT style\n// license that can be found in the LICENSE file.\n\npackage gin\n\nimport (\n\t\"fmt\"\n\t\"net/http\"\n\t\"net/http/httptest\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n)\n\ntype header struct {\n\tKey   string\n\tValue string\n}\n\n// PerformRequest for testing gin router.\nfunc PerformRequest(r http.Handler, method, path string, headers ...header) *httptest.ResponseRecorder {\n\treq := httptest.NewRequest(method, path, nil)\n\tfor _, h := range headers {\n\t\treq.Header.Add(h.Key, h.Value)\n\t}\n\tw := httptest.NewRecorder()\n\tr.ServeHTTP(w, req)\n\treturn w\n}\n\nfunc testRouteOK(method string, t *testing.T) {\n\tpassed := false\n\tpassedAny := false\n\tr := New()\n\tr.Any(\"/test2\", func(c *Context) {\n\t\tpassedAny = true\n\t})\n\tr.Handle(method, \"/test\", func(c *Context) {\n\t\tpassed = true\n\t})\n\n\tw := PerformRequest(r, method, \"/test\")\n\tassert.True(t, passed)\n\tassert.Equal(t, http.StatusOK, w.Code)\n\n\tPerformRequest(r, method, \"/test2\")\n\tassert.True(t, passedAny)\n}\n\n// TestSingleRouteOK tests that POST route is correctly invoked.\nfunc testRouteNotOK(method string, t *testing.T) {\n\tpassed := false\n\trouter := New()\n\trouter.Handle(method, \"/test_2\", func(c *Context) {\n\t\tpassed = true\n\t})\n\n\tw := PerformRequest(router, method, \"/test\")\n\n\tassert.False(t, passed)\n\tassert.Equal(t, http.StatusNotFound, w.Code)\n}\n\n// TestSingleRouteOK tests that POST route is correctly invoked.\nfunc testRouteNotOK2(method string, t *testing.T) {\n\tpassed := false\n\trouter := New()\n\trouter.HandleMethodNotAllowed = true\n\tvar methodRoute string\n\tif method == http.MethodPost {\n\t\tmethodRoute = http.MethodGet\n\t} else {\n\t\tmethodRoute = http.MethodPost\n\t}\n\trouter.Handle(methodRoute, \"/test\", func(c *Context) {\n\t\tpassed = true\n\t})\n\n\tw := PerformRequest(router, method, \"/test\")\n\n\tassert.False(t, passed)\n\tassert.Equal(t, http.StatusMethodNotAllowed, w.Code)\n}\n\nfunc TestRouterMethod(t *testing.T) {\n\trouter := New()\n\trouter.PUT(\"/hey2\", func(c *Context) {\n\t\tc.String(http.StatusOK, \"sup2\")\n\t})\n\n\trouter.PUT(\"/hey\", func(c *Context) {\n\t\tc.String(http.StatusOK, \"called\")\n\t})\n\n\trouter.PUT(\"/hey3\", func(c *Context) {\n\t\tc.String(http.StatusOK, \"sup3\")\n\t})\n\n\tw := PerformRequest(router, http.MethodPut, \"/hey\")\n\n\tassert.Equal(t, http.StatusOK, w.Code)\n\tassert.Equal(t, \"called\", w.Body.String())\n}\n\nfunc TestRouterGroupRouteOK(t *testing.T) {\n\ttestRouteOK(http.MethodGet, t)\n\ttestRouteOK(http.MethodPost, t)\n\ttestRouteOK(http.MethodPut, t)\n\ttestRouteOK(http.MethodPatch, t)\n\ttestRouteOK(http.MethodHead, t)\n\ttestRouteOK(http.MethodOptions, t)\n\ttestRouteOK(http.MethodDelete, t)\n\ttestRouteOK(http.MethodConnect, t)\n\ttestRouteOK(http.MethodTrace, t)\n}\n\nfunc TestRouteNotOK(t *testing.T) {\n\ttestRouteNotOK(http.MethodGet, t)\n\ttestRouteNotOK(http.MethodPost, t)\n\ttestRouteNotOK(http.MethodPut, t)\n\ttestRouteNotOK(http.MethodPatch, t)\n\ttestRouteNotOK(http.MethodHead, t)\n\ttestRouteNotOK(http.MethodOptions, t)\n\ttestRouteNotOK(http.MethodDelete, t)\n\ttestRouteNotOK(http.MethodConnect, t)\n\ttestRouteNotOK(http.MethodTrace, t)\n}\n\nfunc TestRouteNotOK2(t *testing.T) {\n\ttestRouteNotOK2(http.MethodGet, t)\n\ttestRouteNotOK2(http.MethodPost, t)\n\ttestRouteNotOK2(http.MethodPut, t)\n\ttestRouteNotOK2(http.MethodPatch, t)\n\ttestRouteNotOK2(http.MethodHead, t)\n\ttestRouteNotOK2(http.MethodOptions, t)\n\ttestRouteNotOK2(http.MethodDelete, t)\n\ttestRouteNotOK2(http.MethodConnect, t)\n\ttestRouteNotOK2(http.MethodTrace, t)\n}\n\nfunc TestRouteRedirectTrailingSlash(t *testing.T) {\n\trouter := New()\n\trouter.RedirectFixedPath = false\n\trouter.RedirectTrailingSlash = true\n\trouter.GET(\"/path\", func(c *Context) {})\n\trouter.GET(\"/path2/\", func(c *Context) {})\n\trouter.POST(\"/path3\", func(c *Context) {})\n\trouter.PUT(\"/path4/\", func(c *Context) {})\n\n\tw := PerformRequest(router, http.MethodGet, \"/path/\")\n\tassert.Equal(t, \"/path\", w.Header().Get(\"Location\"))\n\tassert.Equal(t, http.StatusMovedPermanently, w.Code)\n\n\tw = PerformRequest(router, http.MethodGet, \"/path2\")\n\tassert.Equal(t, \"/path2/\", w.Header().Get(\"Location\"))\n\tassert.Equal(t, http.StatusMovedPermanently, w.Code)\n\n\tw = PerformRequest(router, http.MethodPost, \"/path3/\")\n\tassert.Equal(t, \"/path3\", w.Header().Get(\"Location\"))\n\tassert.Equal(t, http.StatusTemporaryRedirect, w.Code)\n\n\tw = PerformRequest(router, http.MethodPut, \"/path4\")\n\tassert.Equal(t, \"/path4/\", w.Header().Get(\"Location\"))\n\tassert.Equal(t, http.StatusTemporaryRedirect, w.Code)\n\n\tw = PerformRequest(router, http.MethodGet, \"/path\")\n\tassert.Equal(t, http.StatusOK, w.Code)\n\n\tw = PerformRequest(router, http.MethodGet, \"/path2/\")\n\tassert.Equal(t, http.StatusOK, w.Code)\n\n\tw = PerformRequest(router, http.MethodPost, \"/path3\")\n\tassert.Equal(t, http.StatusOK, w.Code)\n\n\tw = PerformRequest(router, http.MethodPut, \"/path4/\")\n\tassert.Equal(t, http.StatusOK, w.Code)\n\n\tw = PerformRequest(router, http.MethodGet, \"/path2\", header{Key: \"X-Forwarded-Prefix\", Value: \"/api\"})\n\tassert.Equal(t, \"/api/path2/\", w.Header().Get(\"Location\"))\n\tassert.Equal(t, http.StatusMovedPermanently, w.Code)\n\n\tw = PerformRequest(router, http.MethodGet, \"/path2/\", header{Key: \"X-Forwarded-Prefix\", Value: \"/api/\"})\n\tassert.Equal(t, http.StatusOK, w.Code)\n\n\tw = PerformRequest(router, http.MethodGet, \"/path/\", header{Key: \"X-Forwarded-Prefix\", Value: \"../../api#?\"})\n\tassert.Equal(t, \"/api/path\", w.Header().Get(\"Location\"))\n\tassert.Equal(t, http.StatusMovedPermanently, w.Code)\n\n\tw = PerformRequest(router, http.MethodGet, \"/path/\", header{Key: \"X-Forwarded-Prefix\", Value: \"../../api\"})\n\tassert.Equal(t, \"/api/path\", w.Header().Get(\"Location\"))\n\tassert.Equal(t, http.StatusMovedPermanently, w.Code)\n\n\tw = PerformRequest(router, http.MethodGet, \"/path2\", header{Key: \"X-Forwarded-Prefix\", Value: \"../../api\"})\n\tassert.Equal(t, \"/api/path2/\", w.Header().Get(\"Location\"))\n\tassert.Equal(t, http.StatusMovedPermanently, w.Code)\n\n\tw = PerformRequest(router, http.MethodGet, \"/path2\", header{Key: \"X-Forwarded-Prefix\", Value: \"/../../api\"})\n\tassert.Equal(t, \"/api/path2/\", w.Header().Get(\"Location\"))\n\tassert.Equal(t, http.StatusMovedPermanently, w.Code)\n\n\tw = PerformRequest(router, http.MethodGet, \"/path/\", header{Key: \"X-Forwarded-Prefix\", Value: \"api/../../\"})\n\tassert.Equal(t, \"//path\", w.Header().Get(\"Location\"))\n\tassert.Equal(t, http.StatusMovedPermanently, w.Code)\n\n\tw = PerformRequest(router, http.MethodGet, \"/path/\", header{Key: \"X-Forwarded-Prefix\", Value: \"api/../../../\"})\n\tassert.Equal(t, \"/path\", w.Header().Get(\"Location\"))\n\tassert.Equal(t, http.StatusMovedPermanently, w.Code)\n\n\tw = PerformRequest(router, http.MethodGet, \"/path2\", header{Key: \"X-Forwarded-Prefix\", Value: \"../../gin-gonic.com\"})\n\tassert.Equal(t, \"/gin-goniccom/path2/\", w.Header().Get(\"Location\"))\n\tassert.Equal(t, http.StatusMovedPermanently, w.Code)\n\n\tw = PerformRequest(router, http.MethodGet, \"/path2\", header{Key: \"X-Forwarded-Prefix\", Value: \"/../../gin-gonic.com\"})\n\tassert.Equal(t, \"/gin-goniccom/path2/\", w.Header().Get(\"Location\"))\n\tassert.Equal(t, http.StatusMovedPermanently, w.Code)\n\n\tw = PerformRequest(router, http.MethodGet, \"/path/\", header{Key: \"X-Forwarded-Prefix\", Value: \"https://gin-gonic.com/#\"})\n\tassert.Equal(t, \"https/gin-goniccom/https/gin-goniccom/path\", w.Header().Get(\"Location\"))\n\tassert.Equal(t, http.StatusMovedPermanently, w.Code)\n\n\tw = PerformRequest(router, http.MethodGet, \"/path/\", header{Key: \"X-Forwarded-Prefix\", Value: \"#api\"})\n\tassert.Equal(t, \"api/api/path\", w.Header().Get(\"Location\"))\n\tassert.Equal(t, http.StatusMovedPermanently, w.Code)\n\n\tw = PerformRequest(router, http.MethodGet, \"/path/\", header{Key: \"X-Forwarded-Prefix\", Value: \"/nor-mal/#?a=1\"})\n\tassert.Equal(t, \"/nor-mal/a1/path\", w.Header().Get(\"Location\"))\n\tassert.Equal(t, http.StatusMovedPermanently, w.Code)\n\n\tw = PerformRequest(router, http.MethodGet, \"/path/\", header{Key: \"X-Forwarded-Prefix\", Value: \"/nor-mal/%2e%2e/\"})\n\tassert.Equal(t, \"/nor-mal/2e2e/path\", w.Header().Get(\"Location\"))\n\tassert.Equal(t, http.StatusMovedPermanently, w.Code)\n\n\trouter.RedirectTrailingSlash = false\n\n\tw = PerformRequest(router, http.MethodGet, \"/path/\")\n\tassert.Equal(t, http.StatusNotFound, w.Code)\n\tw = PerformRequest(router, http.MethodGet, \"/path2\")\n\tassert.Equal(t, http.StatusNotFound, w.Code)\n\tw = PerformRequest(router, http.MethodPost, \"/path3/\")\n\tassert.Equal(t, http.StatusNotFound, w.Code)\n\tw = PerformRequest(router, http.MethodPut, \"/path4\")\n\tassert.Equal(t, http.StatusNotFound, w.Code)\n}\n\nfunc TestRouteRedirectFixedPath(t *testing.T) {\n\trouter := New()\n\trouter.RedirectFixedPath = true\n\trouter.RedirectTrailingSlash = false\n\n\trouter.GET(\"/path\", func(c *Context) {})\n\trouter.GET(\"/Path2\", func(c *Context) {})\n\trouter.POST(\"/PATH3\", func(c *Context) {})\n\trouter.POST(\"/Path4/\", func(c *Context) {})\n\n\tw := PerformRequest(router, http.MethodGet, \"/PATH\")\n\tassert.Equal(t, \"/path\", w.Header().Get(\"Location\"))\n\tassert.Equal(t, http.StatusMovedPermanently, w.Code)\n\n\tw = PerformRequest(router, http.MethodGet, \"/path2\")\n\tassert.Equal(t, \"/Path2\", w.Header().Get(\"Location\"))\n\tassert.Equal(t, http.StatusMovedPermanently, w.Code)\n\n\tw = PerformRequest(router, http.MethodPost, \"/path3\")\n\tassert.Equal(t, \"/PATH3\", w.Header().Get(\"Location\"))\n\tassert.Equal(t, http.StatusTemporaryRedirect, w.Code)\n\n\tw = PerformRequest(router, http.MethodPost, \"/path4\")\n\tassert.Equal(t, \"/Path4/\", w.Header().Get(\"Location\"))\n\tassert.Equal(t, http.StatusTemporaryRedirect, w.Code)\n}\n\n// TestContextParamsGet tests that a parameter can be parsed from the URL.\nfunc TestRouteParamsByName(t *testing.T) {\n\tname := \"\"\n\tlastName := \"\"\n\twild := \"\"\n\trouter := New()\n\trouter.GET(\"/test/:name/:last_name/*wild\", func(c *Context) {\n\t\tname = c.Params.ByName(\"name\")\n\t\tlastName = c.Params.ByName(\"last_name\")\n\t\tvar ok bool\n\t\twild, ok = c.Params.Get(\"wild\")\n\n\t\tassert.True(t, ok)\n\t\tassert.Equal(t, name, c.Param(\"name\"))\n\t\tassert.Equal(t, lastName, c.Param(\"last_name\"))\n\n\t\tassert.Empty(t, c.Param(\"wtf\"))\n\t\tassert.Empty(t, c.Params.ByName(\"wtf\"))\n\n\t\twtf, ok := c.Params.Get(\"wtf\")\n\t\tassert.Empty(t, wtf)\n\t\tassert.False(t, ok)\n\t})\n\n\tw := PerformRequest(router, http.MethodGet, \"/test/john/smith/is/super/great\")\n\n\tassert.Equal(t, http.StatusOK, w.Code)\n\tassert.Equal(t, \"john\", name)\n\tassert.Equal(t, \"smith\", lastName)\n\tassert.Equal(t, \"/is/super/great\", wild)\n}\n\n// TestContextParamsGet tests that a parameter can be parsed from the URL even with extra slashes.\nfunc TestRouteParamsByNameWithExtraSlash(t *testing.T) {\n\tname := \"\"\n\tlastName := \"\"\n\twild := \"\"\n\trouter := New()\n\trouter.RemoveExtraSlash = true\n\trouter.GET(\"/test/:name/:last_name/*wild\", func(c *Context) {\n\t\tname = c.Params.ByName(\"name\")\n\t\tlastName = c.Params.ByName(\"last_name\")\n\t\tvar ok bool\n\t\twild, ok = c.Params.Get(\"wild\")\n\n\t\tassert.True(t, ok)\n\t\tassert.Equal(t, name, c.Param(\"name\"))\n\t\tassert.Equal(t, lastName, c.Param(\"last_name\"))\n\n\t\tassert.Empty(t, c.Param(\"wtf\"))\n\t\tassert.Empty(t, c.Params.ByName(\"wtf\"))\n\n\t\twtf, ok := c.Params.Get(\"wtf\")\n\t\tassert.Empty(t, wtf)\n\t\tassert.False(t, ok)\n\t})\n\n\tw := PerformRequest(router, http.MethodGet, \"//test//john//smith//is//super//great\")\n\n\tassert.Equal(t, http.StatusOK, w.Code)\n\tassert.Equal(t, \"john\", name)\n\tassert.Equal(t, \"smith\", lastName)\n\tassert.Equal(t, \"/is/super/great\", wild)\n}\n\n// TestRouteParamsNotEmpty tests that context parameters will be set\n// even if a route with params/wildcards is registered after the context\n// initialisation (which happened in a previous requests).\nfunc TestRouteParamsNotEmpty(t *testing.T) {\n\tname := \"\"\n\tlastName := \"\"\n\twild := \"\"\n\trouter := New()\n\n\tw := PerformRequest(router, http.MethodGet, \"/test/john/smith/is/super/great\")\n\n\tassert.Equal(t, http.StatusNotFound, w.Code)\n\n\trouter.GET(\"/test/:name/:last_name/*wild\", func(c *Context) {\n\t\tname = c.Params.ByName(\"name\")\n\t\tlastName = c.Params.ByName(\"last_name\")\n\t\tvar ok bool\n\t\twild, ok = c.Params.Get(\"wild\")\n\n\t\tassert.True(t, ok)\n\t\tassert.Equal(t, name, c.Param(\"name\"))\n\t\tassert.Equal(t, lastName, c.Param(\"last_name\"))\n\n\t\tassert.Empty(t, c.Param(\"wtf\"))\n\t\tassert.Empty(t, c.Params.ByName(\"wtf\"))\n\n\t\twtf, ok := c.Params.Get(\"wtf\")\n\t\tassert.Empty(t, wtf)\n\t\tassert.False(t, ok)\n\t})\n\n\tw = PerformRequest(router, http.MethodGet, \"/test/john/smith/is/super/great\")\n\n\tassert.Equal(t, http.StatusOK, w.Code)\n\tassert.Equal(t, \"john\", name)\n\tassert.Equal(t, \"smith\", lastName)\n\tassert.Equal(t, \"/is/super/great\", wild)\n}\n\n// TestHandleStaticFile - ensure the static file handles properly\nfunc TestRouteStaticFile(t *testing.T) {\n\t// SETUP file\n\ttestRoot, _ := os.Getwd()\n\tf, err := os.CreateTemp(testRoot, \"\")\n\tif err != nil {\n\t\tt.Error(err)\n\t}\n\tdefer os.Remove(f.Name())\n\t_, err = f.WriteString(\"Gin Web Framework\")\n\trequire.NoError(t, err)\n\tf.Close()\n\n\tdir, filename := filepath.Split(f.Name())\n\n\t// SETUP gin\n\trouter := New()\n\trouter.Static(\"/using_static\", dir)\n\trouter.StaticFile(\"/result\", f.Name())\n\n\tw := PerformRequest(router, http.MethodGet, \"/using_static/\"+filename)\n\tw2 := PerformRequest(router, http.MethodGet, \"/result\")\n\n\tassert.Equal(t, w, w2)\n\tassert.Equal(t, http.StatusOK, w.Code)\n\tassert.Equal(t, \"Gin Web Framework\", w.Body.String())\n\tassert.Equal(t, \"text/plain; charset=utf-8\", w.Header().Get(\"Content-Type\"))\n\n\tw3 := PerformRequest(router, http.MethodHead, \"/using_static/\"+filename)\n\tw4 := PerformRequest(router, http.MethodHead, \"/result\")\n\n\tassert.Equal(t, w3, w4)\n\tassert.Equal(t, http.StatusOK, w3.Code)\n}\n\n// TestHandleStaticFile - ensure the static file handles properly\nfunc TestRouteStaticFileFS(t *testing.T) {\n\t// SETUP file\n\ttestRoot, _ := os.Getwd()\n\tf, err := os.CreateTemp(testRoot, \"\")\n\tif err != nil {\n\t\tt.Error(err)\n\t}\n\tdefer os.Remove(f.Name())\n\t_, err = f.WriteString(\"Gin Web Framework\")\n\trequire.NoError(t, err)\n\tf.Close()\n\n\tdir, filename := filepath.Split(f.Name())\n\t// SETUP gin\n\trouter := New()\n\trouter.Static(\"/using_static\", dir)\n\trouter.StaticFileFS(\"/result_fs\", filename, Dir(dir, false))\n\n\tw := PerformRequest(router, http.MethodGet, \"/using_static/\"+filename)\n\tw2 := PerformRequest(router, http.MethodGet, \"/result_fs\")\n\n\tassert.Equal(t, w, w2)\n\tassert.Equal(t, http.StatusOK, w.Code)\n\tassert.Equal(t, \"Gin Web Framework\", w.Body.String())\n\tassert.Equal(t, \"text/plain; charset=utf-8\", w.Header().Get(\"Content-Type\"))\n\n\tw3 := PerformRequest(router, http.MethodHead, \"/using_static/\"+filename)\n\tw4 := PerformRequest(router, http.MethodHead, \"/result_fs\")\n\n\tassert.Equal(t, w3, w4)\n\tassert.Equal(t, http.StatusOK, w3.Code)\n}\n\n// TestHandleStaticDir - ensure the root/sub dir handles properly\nfunc TestRouteStaticListingDir(t *testing.T) {\n\trouter := New()\n\trouter.StaticFS(\"/\", Dir(\"./\", true))\n\n\tw := PerformRequest(router, http.MethodGet, \"/\")\n\n\tassert.Equal(t, http.StatusOK, w.Code)\n\tassert.Contains(t, w.Body.String(), \"gin.go\")\n\tassert.Equal(t, \"text/html; charset=utf-8\", w.Header().Get(\"Content-Type\"))\n}\n\n// TestHandleHeadToDir - ensure the root/sub dir handles properly\nfunc TestRouteStaticNoListing(t *testing.T) {\n\trouter := New()\n\trouter.Static(\"/\", \"./\")\n\n\tw := PerformRequest(router, http.MethodGet, \"/\")\n\n\tassert.Equal(t, http.StatusNotFound, w.Code)\n\tassert.NotContains(t, w.Body.String(), \"gin.go\")\n}\n\nfunc TestRouterMiddlewareAndStatic(t *testing.T) {\n\trouter := New()\n\tstatic := router.Group(\"/\", func(c *Context) {\n\t\tc.Writer.Header().Add(\"Last-Modified\", \"Mon, 02 Jan 2006 15:04:05 MST\")\n\t\tc.Writer.Header().Add(\"Expires\", \"Mon, 02 Jan 2006 15:04:05 MST\")\n\t\tc.Writer.Header().Add(\"X-GIN\", \"Gin Framework\")\n\t})\n\tstatic.Static(\"/\", \"./\")\n\n\tw := PerformRequest(router, http.MethodGet, \"/gin.go\")\n\n\tassert.Equal(t, http.StatusOK, w.Code)\n\tassert.Contains(t, w.Body.String(), \"package gin\")\n\t// Content-Type='text/plain; charset=utf-8' when go version <= 1.16,\n\t// else, Content-Type='text/x-go; charset=utf-8'\n\tassert.NotEmpty(t, w.Header().Get(\"Content-Type\"))\n\tassert.NotEqual(t, \"Mon, 02 Jan 2006 15:04:05 MST\", w.Header().Get(\"Last-Modified\"))\n\tassert.Equal(t, \"Mon, 02 Jan 2006 15:04:05 MST\", w.Header().Get(\"Expires\"))\n\tassert.Equal(t, \"Gin Framework\", w.Header().Get(\"x-GIN\"))\n}\n\nfunc TestRouteNotAllowedEnabled(t *testing.T) {\n\trouter := New()\n\trouter.HandleMethodNotAllowed = true\n\trouter.POST(\"/path\", func(c *Context) {})\n\tw := PerformRequest(router, http.MethodGet, \"/path\")\n\tassert.Equal(t, http.StatusMethodNotAllowed, w.Code)\n\n\trouter.NoMethod(func(c *Context) {\n\t\tc.String(http.StatusTeapot, \"responseText\")\n\t})\n\tw = PerformRequest(router, http.MethodGet, \"/path\")\n\tassert.Equal(t, \"responseText\", w.Body.String())\n\tassert.Equal(t, http.StatusTeapot, w.Code)\n}\n\nfunc TestRouteNotAllowedEnabled2(t *testing.T) {\n\trouter := New()\n\trouter.HandleMethodNotAllowed = true\n\t// add one methodTree to trees\n\trouter.addRoute(http.MethodPost, \"/\", HandlersChain{func(_ *Context) {}})\n\trouter.GET(\"/path2\", func(c *Context) {})\n\tw := PerformRequest(router, http.MethodPost, \"/path2\")\n\tassert.Equal(t, http.StatusMethodNotAllowed, w.Code)\n}\n\nfunc TestRouteNotAllowedEnabled3(t *testing.T) {\n\trouter := New()\n\trouter.HandleMethodNotAllowed = true\n\trouter.GET(\"/path\", func(c *Context) {})\n\trouter.POST(\"/path\", func(c *Context) {})\n\tw := PerformRequest(router, http.MethodPut, \"/path\")\n\tassert.Equal(t, http.StatusMethodNotAllowed, w.Code)\n\tallowed := w.Header().Get(\"Allow\")\n\tassert.Contains(t, allowed, http.MethodGet)\n\tassert.Contains(t, allowed, http.MethodPost)\n}\n\nfunc TestRouteNotAllowedDisabled(t *testing.T) {\n\trouter := New()\n\trouter.HandleMethodNotAllowed = false\n\trouter.POST(\"/path\", func(c *Context) {})\n\tw := PerformRequest(router, http.MethodGet, \"/path\")\n\tassert.Equal(t, http.StatusNotFound, w.Code)\n\n\trouter.NoMethod(func(c *Context) {\n\t\tc.String(http.StatusTeapot, \"responseText\")\n\t})\n\tw = PerformRequest(router, http.MethodGet, \"/path\")\n\tassert.Equal(t, \"404 page not found\", w.Body.String())\n\tassert.Equal(t, http.StatusNotFound, w.Code)\n}\n\nfunc TestRouterNotFoundWithRemoveExtraSlash(t *testing.T) {\n\trouter := New()\n\trouter.RemoveExtraSlash = true\n\trouter.GET(\"/path\", func(c *Context) {})\n\trouter.GET(\"/\", func(c *Context) {})\n\n\ttestRoutes := []struct {\n\t\troute    string\n\t\tcode     int\n\t\tlocation string\n\t}{\n\t\t{\"/../path\", http.StatusOK, \"\"},    // CleanPath\n\t\t{\"/nope\", http.StatusNotFound, \"\"}, // NotFound\n\t}\n\tfor _, tr := range testRoutes {\n\t\tw := PerformRequest(router, http.MethodGet, tr.route)\n\t\tassert.Equal(t, tr.code, w.Code)\n\t\tif w.Code != http.StatusNotFound {\n\t\t\tassert.Equal(t, tr.location, w.Header().Get(\"Location\"))\n\t\t}\n\t}\n}\n\nfunc TestRouterNotFound(t *testing.T) {\n\trouter := New()\n\trouter.RedirectFixedPath = true\n\trouter.GET(\"/path\", func(c *Context) {})\n\trouter.GET(\"/dir/\", func(c *Context) {})\n\trouter.GET(\"/\", func(c *Context) {})\n\n\ttestRoutes := []struct {\n\t\troute    string\n\t\tcode     int\n\t\tlocation string\n\t}{\n\t\t{\"/path/\", http.StatusMovedPermanently, \"/path\"},   // TSR -/\n\t\t{\"/dir\", http.StatusMovedPermanently, \"/dir/\"},     // TSR +/\n\t\t{\"/PATH\", http.StatusMovedPermanently, \"/path\"},    // Fixed Case\n\t\t{\"/DIR/\", http.StatusMovedPermanently, \"/dir/\"},    // Fixed Case\n\t\t{\"/PATH/\", http.StatusMovedPermanently, \"/path\"},   // Fixed Case -/\n\t\t{\"/DIR\", http.StatusMovedPermanently, \"/dir/\"},     // Fixed Case +/\n\t\t{\"/../path\", http.StatusMovedPermanently, \"/path\"}, // Without CleanPath\n\t\t{\"/nope\", http.StatusNotFound, \"\"},                 // NotFound\n\t}\n\tfor _, tr := range testRoutes {\n\t\tw := PerformRequest(router, http.MethodGet, tr.route)\n\t\tassert.Equal(t, tr.code, w.Code)\n\t\tif w.Code != http.StatusNotFound {\n\t\t\tassert.Equal(t, tr.location, w.Header().Get(\"Location\"))\n\t\t}\n\t}\n\n\t// Test custom not found handler\n\tvar notFound bool\n\trouter.NoRoute(func(c *Context) {\n\t\tc.AbortWithStatus(http.StatusNotFound)\n\t\tnotFound = true\n\t})\n\tw := PerformRequest(router, http.MethodGet, \"/nope\")\n\tassert.Equal(t, http.StatusNotFound, w.Code)\n\tassert.True(t, notFound)\n\n\t// Test other method than GET (want 307 instead of 301)\n\trouter.PATCH(\"/path\", func(c *Context) {})\n\tw = PerformRequest(router, http.MethodPatch, \"/path/\")\n\tassert.Equal(t, http.StatusTemporaryRedirect, w.Code)\n\tassert.Equal(t, \"map[Location:[/path]]\", fmt.Sprint(w.Header()))\n\n\t// Test special case where no node for the prefix \"/\" exists\n\trouter = New()\n\trouter.GET(\"/a\", func(c *Context) {})\n\tw = PerformRequest(router, http.MethodGet, \"/\")\n\tassert.Equal(t, http.StatusNotFound, w.Code)\n\n\t// Reproduction test for the bug of issue #2843\n\trouter = New()\n\trouter.NoRoute(func(c *Context) {\n\t\tif c.Request.RequestURI == \"/login\" {\n\t\t\tc.String(http.StatusOK, \"login\")\n\t\t}\n\t})\n\trouter.GET(\"/logout\", func(c *Context) {\n\t\tc.String(http.StatusOK, \"logout\")\n\t})\n\tw = PerformRequest(router, http.MethodGet, \"/login\")\n\tassert.Equal(t, \"login\", w.Body.String())\n\tw = PerformRequest(router, http.MethodGet, \"/logout\")\n\tassert.Equal(t, \"logout\", w.Body.String())\n}\n\nfunc TestRouterStaticFSNotFound(t *testing.T) {\n\trouter := New()\n\trouter.StaticFS(\"/\", http.FileSystem(http.Dir(\"/thisreallydoesntexist/\")))\n\trouter.NoRoute(func(c *Context) {\n\t\tc.String(http.StatusNotFound, \"non existent\")\n\t})\n\n\tw := PerformRequest(router, http.MethodGet, \"/nonexistent\")\n\tassert.Equal(t, \"non existent\", w.Body.String())\n\n\tw = PerformRequest(router, http.MethodHead, \"/nonexistent\")\n\tassert.Equal(t, \"non existent\", w.Body.String())\n}\n\nfunc TestRouterStaticFSFileNotFound(t *testing.T) {\n\trouter := New()\n\n\trouter.StaticFS(\"/\", http.FileSystem(http.Dir(\".\")))\n\n\tassert.NotPanics(t, func() {\n\t\tPerformRequest(router, http.MethodGet, \"/nonexistent\")\n\t})\n}\n\n// Reproduction test for the bug of issue #1805\nfunc TestMiddlewareCalledOnceByRouterStaticFSNotFound(t *testing.T) {\n\trouter := New()\n\n\t// Middleware must be called just only once by per request.\n\tmiddlewareCalledNum := 0\n\trouter.Use(func(c *Context) {\n\t\tmiddlewareCalledNum++\n\t})\n\n\trouter.StaticFS(\"/\", http.FileSystem(http.Dir(\"/thisreallydoesntexist/\")))\n\n\t// First access\n\tPerformRequest(router, http.MethodGet, \"/nonexistent\")\n\tassert.Equal(t, 1, middlewareCalledNum)\n\n\t// Second access\n\tPerformRequest(router, http.MethodHead, \"/nonexistent\")\n\tassert.Equal(t, 2, middlewareCalledNum)\n}\n\nfunc TestRouteRawPath(t *testing.T) {\n\troute := New()\n\troute.UseRawPath = true\n\n\troute.POST(\"/project/:name/build/:num\", func(c *Context) {\n\t\tname := c.Params.ByName(\"name\")\n\t\tnum := c.Params.ByName(\"num\")\n\n\t\tassert.Equal(t, name, c.Param(\"name\"))\n\t\tassert.Equal(t, num, c.Param(\"num\"))\n\n\t\tassert.Equal(t, \"Some/Other/Project\", name)\n\t\tassert.Equal(t, \"222\", num)\n\t})\n\n\tw := PerformRequest(route, http.MethodPost, \"/project/Some%2FOther%2FProject/build/222\")\n\tassert.Equal(t, http.StatusOK, w.Code)\n}\n\nfunc TestRouteRawPathNoUnescape(t *testing.T) {\n\troute := New()\n\troute.UseRawPath = true\n\troute.UnescapePathValues = false\n\n\troute.POST(\"/project/:name/build/:num\", func(c *Context) {\n\t\tname := c.Params.ByName(\"name\")\n\t\tnum := c.Params.ByName(\"num\")\n\n\t\tassert.Equal(t, name, c.Param(\"name\"))\n\t\tassert.Equal(t, num, c.Param(\"num\"))\n\n\t\tassert.Equal(t, \"Some%2FOther%2FProject\", name)\n\t\tassert.Equal(t, \"333\", num)\n\t})\n\n\tw := PerformRequest(route, http.MethodPost, \"/project/Some%2FOther%2FProject/build/333\")\n\tassert.Equal(t, http.StatusOK, w.Code)\n}\n\nfunc TestRouteServeErrorWithWriteHeader(t *testing.T) {\n\troute := New()\n\troute.Use(func(c *Context) {\n\t\tc.Status(http.StatusMisdirectedRequest)\n\t\tc.Next()\n\t})\n\n\tw := PerformRequest(route, http.MethodGet, \"/NotFound\")\n\tassert.Equal(t, http.StatusMisdirectedRequest, w.Code)\n\tassert.Equal(t, 0, w.Body.Len())\n}\n\nfunc TestRouteContextHoldsFullPath(t *testing.T) {\n\trouter := New()\n\n\t// Test routes\n\troutes := []string{\n\t\t\"/simple\",\n\t\t\"/project/:name\",\n\t\t\"/\",\n\t\t\"/news/home\",\n\t\t\"/news\",\n\t\t\"/simple-two/one\",\n\t\t\"/simple-two/one-two\",\n\t\t\"/project/:name/build/*params\",\n\t\t\"/project/:name/bui\",\n\t\t\"/user/:id/status\",\n\t\t\"/user/:id\",\n\t\t\"/user/:id/profile\",\n\t}\n\n\tfor _, route := range routes {\n\t\tactualRoute := route\n\t\trouter.GET(route, func(c *Context) {\n\t\t\t// For each defined route context should contain its full path\n\t\t\tassert.Equal(t, actualRoute, c.FullPath())\n\t\t\tc.AbortWithStatus(http.StatusOK)\n\t\t})\n\t}\n\n\tfor _, route := range routes {\n\t\tw := PerformRequest(router, http.MethodGet, route)\n\t\tassert.Equal(t, http.StatusOK, w.Code)\n\t}\n\n\t// Test not found\n\trouter.Use(func(c *Context) {\n\t\t// For not found routes full path is empty\n\t\tassert.Empty(t, c.FullPath())\n\t})\n\n\tw := PerformRequest(router, http.MethodGet, \"/not-found\")\n\tassert.Equal(t, http.StatusNotFound, w.Code)\n}\n\nfunc TestEngineHandleMethodNotAllowedCornerCase(t *testing.T) {\n\tr := New()\n\tr.HandleMethodNotAllowed = true\n\n\tbase := r.Group(\"base\")\n\tbase.GET(\"/metrics\", handlerTest1)\n\n\tv1 := base.Group(\"v1\")\n\n\tv1.GET(\"/:id/devices\", handlerTest1)\n\tv1.GET(\"/user/:id/groups\", handlerTest1)\n\n\tv1.GET(\"/orgs/:id\", handlerTest1)\n\tv1.DELETE(\"/orgs/:id\", handlerTest1)\n\n\tw := PerformRequest(r, http.MethodGet, \"/base/v1/user/groups\")\n\tassert.Equal(t, http.StatusNotFound, w.Code)\n}\n"
  },
  {
    "path": "test_helpers.go",
    "content": "// Copyright 2017 Manu Martinez-Almeida. All rights reserved.\n// Use of this source code is governed by a MIT style\n// license that can be found in the LICENSE file.\n\npackage gin\n\nimport (\n\t\"fmt\"\n\t\"net/http\"\n\t\"time\"\n)\n\n// CreateTestContext returns a fresh Engine and a Context associated with it.\n// This is useful for tests that need to set up a new Gin engine instance\n// along with a context, for example, to test middleware that doesn't depend on\n// specific routes. The ResponseWriter `w` is used to initialize the context's writer.\nfunc CreateTestContext(w http.ResponseWriter) (c *Context, r *Engine) {\n\tr = New()\n\tc = r.allocateContext(0)\n\tc.reset()\n\tc.writermem.reset(w)\n\treturn\n}\n\n// CreateTestContextOnly returns a fresh Context associated with the provided Engine `r`.\n// This is useful for tests that operate on an existing, possibly pre-configured,\n// Gin engine instance and need a new context for it.\n// The ResponseWriter `w` is used to initialize the context's writer.\n// The context is allocated with the `maxParams` setting from the provided engine.\nfunc CreateTestContextOnly(w http.ResponseWriter, r *Engine) (c *Context) {\n\tc = r.allocateContext(r.maxParams)\n\tc.reset()\n\tc.writermem.reset(w)\n\treturn\n}\n\n// waitForServerReady waits for a server to be ready by making HTTP requests\n// with exponential backoff. This is more reliable than time.Sleep() for testing.\nfunc waitForServerReady(url string, maxAttempts int) error {\n\tclient := &http.Client{\n\t\tTimeout: 100 * time.Millisecond,\n\t}\n\n\tfor i := 0; i < maxAttempts; i++ {\n\t\tresp, err := client.Get(url)\n\t\tif err == nil {\n\t\t\tresp.Body.Close()\n\t\t\treturn nil\n\t\t}\n\n\t\t// Exponential backoff: 10ms, 20ms, 40ms, 80ms, 160ms...\n\t\tbackoff := min(time.Duration(10*(1<<uint(i)))*time.Millisecond, 500*time.Millisecond)\n\t\ttime.Sleep(backoff)\n\t}\n\n\treturn fmt.Errorf(\"server at %s did not become ready after %d attempts\", url, maxAttempts)\n}\n"
  },
  {
    "path": "testdata/certificate/cert.pem",
    "content": "-----BEGIN CERTIFICATE-----\nMIIC9DCCAdygAwIBAgIQUNSK+OxWHYYFxHVJV0IlpDANBgkqhkiG9w0BAQsFADAS\nMRAwDgYDVQQKEwdBY21lIENvMB4XDTE3MTExNjEyMDA0N1oXDTE4MTExNjEyMDA0\nN1owEjEQMA4GA1UEChMHQWNtZSBDbzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCC\nAQoCggEBAKmyj/YZpD59Bpy4w3qf6VzMw9uUBsWp+IP4kl7z5cmGHYUHn/YopTLH\nvR23GAB12p6Km5QWzCBuJF4j61PJXHfg3/rjShZ77JcQ3kzxuy1iKDI+DNKN7Klz\nrdjJ49QD0lczZHeBvvCL7JsJFKFjGy62rnStuW8LaIEdtjXT+GUZTxJh6G7yPYfD\nMS1IsdMQGOdbGwNa+qogMuQPh0TzHw+C73myKrjY6pREijknMC/rnIMz9dLPt6Kl\nxXy4br443dpY6dYGIhDuKhROT+vZ05HKasuuQUFhY7v/KoUpEZMB9rfUSzjQ5fak\neDUAMniXRcd+DmwvboG2TI6ixmuPK+ECAwEAAaNGMEQwDgYDVR0PAQH/BAQDAgWg\nMBMGA1UdJQQMMAoGCCsGAQUFBwMBMAwGA1UdEwEB/wQCMAAwDwYDVR0RBAgwBocE\nfwAAATANBgkqhkiG9w0BAQsFAAOCAQEAMXOLvj7BFsxdbcfRPBd0OFrH/8lI7vPV\nLRcJ6r5iv0cnNvZXXbIOQLbg4clJAWjoE08nRm1KvNXhKdns0ELEV86YN2S6jThq\nrIGrBqKtaJLB3M9BtDSiQ6SGPLYrWvmhj3Avi8PbSGy51bpGbqopd16j6LYU7Cp2\nTefMRlOAFtHojpCVon1CMpqcNxS0WNlQ3lUBSrw3HB0o12x++roja2ibF54tSHXB\nKUuadoEzN+mMBwenEBychmAGzdiG4GQHRmhigh85+mtW6UMGiqyCZHs0EgE9FCLL\nsRrsTI/VOzLz6lluygXkOsXrP+PP0SvmE3eylWjj9e2nj/u/Cy2YKg==\n-----END CERTIFICATE-----\n"
  },
  {
    "path": "testdata/certificate/key.pem",
    "content": "-----BEGIN RSA PRIVATE KEY-----\nMIIEogIBAAKCAQEAqbKP9hmkPn0GnLjDep/pXMzD25QGxan4g/iSXvPlyYYdhQef\n9iilMse9HbcYAHXanoqblBbMIG4kXiPrU8lcd+Df+uNKFnvslxDeTPG7LWIoMj4M\n0o3sqXOt2Mnj1APSVzNkd4G+8IvsmwkUoWMbLraudK25bwtogR22NdP4ZRlPEmHo\nbvI9h8MxLUix0xAY51sbA1r6qiAy5A+HRPMfD4LvebIquNjqlESKOScwL+ucgzP1\n0s+3oqXFfLhuvjjd2ljp1gYiEO4qFE5P69nTkcpqy65BQWFju/8qhSkRkwH2t9RL\nONDl9qR4NQAyeJdFx34ObC9ugbZMjqLGa48r4QIDAQABAoIBAD5mhd+GMEo2KU9J\n9b/Ku8I/HapJtW/L/7Fvn0tBPncrVQGM+zpGWfDhV95sbGwG6lwwNeNvuqIWPlNL\nvAY0XkdKrrIQEDdSXH50WnpKzXxzwrou7QIj5Cmvevbjzl4xBZDBOilj0XWczmV4\nIljyG5XC4UXQeAaoWEZaSZ1jk8yAt2Zq1Hgg7HqhHsK/arWXBgax+4K5nV/s9gZx\nyjKU9mXTIs7k/aNnZqwQKqcZF+l3mvbZttOaFwsP14H0I8OFWhnM9hie54Dejqxi\nf4/llNxDqUs6lqJfP3qNxtORLcFe75M+Yl8v7g2hkjtLdZBakPzSTEx3TAK/UHgi\naM8DdxECgYEA3fmg/PI4EgUEj0C3SCmQXR/CnQLMUQgb54s0asp4akvp+M7YCcr1\npQd3HFUpBwhBcJg5LeSe87vLupY7pHCKk56cl9WY6hse0b9sP/7DWJuGiO62m0E0\nvNjQ2jpG99oR2ROIHHeWsGCpGLmrRT/kY+vR3M+AOLZniXlOCw8k0aUCgYEAw7WL\nXFWLxgZYQYilywqrQmfv1MBfaUCvykO6oWB+f6mmnihSFjecI+nDw/b3yXVYGEgy\n0ebkuw0jP8suC8wBqX9WuXj+9nZNomJRssJyOMiEhDEqUiTztFPSp9pdruoakLTh\nWk1p9NralOqGPUmxpXlFKVmYRTUbluikVxDypI0CgYBn6sqEQH0hann0+o4TWWn9\nPrYkPUAbm1k8771tVTZERR/W3Dbldr/DL5iCihe39BR2urziEEqdvkglJNntJMar\nTzDuIBADYQjvltb9qq4XGFBGYMLaMg+XbUVxNKEuvUdnwa4R7aZ9EfN34MwekkfA\nw5Cu9/GGG1ajVEfGA6PwBQKBgA3o71jGs8KFXOx7e90sivOTU5Z5fc6LTHNB0Rf7\nNcJ5GmCPWRY/KZfb25AoE4B8GKDRMNt+X69zxZeZJ1KrU0rqxA02rlhyHB54gnoE\nG/4xMkn6/JkOC0w70PMhMBtohC7YzFOQwQEoNPT0nkno3Pl33xSLS6lPlwBo1JVj\nnPtZAoGACXNLXYkR5vexE+w6FGl59r4RQhu1XU8Mr5DIHeB7kXPN3RKbS201M+Tb\nSB5jbu0iDV477XkzSNmhaksFf2wM9MT6CaE+8n3UU5tMa+MmBGgwYTp/i9HkqVh5\njjpJifn1VWBINd4cpNzwCg9LXoo0tbtUPWwGzqVeyo/YE5GIHGo=\n-----END RSA PRIVATE KEY-----\n"
  },
  {
    "path": "testdata/protoexample/test.pb.go",
    "content": "// Code generated by protoc-gen-go. DO NOT EDIT.\n// versions:\n// \tprotoc-gen-go v1.27.0\n// \tprotoc        v3.15.8\n// source: test.proto\n\npackage protoexample\n\nimport (\n\tprotoreflect \"google.golang.org/protobuf/reflect/protoreflect\"\n\tprotoimpl \"google.golang.org/protobuf/runtime/protoimpl\"\n\treflect \"reflect\"\n\tsync \"sync\"\n)\n\nconst (\n\t// Verify that this generated code is sufficiently up-to-date.\n\t_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)\n\t// Verify that runtime/protoimpl is sufficiently up-to-date.\n\t_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)\n)\n\ntype FOO int32\n\nconst (\n\tFOO_X FOO = 17\n)\n\n// Enum value maps for FOO.\nvar (\n\tFOO_name = map[int32]string{\n\t\t17: \"X\",\n\t}\n\tFOO_value = map[string]int32{\n\t\t\"X\": 17,\n\t}\n)\n\nfunc (x FOO) Enum() *FOO {\n\tp := new(FOO)\n\t*p = x\n\treturn p\n}\n\nfunc (x FOO) String() string {\n\treturn protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))\n}\n\nfunc (FOO) Descriptor() protoreflect.EnumDescriptor {\n\treturn file_test_proto_enumTypes[0].Descriptor()\n}\n\nfunc (FOO) Type() protoreflect.EnumType {\n\treturn &file_test_proto_enumTypes[0]\n}\n\nfunc (x FOO) Number() protoreflect.EnumNumber {\n\treturn protoreflect.EnumNumber(x)\n}\n\n// Deprecated: Do not use.\nfunc (x *FOO) UnmarshalJSON(b []byte) error {\n\tnum, err := protoimpl.X.UnmarshalJSONEnum(x.Descriptor(), b)\n\tif err != nil {\n\t\treturn err\n\t}\n\t*x = FOO(num)\n\treturn nil\n}\n\n// Deprecated: Use FOO.Descriptor instead.\nfunc (FOO) EnumDescriptor() ([]byte, []int) {\n\treturn file_test_proto_rawDescGZIP(), []int{0}\n}\n\ntype Test struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\tLabel         *string             `protobuf:\"bytes,1,req,name=label\" json:\"label,omitempty\"`\n\tType          *int32              `protobuf:\"varint,2,opt,name=type,def=77\" json:\"type,omitempty\"`\n\tReps          []int64             `protobuf:\"varint,3,rep,name=reps\" json:\"reps,omitempty\"`\n\tOptionalgroup *Test_OptionalGroup `protobuf:\"group,4,opt,name=OptionalGroup,json=optionalgroup\" json:\"optionalgroup,omitempty\"`\n}\n\n// Default values for Test fields.\nconst (\n\tDefault_Test_Type = int32(77)\n)\n\nfunc (x *Test) Reset() {\n\t*x = Test{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_test_proto_msgTypes[0]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *Test) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*Test) ProtoMessage() {}\n\nfunc (x *Test) ProtoReflect() protoreflect.Message {\n\tmi := &file_test_proto_msgTypes[0]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use Test.ProtoReflect.Descriptor instead.\nfunc (*Test) Descriptor() ([]byte, []int) {\n\treturn file_test_proto_rawDescGZIP(), []int{0}\n}\n\nfunc (x *Test) GetLabel() string {\n\tif x != nil && x.Label != nil {\n\t\treturn *x.Label\n\t}\n\treturn \"\"\n}\n\nfunc (x *Test) GetType() int32 {\n\tif x != nil && x.Type != nil {\n\t\treturn *x.Type\n\t}\n\treturn Default_Test_Type\n}\n\nfunc (x *Test) GetReps() []int64 {\n\tif x != nil {\n\t\treturn x.Reps\n\t}\n\treturn nil\n}\n\nfunc (x *Test) GetOptionalgroup() *Test_OptionalGroup {\n\tif x != nil {\n\t\treturn x.Optionalgroup\n\t}\n\treturn nil\n}\n\ntype Test_OptionalGroup struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\tRequiredField *string `protobuf:\"bytes,5,req,name=RequiredField\" json:\"RequiredField,omitempty\"`\n}\n\nfunc (x *Test_OptionalGroup) Reset() {\n\t*x = Test_OptionalGroup{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_test_proto_msgTypes[1]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *Test_OptionalGroup) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*Test_OptionalGroup) ProtoMessage() {}\n\nfunc (x *Test_OptionalGroup) ProtoReflect() protoreflect.Message {\n\tmi := &file_test_proto_msgTypes[1]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use Test_OptionalGroup.ProtoReflect.Descriptor instead.\nfunc (*Test_OptionalGroup) Descriptor() ([]byte, []int) {\n\treturn file_test_proto_rawDescGZIP(), []int{0, 0}\n}\n\nfunc (x *Test_OptionalGroup) GetRequiredField() string {\n\tif x != nil && x.RequiredField != nil {\n\t\treturn *x.RequiredField\n\t}\n\treturn \"\"\n}\n\nvar File_test_proto protoreflect.FileDescriptor\n\nvar file_test_proto_rawDesc = []byte{\n\t0x0a, 0x0a, 0x74, 0x65, 0x73, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x0c, 0x70, 0x72,\n\t0x6f, 0x74, 0x6f, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x22, 0xc7, 0x01, 0x0a, 0x04, 0x54,\n\t0x65, 0x73, 0x74, 0x12, 0x14, 0x0a, 0x05, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x18, 0x01, 0x20, 0x02,\n\t0x28, 0x09, 0x52, 0x05, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x12, 0x16, 0x0a, 0x04, 0x74, 0x79, 0x70,\n\t0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x3a, 0x02, 0x37, 0x37, 0x52, 0x04, 0x74, 0x79, 0x70,\n\t0x65, 0x12, 0x12, 0x0a, 0x04, 0x72, 0x65, 0x70, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x03, 0x52,\n\t0x04, 0x72, 0x65, 0x70, 0x73, 0x12, 0x46, 0x0a, 0x0d, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x61,\n\t0x6c, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0a, 0x32, 0x20, 0x2e, 0x70,\n\t0x72, 0x6f, 0x74, 0x6f, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x2e, 0x54, 0x65, 0x73, 0x74,\n\t0x2e, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x61, 0x6c, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x52, 0x0d,\n\t0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x61, 0x6c, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x1a, 0x35, 0x0a,\n\t0x0d, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x61, 0x6c, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x12, 0x24,\n\t0x0a, 0x0d, 0x52, 0x65, 0x71, 0x75, 0x69, 0x72, 0x65, 0x64, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x18,\n\t0x05, 0x20, 0x02, 0x28, 0x09, 0x52, 0x0d, 0x52, 0x65, 0x71, 0x75, 0x69, 0x72, 0x65, 0x64, 0x46,\n\t0x69, 0x65, 0x6c, 0x64, 0x2a, 0x0c, 0x0a, 0x03, 0x46, 0x4f, 0x4f, 0x12, 0x05, 0x0a, 0x01, 0x58,\n\t0x10, 0x11,\n}\n\nvar (\n\tfile_test_proto_rawDescOnce sync.Once\n\tfile_test_proto_rawDescData = file_test_proto_rawDesc\n)\n\nfunc file_test_proto_rawDescGZIP() []byte {\n\tfile_test_proto_rawDescOnce.Do(func() {\n\t\tfile_test_proto_rawDescData = protoimpl.X.CompressGZIP(file_test_proto_rawDescData)\n\t})\n\treturn file_test_proto_rawDescData\n}\n\nvar file_test_proto_enumTypes = make([]protoimpl.EnumInfo, 1)\nvar file_test_proto_msgTypes = make([]protoimpl.MessageInfo, 2)\nvar file_test_proto_goTypes = []any{\n\t(FOO)(0),                   // 0: protoexample.FOO\n\t(*Test)(nil),               // 1: protoexample.Test\n\t(*Test_OptionalGroup)(nil), // 2: protoexample.Test.OptionalGroup\n}\nvar file_test_proto_depIdxs = []int32{\n\t2, // 0: protoexample.Test.optionalgroup:type_name -> protoexample.Test.OptionalGroup\n\t1, // [1:1] is the sub-list for method output_type\n\t1, // [1:1] is the sub-list for method input_type\n\t1, // [1:1] is the sub-list for extension type_name\n\t1, // [1:1] is the sub-list for extension extendee\n\t0, // [0:1] is the sub-list for field type_name\n}\n\nfunc init() { file_test_proto_init() }\nfunc file_test_proto_init() {\n\tif File_test_proto != nil {\n\t\treturn\n\t}\n\tif !protoimpl.UnsafeEnabled {\n\t\tfile_test_proto_msgTypes[0].Exporter = func(v any, i int) any {\n\t\t\tswitch v := v.(*Test); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_test_proto_msgTypes[1].Exporter = func(v any, i int) any {\n\t\t\tswitch v := v.(*Test_OptionalGroup); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t}\n\ttype x struct{}\n\tout := protoimpl.TypeBuilder{\n\t\tFile: protoimpl.DescBuilder{\n\t\t\tGoPackagePath: reflect.TypeOf(x{}).PkgPath(),\n\t\t\tRawDescriptor: file_test_proto_rawDesc,\n\t\t\tNumEnums:      1,\n\t\t\tNumMessages:   2,\n\t\t\tNumExtensions: 0,\n\t\t\tNumServices:   0,\n\t\t},\n\t\tGoTypes:           file_test_proto_goTypes,\n\t\tDependencyIndexes: file_test_proto_depIdxs,\n\t\tEnumInfos:         file_test_proto_enumTypes,\n\t\tMessageInfos:      file_test_proto_msgTypes,\n\t}.Build()\n\tFile_test_proto = out.File\n\tfile_test_proto_rawDesc = nil\n\tfile_test_proto_goTypes = nil\n\tfile_test_proto_depIdxs = nil\n}\n"
  },
  {
    "path": "testdata/protoexample/test.proto",
    "content": "package protoexample;\n\nenum FOO {X=17;};\n\nmessage Test {\n   required string label = 1;\n   optional int32 type = 2[default=77];\n   repeated int64 reps = 3;\n   optional group OptionalGroup = 4{\n     required string RequiredField = 5;\n   }\n}\n"
  },
  {
    "path": "testdata/template/hello.tmpl",
    "content": "<h1>Hello {[{.name}]}</h1>"
  },
  {
    "path": "testdata/template/raw.tmpl",
    "content": "Date: {[{.now | formatAsDate}]}"
  },
  {
    "path": "testdata/test_file.txt",
    "content": "This is a test file for Context.File() method testing.\nIt contains some sample content to verify file serving functionality."
  },
  {
    "path": "tree.go",
    "content": "// Copyright 2013 Julien Schmidt. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be found\n// at https://github.com/julienschmidt/httprouter/blob/master/LICENSE\n\npackage gin\n\nimport (\n\t\"net/url\"\n\t\"strings\"\n\t\"unicode\"\n\t\"unicode/utf8\"\n\n\t\"github.com/gin-gonic/gin/internal/bytesconv\"\n)\n\n// Param is a single URL parameter, consisting of a key and a value.\ntype Param struct {\n\tKey   string\n\tValue string\n}\n\n// Params is a Param-slice, as returned by the router.\n// The slice is ordered, the first URL parameter is also the first slice value.\n// It is therefore safe to read values by the index.\ntype Params []Param\n\n// Get returns the value of the first Param which key matches the given name and a boolean true.\n// If no matching Param is found, an empty string is returned and a boolean false .\nfunc (ps Params) Get(name string) (string, bool) {\n\tfor _, entry := range ps {\n\t\tif entry.Key == name {\n\t\t\treturn entry.Value, true\n\t\t}\n\t}\n\treturn \"\", false\n}\n\n// ByName returns the value of the first Param which key matches the given name.\n// If no matching Param is found, an empty string is returned.\nfunc (ps Params) ByName(name string) (va string) {\n\tva, _ = ps.Get(name)\n\treturn\n}\n\ntype methodTree struct {\n\tmethod string\n\troot   *node\n}\n\ntype methodTrees []methodTree\n\nfunc (trees methodTrees) get(method string) *node {\n\tfor _, tree := range trees {\n\t\tif tree.method == method {\n\t\t\treturn tree.root\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc longestCommonPrefix(a, b string) int {\n\ti := 0\n\tmax_ := min(len(a), len(b))\n\tfor i < max_ && a[i] == b[i] {\n\t\ti++\n\t}\n\treturn i\n}\n\n// addChild will add a child node, keeping wildcardChild at the end\nfunc (n *node) addChild(child *node) {\n\tif n.wildChild && len(n.children) > 0 {\n\t\twildcardChild := n.children[len(n.children)-1]\n\t\tn.children = append(n.children[:len(n.children)-1], child, wildcardChild)\n\t} else {\n\t\tn.children = append(n.children, child)\n\t}\n}\n\nfunc countParams(path string) uint16 {\n\tcolons := strings.Count(path, \":\")\n\tstars := strings.Count(path, \"*\")\n\treturn safeUint16(colons + stars)\n}\n\nfunc countSections(path string) uint16 {\n\treturn safeUint16(strings.Count(path, \"/\"))\n}\n\ntype nodeType uint8\n\nconst (\n\tstatic nodeType = iota\n\troot\n\tparam\n\tcatchAll\n)\n\ntype node struct {\n\tpath      string\n\tindices   string\n\twildChild bool\n\tnType     nodeType\n\tpriority  uint32\n\tchildren  []*node // child nodes, at most 1 :param style node at the end of the array\n\thandlers  HandlersChain\n\tfullPath  string\n}\n\n// Increments priority of the given child and reorders if necessary\nfunc (n *node) incrementChildPrio(pos int) int {\n\tcs := n.children\n\tcs[pos].priority++\n\tprio := cs[pos].priority\n\n\t// Adjust position (move to front)\n\tnewPos := pos\n\tfor ; newPos > 0 && cs[newPos-1].priority < prio; newPos-- {\n\t\t// Swap node positions\n\t\tcs[newPos-1], cs[newPos] = cs[newPos], cs[newPos-1]\n\t}\n\n\t// Build new index char string\n\tif newPos != pos {\n\t\tn.indices = n.indices[:newPos] + // Unchanged prefix, might be empty\n\t\t\tn.indices[pos:pos+1] + // The index char we move\n\t\t\tn.indices[newPos:pos] + n.indices[pos+1:] // Rest without char at 'pos'\n\t}\n\n\treturn newPos\n}\n\n// addRoute adds a node with the given handle to the path.\n// Not concurrency-safe!\nfunc (n *node) addRoute(path string, handlers HandlersChain) {\n\tfullPath := path\n\tn.priority++\n\n\t// Empty tree\n\tif len(n.path) == 0 && len(n.children) == 0 {\n\t\tn.insertChild(path, fullPath, handlers)\n\t\tn.nType = root\n\t\treturn\n\t}\n\n\tparentFullPathIndex := 0\n\nwalk:\n\tfor {\n\t\t// Find the longest common prefix.\n\t\t// This also implies that the common prefix contains no ':' or '*'\n\t\t// since the existing key can't contain those chars.\n\t\ti := longestCommonPrefix(path, n.path)\n\n\t\t// Split edge\n\t\tif i < len(n.path) {\n\t\t\tchild := node{\n\t\t\t\tpath:      n.path[i:],\n\t\t\t\twildChild: n.wildChild,\n\t\t\t\tnType:     static,\n\t\t\t\tindices:   n.indices,\n\t\t\t\tchildren:  n.children,\n\t\t\t\thandlers:  n.handlers,\n\t\t\t\tpriority:  n.priority - 1,\n\t\t\t\tfullPath:  n.fullPath,\n\t\t\t}\n\n\t\t\tn.children = []*node{&child}\n\t\t\t// []byte for proper unicode char conversion, see #65\n\t\t\tn.indices = bytesconv.BytesToString([]byte{n.path[i]})\n\t\t\tn.path = path[:i]\n\t\t\tn.handlers = nil\n\t\t\tn.wildChild = false\n\t\t\tn.fullPath = fullPath[:parentFullPathIndex+i]\n\t\t}\n\n\t\t// Make new node a child of this node\n\t\tif i < len(path) {\n\t\t\tpath = path[i:]\n\t\t\tc := path[0]\n\n\t\t\t// '/' after param\n\t\t\tif n.nType == param && c == '/' && len(n.children) == 1 {\n\t\t\t\tparentFullPathIndex += len(n.path)\n\t\t\t\tn = n.children[0]\n\t\t\t\tn.priority++\n\t\t\t\tcontinue walk\n\t\t\t}\n\n\t\t\t// Check if a child with the next path byte exists\n\t\t\tfor i, max_ := 0, len(n.indices); i < max_; i++ {\n\t\t\t\tif c == n.indices[i] {\n\t\t\t\t\tparentFullPathIndex += len(n.path)\n\t\t\t\t\ti = n.incrementChildPrio(i)\n\t\t\t\t\tn = n.children[i]\n\t\t\t\t\tcontinue walk\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Otherwise insert it\n\t\t\tif c != ':' && c != '*' && n.nType != catchAll {\n\t\t\t\t// []byte for proper unicode char conversion, see #65\n\t\t\t\tn.indices += bytesconv.BytesToString([]byte{c})\n\t\t\t\tchild := &node{\n\t\t\t\t\tfullPath: fullPath,\n\t\t\t\t}\n\t\t\t\tn.addChild(child)\n\t\t\t\tn.incrementChildPrio(len(n.indices) - 1)\n\t\t\t\tn = child\n\t\t\t} else if n.wildChild {\n\t\t\t\t// inserting a wildcard node, need to check if it conflicts with the existing wildcard\n\t\t\t\tn = n.children[len(n.children)-1]\n\t\t\t\tn.priority++\n\n\t\t\t\t// Check if the wildcard matches\n\t\t\t\tif len(path) >= len(n.path) && n.path == path[:len(n.path)] &&\n\t\t\t\t\t// Adding a child to a catchAll is not possible\n\t\t\t\t\tn.nType != catchAll &&\n\t\t\t\t\t// Check for longer wildcard, e.g. :name and :names\n\t\t\t\t\t(len(n.path) >= len(path) || path[len(n.path)] == '/') {\n\t\t\t\t\tcontinue walk\n\t\t\t\t}\n\n\t\t\t\t// Wildcard conflict\n\t\t\t\tpathSeg := path\n\t\t\t\tif n.nType != catchAll {\n\t\t\t\t\tpathSeg, _, _ = strings.Cut(pathSeg, \"/\")\n\t\t\t\t}\n\t\t\t\tprefix := fullPath[:strings.Index(fullPath, pathSeg)] + n.path\n\t\t\t\tpanic(\"'\" + pathSeg +\n\t\t\t\t\t\"' in new path '\" + fullPath +\n\t\t\t\t\t\"' conflicts with existing wildcard '\" + n.path +\n\t\t\t\t\t\"' in existing prefix '\" + prefix +\n\t\t\t\t\t\"'\")\n\t\t\t}\n\n\t\t\tn.insertChild(path, fullPath, handlers)\n\t\t\treturn\n\t\t}\n\n\t\t// Otherwise add handle to current node\n\t\tif n.handlers != nil {\n\t\t\tpanic(\"handlers are already registered for path '\" + fullPath + \"'\")\n\t\t}\n\t\tn.handlers = handlers\n\t\tn.fullPath = fullPath\n\t\treturn\n\t}\n}\n\n// Search for a wildcard segment and check the name for invalid characters.\n// Returns -1 as index, if no wildcard was found.\nfunc findWildcard(path string) (wildcard string, i int, valid bool) {\n\t// Find start\n\tescapeColon := false\n\tfor start, c := range []byte(path) {\n\t\tif escapeColon {\n\t\t\tescapeColon = false\n\t\t\tif c == ':' {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tpanic(\"invalid escape string in path '\" + path + \"'\")\n\t\t}\n\t\tif c == '\\\\' {\n\t\t\tescapeColon = true\n\t\t\tcontinue\n\t\t}\n\t\t// A wildcard starts with ':' (param) or '*' (catch-all)\n\t\tif c != ':' && c != '*' {\n\t\t\tcontinue\n\t\t}\n\n\t\t// Find end and check for invalid characters\n\t\tvalid = true\n\t\tfor end, c := range []byte(path[start+1:]) {\n\t\t\tswitch c {\n\t\t\tcase '/':\n\t\t\t\treturn path[start : start+1+end], start, valid\n\t\t\tcase ':', '*':\n\t\t\t\tvalid = false\n\t\t\t}\n\t\t}\n\t\treturn path[start:], start, valid\n\t}\n\treturn \"\", -1, false\n}\n\nfunc (n *node) insertChild(path string, fullPath string, handlers HandlersChain) {\n\tfor {\n\t\t// Find prefix until first wildcard\n\t\twildcard, i, valid := findWildcard(path)\n\t\tif i < 0 { // No wildcard found\n\t\t\tbreak\n\t\t}\n\n\t\t// The wildcard name must only contain one ':' or '*' character\n\t\tif !valid {\n\t\t\tpanic(\"only one wildcard per path segment is allowed, has: '\" +\n\t\t\t\twildcard + \"' in path '\" + fullPath + \"'\")\n\t\t}\n\n\t\t// check if the wildcard has a name\n\t\tif len(wildcard) < 2 {\n\t\t\tpanic(\"wildcards must be named with a non-empty name in path '\" + fullPath + \"'\")\n\t\t}\n\n\t\tif wildcard[0] == ':' { // param\n\t\t\tif i > 0 {\n\t\t\t\t// Insert prefix before the current wildcard\n\t\t\t\tn.path = path[:i]\n\t\t\t\tpath = path[i:]\n\t\t\t}\n\n\t\t\tchild := &node{\n\t\t\t\tnType:    param,\n\t\t\t\tpath:     wildcard,\n\t\t\t\tfullPath: fullPath,\n\t\t\t}\n\t\t\tn.addChild(child)\n\t\t\tn.wildChild = true\n\t\t\tn = child\n\t\t\tn.priority++\n\n\t\t\t// if the path doesn't end with the wildcard, then there\n\t\t\t// will be another subpath starting with '/'\n\t\t\tif len(wildcard) < len(path) {\n\t\t\t\tpath = path[len(wildcard):]\n\n\t\t\t\tchild := &node{\n\t\t\t\t\tpriority: 1,\n\t\t\t\t\tfullPath: fullPath,\n\t\t\t\t}\n\t\t\t\tn.addChild(child)\n\t\t\t\tn = child\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\t// Otherwise we're done. Insert the handle in the new leaf\n\t\t\tn.handlers = handlers\n\t\t\treturn\n\t\t}\n\n\t\t// catchAll\n\t\tif i+len(wildcard) != len(path) {\n\t\t\tpanic(\"catch-all routes are only allowed at the end of the path in path '\" + fullPath + \"'\")\n\t\t}\n\n\t\tif len(n.path) > 0 && n.path[len(n.path)-1] == '/' {\n\t\t\tpathSeg := \"\"\n\t\t\tif len(n.children) != 0 {\n\t\t\t\tpathSeg, _, _ = strings.Cut(n.children[0].path, \"/\")\n\t\t\t}\n\t\t\tpanic(\"catch-all wildcard '\" + path +\n\t\t\t\t\"' in new path '\" + fullPath +\n\t\t\t\t\"' conflicts with existing path segment '\" + pathSeg +\n\t\t\t\t\"' in existing prefix '\" + n.path + pathSeg +\n\t\t\t\t\"'\")\n\t\t}\n\n\t\t// currently fixed width 1 for '/'\n\t\ti--\n\t\tif i < 0 || path[i] != '/' {\n\t\t\tpanic(\"no / before catch-all in path '\" + fullPath + \"'\")\n\t\t}\n\n\t\tn.path = path[:i]\n\n\t\t// First node: catchAll node with empty path\n\t\tchild := &node{\n\t\t\twildChild: true,\n\t\t\tnType:     catchAll,\n\t\t\tfullPath:  fullPath,\n\t\t}\n\n\t\tn.addChild(child)\n\t\tn.indices = \"/\"\n\t\tn = child\n\t\tn.priority++\n\n\t\t// second node: node holding the variable\n\t\tchild = &node{\n\t\t\tpath:     path[i:],\n\t\t\tnType:    catchAll,\n\t\t\thandlers: handlers,\n\t\t\tpriority: 1,\n\t\t\tfullPath: fullPath,\n\t\t}\n\t\tn.children = []*node{child}\n\n\t\treturn\n\t}\n\n\t// If no wildcard was found, simply insert the path and handle\n\tn.path = path\n\tn.handlers = handlers\n\tn.fullPath = fullPath\n}\n\n// nodeValue holds return values of (*Node).getValue method\ntype nodeValue struct {\n\thandlers HandlersChain\n\tparams   *Params\n\ttsr      bool\n\tfullPath string\n}\n\ntype skippedNode struct {\n\tpath        string\n\tnode        *node\n\tparamsCount int16\n}\n\n// Returns the handle registered with the given path (key). The values of\n// wildcards are saved to a map.\n// If no handle can be found, a TSR (trailing slash redirect) recommendation is\n// made if a handle exists with an extra (without the) trailing slash for the\n// given path.\nfunc (n *node) getValue(path string, params *Params, skippedNodes *[]skippedNode, unescape bool) (value nodeValue) {\n\tvar globalParamsCount int16\n\nwalk: // Outer loop for walking the tree\n\tfor {\n\t\tprefix := n.path\n\t\tif len(path) > len(prefix) {\n\t\t\tif path[:len(prefix)] == prefix {\n\t\t\t\tpath = path[len(prefix):]\n\n\t\t\t\t// Try all the non-wildcard children first by matching the indices\n\t\t\t\tidxc := path[0]\n\t\t\t\tfor i, c := range []byte(n.indices) {\n\t\t\t\t\tif c == idxc {\n\t\t\t\t\t\t//  strings.HasPrefix(n.children[len(n.children)-1].path, \":\") == n.wildChild\n\t\t\t\t\t\tif n.wildChild {\n\t\t\t\t\t\t\tindex := len(*skippedNodes)\n\t\t\t\t\t\t\t*skippedNodes = (*skippedNodes)[:index+1]\n\t\t\t\t\t\t\t(*skippedNodes)[index] = skippedNode{\n\t\t\t\t\t\t\t\tpath: prefix + path,\n\t\t\t\t\t\t\t\tnode: &node{\n\t\t\t\t\t\t\t\t\tpath:      n.path,\n\t\t\t\t\t\t\t\t\twildChild: n.wildChild,\n\t\t\t\t\t\t\t\t\tnType:     n.nType,\n\t\t\t\t\t\t\t\t\tpriority:  n.priority,\n\t\t\t\t\t\t\t\t\tchildren:  n.children,\n\t\t\t\t\t\t\t\t\thandlers:  n.handlers,\n\t\t\t\t\t\t\t\t\tfullPath:  n.fullPath,\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\tparamsCount: globalParamsCount,\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tn = n.children[i]\n\t\t\t\t\t\tcontinue walk\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tif !n.wildChild {\n\t\t\t\t\t// If the path at the end of the loop is not equal to '/' and the current node has no child nodes\n\t\t\t\t\t// the current node needs to roll back to last valid skippedNode\n\t\t\t\t\tif path != \"/\" {\n\t\t\t\t\t\tfor length := len(*skippedNodes); length > 0; length-- {\n\t\t\t\t\t\t\tskippedNode := (*skippedNodes)[length-1]\n\t\t\t\t\t\t\t*skippedNodes = (*skippedNodes)[:length-1]\n\t\t\t\t\t\t\tif strings.HasSuffix(skippedNode.path, path) {\n\t\t\t\t\t\t\t\tpath = skippedNode.path\n\t\t\t\t\t\t\t\tn = skippedNode.node\n\t\t\t\t\t\t\t\tif value.params != nil {\n\t\t\t\t\t\t\t\t\t*value.params = (*value.params)[:skippedNode.paramsCount]\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tglobalParamsCount = skippedNode.paramsCount\n\t\t\t\t\t\t\t\tcontinue walk\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\t// Nothing found.\n\t\t\t\t\t// We can recommend to redirect to the same URL without a\n\t\t\t\t\t// trailing slash if a leaf exists for that path.\n\t\t\t\t\tvalue.tsr = path == \"/\" && n.handlers != nil\n\t\t\t\t\treturn value\n\t\t\t\t}\n\n\t\t\t\t// Handle wildcard child, which is always at the end of the array\n\t\t\t\tn = n.children[len(n.children)-1]\n\t\t\t\tglobalParamsCount++\n\n\t\t\t\tswitch n.nType {\n\t\t\t\tcase param:\n\t\t\t\t\t// fix truncate the parameter\n\t\t\t\t\t// tree_test.go  line: 204\n\n\t\t\t\t\t// Find param end (either '/' or path end)\n\t\t\t\t\tend := 0\n\t\t\t\t\tfor end < len(path) && path[end] != '/' {\n\t\t\t\t\t\tend++\n\t\t\t\t\t}\n\n\t\t\t\t\t// Save param value\n\t\t\t\t\tif params != nil {\n\t\t\t\t\t\t// Preallocate capacity if necessary\n\t\t\t\t\t\tif cap(*params) < int(globalParamsCount) {\n\t\t\t\t\t\t\tnewParams := make(Params, len(*params), globalParamsCount)\n\t\t\t\t\t\t\tcopy(newParams, *params)\n\t\t\t\t\t\t\t*params = newParams\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tif value.params == nil {\n\t\t\t\t\t\t\tvalue.params = params\n\t\t\t\t\t\t}\n\t\t\t\t\t\t// Expand slice within preallocated capacity\n\t\t\t\t\t\ti := len(*value.params)\n\t\t\t\t\t\t*value.params = (*value.params)[:i+1]\n\t\t\t\t\t\tval := path[:end]\n\t\t\t\t\t\tif unescape {\n\t\t\t\t\t\t\tif v, err := url.QueryUnescape(val); err == nil {\n\t\t\t\t\t\t\t\tval = v\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\t(*value.params)[i] = Param{\n\t\t\t\t\t\t\tKey:   n.path[1:],\n\t\t\t\t\t\t\tValue: val,\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\t// we need to go deeper!\n\t\t\t\t\tif end < len(path) {\n\t\t\t\t\t\tif len(n.children) > 0 {\n\t\t\t\t\t\t\tpath = path[end:]\n\t\t\t\t\t\t\tn = n.children[0]\n\t\t\t\t\t\t\tcontinue walk\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\t// ... but we can't\n\t\t\t\t\t\tvalue.tsr = len(path) == end+1\n\t\t\t\t\t\treturn value\n\t\t\t\t\t}\n\n\t\t\t\t\tif value.handlers = n.handlers; value.handlers != nil {\n\t\t\t\t\t\tvalue.fullPath = n.fullPath\n\t\t\t\t\t\treturn value\n\t\t\t\t\t}\n\t\t\t\t\tif len(n.children) == 1 {\n\t\t\t\t\t\t// No handle found. Check if a handle for this path + a\n\t\t\t\t\t\t// trailing slash exists for TSR recommendation\n\t\t\t\t\t\tn = n.children[0]\n\t\t\t\t\t\tvalue.tsr = (n.path == \"/\" && n.handlers != nil) || (n.path == \"\" && n.indices == \"/\")\n\t\t\t\t\t}\n\t\t\t\t\treturn value\n\n\t\t\t\tcase catchAll:\n\t\t\t\t\t// Save param value\n\t\t\t\t\tif params != nil {\n\t\t\t\t\t\t// Preallocate capacity if necessary\n\t\t\t\t\t\tif cap(*params) < int(globalParamsCount) {\n\t\t\t\t\t\t\tnewParams := make(Params, len(*params), globalParamsCount)\n\t\t\t\t\t\t\tcopy(newParams, *params)\n\t\t\t\t\t\t\t*params = newParams\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tif value.params == nil {\n\t\t\t\t\t\t\tvalue.params = params\n\t\t\t\t\t\t}\n\t\t\t\t\t\t// Expand slice within preallocated capacity\n\t\t\t\t\t\ti := len(*value.params)\n\t\t\t\t\t\t*value.params = (*value.params)[:i+1]\n\t\t\t\t\t\tval := path\n\t\t\t\t\t\tif unescape {\n\t\t\t\t\t\t\tif v, err := url.QueryUnescape(path); err == nil {\n\t\t\t\t\t\t\t\tval = v\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\t(*value.params)[i] = Param{\n\t\t\t\t\t\t\tKey:   n.path[2:],\n\t\t\t\t\t\t\tValue: val,\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\tvalue.handlers = n.handlers\n\t\t\t\t\tvalue.fullPath = n.fullPath\n\t\t\t\t\treturn value\n\n\t\t\t\tdefault:\n\t\t\t\t\tpanic(\"invalid node type\")\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tif path == prefix {\n\t\t\t// If the current path does not equal '/' and the node does not have a registered handle and the most recently matched node has a child node\n\t\t\t// the current node needs to roll back to last valid skippedNode\n\t\t\tif n.handlers == nil && path != \"/\" {\n\t\t\t\tfor length := len(*skippedNodes); length > 0; length-- {\n\t\t\t\t\tskippedNode := (*skippedNodes)[length-1]\n\t\t\t\t\t*skippedNodes = (*skippedNodes)[:length-1]\n\t\t\t\t\tif strings.HasSuffix(skippedNode.path, path) {\n\t\t\t\t\t\tpath = skippedNode.path\n\t\t\t\t\t\tn = skippedNode.node\n\t\t\t\t\t\tif value.params != nil {\n\t\t\t\t\t\t\t*value.params = (*value.params)[:skippedNode.paramsCount]\n\t\t\t\t\t\t}\n\t\t\t\t\t\tglobalParamsCount = skippedNode.paramsCount\n\t\t\t\t\t\tcontinue walk\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\t//\tn = latestNode.children[len(latestNode.children)-1]\n\t\t\t}\n\t\t\t// We should have reached the node containing the handle.\n\t\t\t// Check if this node has a handle registered.\n\t\t\tif value.handlers = n.handlers; value.handlers != nil {\n\t\t\t\tvalue.fullPath = n.fullPath\n\t\t\t\treturn value\n\t\t\t}\n\n\t\t\t// If there is no handle for this route, but this route has a\n\t\t\t// wildcard child, there must be a handle for this path with an\n\t\t\t// additional trailing slash\n\t\t\tif path == \"/\" && n.wildChild && n.nType != root {\n\t\t\t\tvalue.tsr = true\n\t\t\t\treturn value\n\t\t\t}\n\n\t\t\tif path == \"/\" && n.nType == static {\n\t\t\t\tvalue.tsr = true\n\t\t\t\treturn value\n\t\t\t}\n\n\t\t\t// No handle found. Check if a handle for this path + a\n\t\t\t// trailing slash exists for trailing slash recommendation\n\t\t\tfor i, c := range []byte(n.indices) {\n\t\t\t\tif c == '/' {\n\t\t\t\t\tn = n.children[i]\n\t\t\t\t\tvalue.tsr = (len(n.path) == 1 && n.handlers != nil) ||\n\t\t\t\t\t\t(n.nType == catchAll && n.children[0].handlers != nil)\n\t\t\t\t\treturn value\n\t\t\t\t}\n\t\t\t}\n\n\t\t\treturn value\n\t\t}\n\n\t\t// Nothing found. We can recommend to redirect to the same URL with an\n\t\t// extra trailing slash if a leaf exists for that path\n\t\tvalue.tsr = path == \"/\" ||\n\t\t\t(len(prefix) == len(path)+1 && prefix[len(path)] == '/' &&\n\t\t\t\tpath == prefix[:len(prefix)-1] && n.handlers != nil)\n\n\t\t// roll back to last valid skippedNode\n\t\tif !value.tsr && path != \"/\" {\n\t\t\tfor length := len(*skippedNodes); length > 0; length-- {\n\t\t\t\tskippedNode := (*skippedNodes)[length-1]\n\t\t\t\t*skippedNodes = (*skippedNodes)[:length-1]\n\t\t\t\tif strings.HasSuffix(skippedNode.path, path) {\n\t\t\t\t\tpath = skippedNode.path\n\t\t\t\t\tn = skippedNode.node\n\t\t\t\t\tif value.params != nil {\n\t\t\t\t\t\t*value.params = (*value.params)[:skippedNode.paramsCount]\n\t\t\t\t\t}\n\t\t\t\t\tglobalParamsCount = skippedNode.paramsCount\n\t\t\t\t\tcontinue walk\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn value\n\t}\n}\n\n// Makes a case-insensitive lookup of the given path and tries to find a handler.\n// It can optionally also fix trailing slashes.\n// It returns the case-corrected path and a bool indicating whether the lookup\n// was successful.\nfunc (n *node) findCaseInsensitivePath(path string, fixTrailingSlash bool) ([]byte, bool) {\n\tconst stackBufSize = 128\n\n\tbuf := make([]byte, 0, max(stackBufSize, len(path)+1))\n\n\tciPath := n.findCaseInsensitivePathRec(\n\t\tpath,\n\t\tbuf,       // Preallocate enough memory for new path\n\t\t[4]byte{}, // Empty rune buffer\n\t\tfixTrailingSlash,\n\t)\n\n\treturn ciPath, ciPath != nil\n}\n\n// Shift bytes in array by n bytes left\nfunc shiftNRuneBytes(rb [4]byte, n int) [4]byte {\n\tswitch n {\n\tcase 0:\n\t\treturn rb\n\tcase 1:\n\t\treturn [4]byte{rb[1], rb[2], rb[3], 0}\n\tcase 2:\n\t\treturn [4]byte{rb[2], rb[3]}\n\tcase 3:\n\t\treturn [4]byte{rb[3]}\n\tdefault:\n\t\treturn [4]byte{}\n\t}\n}\n\n// Recursive case-insensitive lookup function used by n.findCaseInsensitivePath\nfunc (n *node) findCaseInsensitivePathRec(path string, ciPath []byte, rb [4]byte, fixTrailingSlash bool) []byte {\n\tnpLen := len(n.path)\n\nwalk: // Outer loop for walking the tree\n\tfor len(path) >= npLen && (npLen == 0 || strings.EqualFold(path[1:npLen], n.path[1:])) {\n\t\t// Add common prefix to result\n\t\toldPath := path\n\t\tpath = path[npLen:]\n\t\tciPath = append(ciPath, n.path...)\n\n\t\tif len(path) == 0 {\n\t\t\t// We should have reached the node containing the handle.\n\t\t\t// Check if this node has a handle registered.\n\t\t\tif n.handlers != nil {\n\t\t\t\treturn ciPath\n\t\t\t}\n\n\t\t\t// No handle found.\n\t\t\t// Try to fix the path by adding a trailing slash\n\t\t\tif fixTrailingSlash {\n\t\t\t\tfor i, c := range []byte(n.indices) {\n\t\t\t\t\tif c == '/' {\n\t\t\t\t\t\tn = n.children[i]\n\t\t\t\t\t\tif (len(n.path) == 1 && n.handlers != nil) ||\n\t\t\t\t\t\t\t(n.nType == catchAll && n.children[0].handlers != nil) {\n\t\t\t\t\t\t\treturn append(ciPath, '/')\n\t\t\t\t\t\t}\n\t\t\t\t\t\treturn nil\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn nil\n\t\t}\n\n\t\t// If this node does not have a wildcard (param or catchAll) child,\n\t\t// we can just look up the next child node and continue to walk down\n\t\t// the tree\n\t\tif !n.wildChild {\n\t\t\t// Skip rune bytes already processed\n\t\t\trb = shiftNRuneBytes(rb, npLen)\n\n\t\t\tif rb[0] != 0 {\n\t\t\t\t// Old rune not finished\n\t\t\t\tidxc := rb[0]\n\t\t\t\tfor i, c := range []byte(n.indices) {\n\t\t\t\t\tif c == idxc {\n\t\t\t\t\t\t// continue with child node\n\t\t\t\t\t\tn = n.children[i]\n\t\t\t\t\t\tnpLen = len(n.path)\n\t\t\t\t\t\tcontinue walk\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\t// Process a new rune\n\t\t\t\tvar rv rune\n\n\t\t\t\t// Find rune start.\n\t\t\t\t// Runes are up to 4 byte long,\n\t\t\t\t// -4 would definitely be another rune.\n\t\t\t\tvar off int\n\t\t\t\tfor max_ := min(npLen, 3); off < max_; off++ {\n\t\t\t\t\tif i := npLen - off; utf8.RuneStart(oldPath[i]) {\n\t\t\t\t\t\t// read rune from cached path\n\t\t\t\t\t\trv, _ = utf8.DecodeRuneInString(oldPath[i:])\n\t\t\t\t\t\tbreak\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\t// Calculate lowercase bytes of current rune\n\t\t\t\tlo := unicode.ToLower(rv)\n\t\t\t\tutf8.EncodeRune(rb[:], lo)\n\n\t\t\t\t// Skip already processed bytes\n\t\t\t\trb = shiftNRuneBytes(rb, off)\n\n\t\t\t\tidxc := rb[0]\n\t\t\t\tfor i, c := range []byte(n.indices) {\n\t\t\t\t\t// Lowercase matches\n\t\t\t\t\tif c == idxc {\n\t\t\t\t\t\t// must use a recursive approach since both the\n\t\t\t\t\t\t// uppercase byte and the lowercase byte might exist\n\t\t\t\t\t\t// as an index\n\t\t\t\t\t\tif out := n.children[i].findCaseInsensitivePathRec(\n\t\t\t\t\t\t\tpath, ciPath, rb, fixTrailingSlash,\n\t\t\t\t\t\t); out != nil {\n\t\t\t\t\t\t\treturn out\n\t\t\t\t\t\t}\n\t\t\t\t\t\tbreak\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\t// If we found no match, the same for the uppercase rune,\n\t\t\t\t// if it differs\n\t\t\t\tif up := unicode.ToUpper(rv); up != lo {\n\t\t\t\t\tutf8.EncodeRune(rb[:], up)\n\t\t\t\t\trb = shiftNRuneBytes(rb, off)\n\n\t\t\t\t\tidxc := rb[0]\n\t\t\t\t\tfor i, c := range []byte(n.indices) {\n\t\t\t\t\t\t// Uppercase matches\n\t\t\t\t\t\tif c == idxc {\n\t\t\t\t\t\t\t// Continue with child node\n\t\t\t\t\t\t\tn = n.children[i]\n\t\t\t\t\t\t\tnpLen = len(n.path)\n\t\t\t\t\t\t\tcontinue walk\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Nothing found. We can recommend to redirect to the same URL\n\t\t\t// without a trailing slash if a leaf exists for that path\n\t\t\tif fixTrailingSlash && path == \"/\" && n.handlers != nil {\n\t\t\t\treturn ciPath\n\t\t\t}\n\t\t\treturn nil\n\t\t}\n\n\t\t// When wildChild is true, try static children first (via indices)\n\t\t// before falling back to the wildcard child. This ensures that\n\t\t// case-insensitive lookups prefer static routes over param routes\n\t\t// (e.g., /PREFIX/XXX should resolve to /prefix/xxx, not match :id).\n\t\tif len(n.indices) > 0 {\n\t\t\trb = shiftNRuneBytes(rb, npLen)\n\n\t\t\tif rb[0] != 0 {\n\t\t\t\tidxc := rb[0]\n\t\t\t\tfor i, c := range []byte(n.indices) {\n\t\t\t\t\tif c == idxc {\n\t\t\t\t\t\tif out := n.children[i].findCaseInsensitivePathRec(\n\t\t\t\t\t\t\tpath, ciPath, rb, fixTrailingSlash,\n\t\t\t\t\t\t); out != nil {\n\t\t\t\t\t\t\treturn out\n\t\t\t\t\t\t}\n\t\t\t\t\t\tbreak\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tvar rv rune\n\t\t\t\tvar off int\n\t\t\t\tfor max_ := min(npLen, 3); off < max_; off++ {\n\t\t\t\t\tif i := npLen - off; utf8.RuneStart(oldPath[i]) {\n\t\t\t\t\t\trv, _ = utf8.DecodeRuneInString(oldPath[i:])\n\t\t\t\t\t\tbreak\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tlo := unicode.ToLower(rv)\n\t\t\t\tutf8.EncodeRune(rb[:], lo)\n\t\t\t\trb = shiftNRuneBytes(rb, off)\n\n\t\t\t\tidxc := rb[0]\n\t\t\t\tfor i, c := range []byte(n.indices) {\n\t\t\t\t\tif c == idxc {\n\t\t\t\t\t\tif out := n.children[i].findCaseInsensitivePathRec(\n\t\t\t\t\t\t\tpath, ciPath, rb, fixTrailingSlash,\n\t\t\t\t\t\t); out != nil {\n\t\t\t\t\t\t\treturn out\n\t\t\t\t\t\t}\n\t\t\t\t\t\tbreak\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tif up := unicode.ToUpper(rv); up != lo {\n\t\t\t\t\tutf8.EncodeRune(rb[:], up)\n\t\t\t\t\trb = shiftNRuneBytes(rb, off)\n\n\t\t\t\t\tidxc := rb[0]\n\t\t\t\t\tfor i, c := range []byte(n.indices) {\n\t\t\t\t\t\tif c == idxc {\n\t\t\t\t\t\t\tif out := n.children[i].findCaseInsensitivePathRec(\n\t\t\t\t\t\t\t\tpath, ciPath, rb, fixTrailingSlash,\n\t\t\t\t\t\t\t); out != nil {\n\t\t\t\t\t\t\t\treturn out\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tbreak\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// Fall back to wildcard child, which is always at the end of the array\n\t\tn = n.children[len(n.children)-1]\n\t\tswitch n.nType {\n\t\tcase param:\n\t\t\t// Find param end (either '/' or path end)\n\t\t\tend := 0\n\t\t\tfor end < len(path) && path[end] != '/' {\n\t\t\t\tend++\n\t\t\t}\n\n\t\t\t// Add param value to case insensitive path\n\t\t\tciPath = append(ciPath, path[:end]...)\n\n\t\t\t// We need to go deeper!\n\t\t\tif end < len(path) {\n\t\t\t\tif len(n.children) > 0 {\n\t\t\t\t\t// Continue with child node\n\t\t\t\t\tn = n.children[0]\n\t\t\t\t\tnpLen = len(n.path)\n\t\t\t\t\tpath = path[end:]\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\n\t\t\t\t// ... but we can't\n\t\t\t\tif fixTrailingSlash && len(path) == end+1 {\n\t\t\t\t\treturn ciPath\n\t\t\t\t}\n\t\t\t\treturn nil\n\t\t\t}\n\n\t\t\tif n.handlers != nil {\n\t\t\t\treturn ciPath\n\t\t\t}\n\n\t\t\tif fixTrailingSlash && len(n.children) == 1 {\n\t\t\t\t// No handle found. Check if a handle for this path + a\n\t\t\t\t// trailing slash exists\n\t\t\t\tn = n.children[0]\n\t\t\t\tif n.path == \"/\" && n.handlers != nil {\n\t\t\t\t\treturn append(ciPath, '/')\n\t\t\t\t}\n\t\t\t}\n\n\t\t\treturn nil\n\n\t\tcase catchAll:\n\t\t\treturn append(ciPath, path...)\n\n\t\tdefault:\n\t\t\tpanic(\"invalid node type\")\n\t\t}\n\t}\n\n\t// Nothing found.\n\t// Try to fix the path by adding / removing a trailing slash\n\tif fixTrailingSlash {\n\t\tif path == \"/\" {\n\t\t\treturn ciPath\n\t\t}\n\t\tif len(path)+1 == npLen && n.path[len(path)] == '/' &&\n\t\t\tstrings.EqualFold(path[1:], n.path[1:len(path)]) && n.handlers != nil {\n\t\t\treturn append(ciPath, n.path...)\n\t\t}\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "tree_test.go",
    "content": "// Copyright 2013 Julien Schmidt. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be found\n// at https://github.com/julienschmidt/httprouter/blob/master/LICENSE\n\npackage gin\n\nimport (\n\t\"fmt\"\n\t\"reflect\"\n\t\"regexp\"\n\t\"strings\"\n\t\"testing\"\n)\n\n// Used as a workaround since we can't compare functions or their addresses\nvar fakeHandlerValue string\n\nfunc fakeHandler(val string) HandlersChain {\n\treturn HandlersChain{func(c *Context) {\n\t\tfakeHandlerValue = val\n\t}}\n}\n\ntype testRequests []struct {\n\tpath       string\n\tnilHandler bool\n\troute      string\n\tps         Params\n}\n\nfunc getParams() *Params {\n\tps := make(Params, 0, 20)\n\treturn &ps\n}\n\nfunc getSkippedNodes() *[]skippedNode {\n\tps := make([]skippedNode, 0, 20)\n\treturn &ps\n}\n\nfunc checkRequests(t *testing.T, tree *node, requests testRequests, unescapes ...bool) {\n\tunescape := false\n\tif len(unescapes) >= 1 {\n\t\tunescape = unescapes[0]\n\t}\n\n\tfor _, request := range requests {\n\t\tvalue := tree.getValue(request.path, getParams(), getSkippedNodes(), unescape)\n\n\t\tif value.handlers == nil {\n\t\t\tif !request.nilHandler {\n\t\t\t\tt.Errorf(\"handle mismatch for route '%s': Expected non-nil handle\", request.path)\n\t\t\t}\n\t\t} else if request.nilHandler {\n\t\t\tt.Errorf(\"handle mismatch for route '%s': Expected nil handle\", request.path)\n\t\t} else {\n\t\t\tvalue.handlers[0](nil)\n\t\t\tif fakeHandlerValue != request.route {\n\t\t\t\tt.Errorf(\"handle mismatch for route '%s': Wrong handle (%s != %s)\", request.path, fakeHandlerValue, request.route)\n\t\t\t}\n\t\t}\n\n\t\tif value.params != nil {\n\t\t\tif !reflect.DeepEqual(*value.params, request.ps) {\n\t\t\t\tt.Errorf(\"Params mismatch for route '%s'\", request.path)\n\t\t\t}\n\t\t}\n\n\t}\n}\n\nfunc checkPriorities(t *testing.T, n *node) uint32 {\n\tvar prio uint32\n\tfor i := range n.children {\n\t\tprio += checkPriorities(t, n.children[i])\n\t}\n\n\tif n.handlers != nil {\n\t\tprio++\n\t}\n\n\tif n.priority != prio {\n\t\tt.Errorf(\n\t\t\t\"priority mismatch for node '%s': is %d, should be %d\",\n\t\t\tn.path, n.priority, prio,\n\t\t)\n\t}\n\n\treturn prio\n}\n\nfunc TestCountParams(t *testing.T) {\n\tif countParams(\"/path/:param1/static/*catch-all\") != 2 {\n\t\tt.Fail()\n\t}\n\tif countParams(strings.Repeat(\"/:param\", 256)) != 256 {\n\t\tt.Fail()\n\t}\n}\n\nfunc TestTreeAddAndGet(t *testing.T) {\n\ttree := &node{}\n\n\troutes := [...]string{\n\t\t\"/hi\",\n\t\t\"/contact\",\n\t\t\"/co\",\n\t\t\"/c\",\n\t\t\"/a\",\n\t\t\"/ab\",\n\t\t\"/doc/\",\n\t\t\"/doc/go_faq.html\",\n\t\t\"/doc/go1.html\",\n\t\t\"/α\",\n\t\t\"/β\",\n\t}\n\tfor _, route := range routes {\n\t\ttree.addRoute(route, fakeHandler(route))\n\t}\n\n\tcheckRequests(t, tree, testRequests{\n\t\t{\"/a\", false, \"/a\", nil},\n\t\t{\"/\", true, \"\", nil},\n\t\t{\"/hi\", false, \"/hi\", nil},\n\t\t{\"/contact\", false, \"/contact\", nil},\n\t\t{\"/co\", false, \"/co\", nil},\n\t\t{\"/con\", true, \"\", nil},  // key mismatch\n\t\t{\"/cona\", true, \"\", nil}, // key mismatch\n\t\t{\"/no\", true, \"\", nil},   // no matching child\n\t\t{\"/ab\", false, \"/ab\", nil},\n\t\t{\"/α\", false, \"/α\", nil},\n\t\t{\"/β\", false, \"/β\", nil},\n\t})\n\n\tcheckPriorities(t, tree)\n}\n\nfunc TestTreeWildcard(t *testing.T) {\n\ttree := &node{}\n\n\troutes := [...]string{\n\t\t\"/\",\n\t\t\"/cmd/:tool/\",\n\t\t\"/cmd/:tool/:sub\",\n\t\t\"/cmd/whoami\",\n\t\t\"/cmd/whoami/root\",\n\t\t\"/cmd/whoami/root/\",\n\t\t\"/src/*filepath\",\n\t\t\"/search/\",\n\t\t\"/search/:query\",\n\t\t\"/search/gin-gonic\",\n\t\t\"/search/google\",\n\t\t\"/user_:name\",\n\t\t\"/user_:name/about\",\n\t\t\"/files/:dir/*filepath\",\n\t\t\"/doc/\",\n\t\t\"/doc/go_faq.html\",\n\t\t\"/doc/go1.html\",\n\t\t\"/info/:user/public\",\n\t\t\"/info/:user/project/:project\",\n\t\t\"/info/:user/project/golang\",\n\t\t\"/aa/*xx\",\n\t\t\"/ab/*xx\",\n\t\t\"/:cc\",\n\t\t\"/c1/:dd/e\",\n\t\t\"/c1/:dd/e1\",\n\t\t\"/:cc/cc\",\n\t\t\"/:cc/:dd/ee\",\n\t\t\"/:cc/:dd/:ee/ff\",\n\t\t\"/:cc/:dd/:ee/:ff/gg\",\n\t\t\"/:cc/:dd/:ee/:ff/:gg/hh\",\n\t\t\"/get/test/abc/\",\n\t\t\"/get/:param/abc/\",\n\t\t\"/something/:paramname/thirdthing\",\n\t\t\"/something/secondthing/test\",\n\t\t\"/get/abc\",\n\t\t\"/get/:param\",\n\t\t\"/get/abc/123abc\",\n\t\t\"/get/abc/:param\",\n\t\t\"/get/abc/123abc/xxx8\",\n\t\t\"/get/abc/123abc/:param\",\n\t\t\"/get/abc/123abc/xxx8/1234\",\n\t\t\"/get/abc/123abc/xxx8/:param\",\n\t\t\"/get/abc/123abc/xxx8/1234/ffas\",\n\t\t\"/get/abc/123abc/xxx8/1234/:param\",\n\t\t\"/get/abc/123abc/xxx8/1234/kkdd/12c\",\n\t\t\"/get/abc/123abc/xxx8/1234/kkdd/:param\",\n\t\t\"/get/abc/:param/test\",\n\t\t\"/get/abc/123abd/:param\",\n\t\t\"/get/abc/123abddd/:param\",\n\t\t\"/get/abc/123/:param\",\n\t\t\"/get/abc/123abg/:param\",\n\t\t\"/get/abc/123abf/:param\",\n\t\t\"/get/abc/123abfff/:param\",\n\t\t\"/get/abc/escaped_colon/test\\\\:param\",\n\t}\n\tfor _, route := range routes {\n\t\ttree.addRoute(route, fakeHandler(route))\n\t}\n\n\tcheckRequests(t, tree, testRequests{\n\t\t{\"/\", false, \"/\", nil},\n\t\t{\"/cmd/test\", true, \"/cmd/:tool/\", Params{Param{\"tool\", \"test\"}}},\n\t\t{\"/cmd/test/\", false, \"/cmd/:tool/\", Params{Param{\"tool\", \"test\"}}},\n\t\t{\"/cmd/test/3\", false, \"/cmd/:tool/:sub\", Params{Param{Key: \"tool\", Value: \"test\"}, Param{Key: \"sub\", Value: \"3\"}}},\n\t\t{\"/cmd/who\", true, \"/cmd/:tool/\", Params{Param{\"tool\", \"who\"}}},\n\t\t{\"/cmd/who/\", false, \"/cmd/:tool/\", Params{Param{\"tool\", \"who\"}}},\n\t\t{\"/cmd/whoami\", false, \"/cmd/whoami\", nil},\n\t\t{\"/cmd/whoami/\", true, \"/cmd/whoami\", nil},\n\t\t{\"/cmd/whoami/r\", false, \"/cmd/:tool/:sub\", Params{Param{Key: \"tool\", Value: \"whoami\"}, Param{Key: \"sub\", Value: \"r\"}}},\n\t\t{\"/cmd/whoami/r/\", true, \"/cmd/:tool/:sub\", Params{Param{Key: \"tool\", Value: \"whoami\"}, Param{Key: \"sub\", Value: \"r\"}}},\n\t\t{\"/cmd/whoami/root\", false, \"/cmd/whoami/root\", nil},\n\t\t{\"/cmd/whoami/root/\", false, \"/cmd/whoami/root/\", nil},\n\t\t{\"/src/\", false, \"/src/*filepath\", Params{Param{Key: \"filepath\", Value: \"/\"}}},\n\t\t{\"/src/some/file.png\", false, \"/src/*filepath\", Params{Param{Key: \"filepath\", Value: \"/some/file.png\"}}},\n\t\t{\"/search/\", false, \"/search/\", nil},\n\t\t{\"/search/someth!ng+in+ünìcodé\", false, \"/search/:query\", Params{Param{Key: \"query\", Value: \"someth!ng+in+ünìcodé\"}}},\n\t\t{\"/search/someth!ng+in+ünìcodé/\", true, \"\", Params{Param{Key: \"query\", Value: \"someth!ng+in+ünìcodé\"}}},\n\t\t{\"/search/gin\", false, \"/search/:query\", Params{Param{\"query\", \"gin\"}}},\n\t\t{\"/search/gin-gonic\", false, \"/search/gin-gonic\", nil},\n\t\t{\"/search/google\", false, \"/search/google\", nil},\n\t\t{\"/user_gopher\", false, \"/user_:name\", Params{Param{Key: \"name\", Value: \"gopher\"}}},\n\t\t{\"/user_gopher/about\", false, \"/user_:name/about\", Params{Param{Key: \"name\", Value: \"gopher\"}}},\n\t\t{\"/files/js/inc/framework.js\", false, \"/files/:dir/*filepath\", Params{Param{Key: \"dir\", Value: \"js\"}, Param{Key: \"filepath\", Value: \"/inc/framework.js\"}}},\n\t\t{\"/info/gordon/public\", false, \"/info/:user/public\", Params{Param{Key: \"user\", Value: \"gordon\"}}},\n\t\t{\"/info/gordon/project/go\", false, \"/info/:user/project/:project\", Params{Param{Key: \"user\", Value: \"gordon\"}, Param{Key: \"project\", Value: \"go\"}}},\n\t\t{\"/info/gordon/project/golang\", false, \"/info/:user/project/golang\", Params{Param{Key: \"user\", Value: \"gordon\"}}},\n\t\t{\"/aa/aa\", false, \"/aa/*xx\", Params{Param{Key: \"xx\", Value: \"/aa\"}}},\n\t\t{\"/ab/ab\", false, \"/ab/*xx\", Params{Param{Key: \"xx\", Value: \"/ab\"}}},\n\t\t{\"/a\", false, \"/:cc\", Params{Param{Key: \"cc\", Value: \"a\"}}},\n\t\t// * Error with argument being intercepted\n\t\t// new PR handle (/all /all/cc /a/cc)\n\t\t// fix PR: https://github.com/gin-gonic/gin/pull/2796\n\t\t{\"/all\", false, \"/:cc\", Params{Param{Key: \"cc\", Value: \"all\"}}},\n\t\t{\"/d\", false, \"/:cc\", Params{Param{Key: \"cc\", Value: \"d\"}}},\n\t\t{\"/ad\", false, \"/:cc\", Params{Param{Key: \"cc\", Value: \"ad\"}}},\n\t\t{\"/dd\", false, \"/:cc\", Params{Param{Key: \"cc\", Value: \"dd\"}}},\n\t\t{\"/dddaa\", false, \"/:cc\", Params{Param{Key: \"cc\", Value: \"dddaa\"}}},\n\t\t{\"/aa\", false, \"/:cc\", Params{Param{Key: \"cc\", Value: \"aa\"}}},\n\t\t{\"/aaa\", false, \"/:cc\", Params{Param{Key: \"cc\", Value: \"aaa\"}}},\n\t\t{\"/aaa/cc\", false, \"/:cc/cc\", Params{Param{Key: \"cc\", Value: \"aaa\"}}},\n\t\t{\"/ab\", false, \"/:cc\", Params{Param{Key: \"cc\", Value: \"ab\"}}},\n\t\t{\"/abb\", false, \"/:cc\", Params{Param{Key: \"cc\", Value: \"abb\"}}},\n\t\t{\"/abb/cc\", false, \"/:cc/cc\", Params{Param{Key: \"cc\", Value: \"abb\"}}},\n\t\t{\"/allxxxx\", false, \"/:cc\", Params{Param{Key: \"cc\", Value: \"allxxxx\"}}},\n\t\t{\"/alldd\", false, \"/:cc\", Params{Param{Key: \"cc\", Value: \"alldd\"}}},\n\t\t{\"/all/cc\", false, \"/:cc/cc\", Params{Param{Key: \"cc\", Value: \"all\"}}},\n\t\t{\"/a/cc\", false, \"/:cc/cc\", Params{Param{Key: \"cc\", Value: \"a\"}}},\n\t\t{\"/c1/d/e\", false, \"/c1/:dd/e\", Params{Param{Key: \"dd\", Value: \"d\"}}},\n\t\t{\"/c1/d/e1\", false, \"/c1/:dd/e1\", Params{Param{Key: \"dd\", Value: \"d\"}}},\n\t\t{\"/c1/d/ee\", false, \"/:cc/:dd/ee\", Params{Param{Key: \"cc\", Value: \"c1\"}, Param{Key: \"dd\", Value: \"d\"}}},\n\t\t{\"/cc/cc\", false, \"/:cc/cc\", Params{Param{Key: \"cc\", Value: \"cc\"}}},\n\t\t{\"/ccc/cc\", false, \"/:cc/cc\", Params{Param{Key: \"cc\", Value: \"ccc\"}}},\n\t\t{\"/deedwjfs/cc\", false, \"/:cc/cc\", Params{Param{Key: \"cc\", Value: \"deedwjfs\"}}},\n\t\t{\"/acllcc/cc\", false, \"/:cc/cc\", Params{Param{Key: \"cc\", Value: \"acllcc\"}}},\n\t\t{\"/get/test/abc/\", false, \"/get/test/abc/\", nil},\n\t\t{\"/get/te/abc/\", false, \"/get/:param/abc/\", Params{Param{Key: \"param\", Value: \"te\"}}},\n\t\t{\"/get/testaa/abc/\", false, \"/get/:param/abc/\", Params{Param{Key: \"param\", Value: \"testaa\"}}},\n\t\t{\"/get/xx/abc/\", false, \"/get/:param/abc/\", Params{Param{Key: \"param\", Value: \"xx\"}}},\n\t\t{\"/get/tt/abc/\", false, \"/get/:param/abc/\", Params{Param{Key: \"param\", Value: \"tt\"}}},\n\t\t{\"/get/a/abc/\", false, \"/get/:param/abc/\", Params{Param{Key: \"param\", Value: \"a\"}}},\n\t\t{\"/get/t/abc/\", false, \"/get/:param/abc/\", Params{Param{Key: \"param\", Value: \"t\"}}},\n\t\t{\"/get/aa/abc/\", false, \"/get/:param/abc/\", Params{Param{Key: \"param\", Value: \"aa\"}}},\n\t\t{\"/get/abas/abc/\", false, \"/get/:param/abc/\", Params{Param{Key: \"param\", Value: \"abas\"}}},\n\t\t{\"/something/secondthing/test\", false, \"/something/secondthing/test\", nil},\n\t\t{\"/something/abcdad/thirdthing\", false, \"/something/:paramname/thirdthing\", Params{Param{Key: \"paramname\", Value: \"abcdad\"}}},\n\t\t{\"/something/secondthingaaaa/thirdthing\", false, \"/something/:paramname/thirdthing\", Params{Param{Key: \"paramname\", Value: \"secondthingaaaa\"}}},\n\t\t{\"/something/se/thirdthing\", false, \"/something/:paramname/thirdthing\", Params{Param{Key: \"paramname\", Value: \"se\"}}},\n\t\t{\"/something/s/thirdthing\", false, \"/something/:paramname/thirdthing\", Params{Param{Key: \"paramname\", Value: \"s\"}}},\n\t\t{\"/c/d/ee\", false, \"/:cc/:dd/ee\", Params{Param{Key: \"cc\", Value: \"c\"}, Param{Key: \"dd\", Value: \"d\"}}},\n\t\t{\"/c/d/e/ff\", false, \"/:cc/:dd/:ee/ff\", Params{Param{Key: \"cc\", Value: \"c\"}, Param{Key: \"dd\", Value: \"d\"}, Param{Key: \"ee\", Value: \"e\"}}},\n\t\t{\"/c/d/e/f/gg\", false, \"/:cc/:dd/:ee/:ff/gg\", Params{Param{Key: \"cc\", Value: \"c\"}, Param{Key: \"dd\", Value: \"d\"}, Param{Key: \"ee\", Value: \"e\"}, Param{Key: \"ff\", Value: \"f\"}}},\n\t\t{\"/c/d/e/f/g/hh\", false, \"/:cc/:dd/:ee/:ff/:gg/hh\", Params{Param{Key: \"cc\", Value: \"c\"}, Param{Key: \"dd\", Value: \"d\"}, Param{Key: \"ee\", Value: \"e\"}, Param{Key: \"ff\", Value: \"f\"}, Param{Key: \"gg\", Value: \"g\"}}},\n\t\t{\"/cc/dd/ee/ff/gg/hh\", false, \"/:cc/:dd/:ee/:ff/:gg/hh\", Params{Param{Key: \"cc\", Value: \"cc\"}, Param{Key: \"dd\", Value: \"dd\"}, Param{Key: \"ee\", Value: \"ee\"}, Param{Key: \"ff\", Value: \"ff\"}, Param{Key: \"gg\", Value: \"gg\"}}},\n\t\t{\"/get/abc\", false, \"/get/abc\", nil},\n\t\t{\"/get/a\", false, \"/get/:param\", Params{Param{Key: \"param\", Value: \"a\"}}},\n\t\t{\"/get/abz\", false, \"/get/:param\", Params{Param{Key: \"param\", Value: \"abz\"}}},\n\t\t{\"/get/12a\", false, \"/get/:param\", Params{Param{Key: \"param\", Value: \"12a\"}}},\n\t\t{\"/get/abcd\", false, \"/get/:param\", Params{Param{Key: \"param\", Value: \"abcd\"}}},\n\t\t{\"/get/abc/123abc\", false, \"/get/abc/123abc\", nil},\n\t\t{\"/get/abc/12\", false, \"/get/abc/:param\", Params{Param{Key: \"param\", Value: \"12\"}}},\n\t\t{\"/get/abc/123ab\", false, \"/get/abc/:param\", Params{Param{Key: \"param\", Value: \"123ab\"}}},\n\t\t{\"/get/abc/xyz\", false, \"/get/abc/:param\", Params{Param{Key: \"param\", Value: \"xyz\"}}},\n\t\t{\"/get/abc/123abcddxx\", false, \"/get/abc/:param\", Params{Param{Key: \"param\", Value: \"123abcddxx\"}}},\n\t\t{\"/get/abc/123abc/xxx8\", false, \"/get/abc/123abc/xxx8\", nil},\n\t\t{\"/get/abc/123abc/x\", false, \"/get/abc/123abc/:param\", Params{Param{Key: \"param\", Value: \"x\"}}},\n\t\t{\"/get/abc/123abc/xxx\", false, \"/get/abc/123abc/:param\", Params{Param{Key: \"param\", Value: \"xxx\"}}},\n\t\t{\"/get/abc/123abc/abc\", false, \"/get/abc/123abc/:param\", Params{Param{Key: \"param\", Value: \"abc\"}}},\n\t\t{\"/get/abc/123abc/xxx8xxas\", false, \"/get/abc/123abc/:param\", Params{Param{Key: \"param\", Value: \"xxx8xxas\"}}},\n\t\t{\"/get/abc/123abc/xxx8/1234\", false, \"/get/abc/123abc/xxx8/1234\", nil},\n\t\t{\"/get/abc/123abc/xxx8/1\", false, \"/get/abc/123abc/xxx8/:param\", Params{Param{Key: \"param\", Value: \"1\"}}},\n\t\t{\"/get/abc/123abc/xxx8/123\", false, \"/get/abc/123abc/xxx8/:param\", Params{Param{Key: \"param\", Value: \"123\"}}},\n\t\t{\"/get/abc/123abc/xxx8/78k\", false, \"/get/abc/123abc/xxx8/:param\", Params{Param{Key: \"param\", Value: \"78k\"}}},\n\t\t{\"/get/abc/123abc/xxx8/1234xxxd\", false, \"/get/abc/123abc/xxx8/:param\", Params{Param{Key: \"param\", Value: \"1234xxxd\"}}},\n\t\t{\"/get/abc/123abc/xxx8/1234/ffas\", false, \"/get/abc/123abc/xxx8/1234/ffas\", nil},\n\t\t{\"/get/abc/123abc/xxx8/1234/f\", false, \"/get/abc/123abc/xxx8/1234/:param\", Params{Param{Key: \"param\", Value: \"f\"}}},\n\t\t{\"/get/abc/123abc/xxx8/1234/ffa\", false, \"/get/abc/123abc/xxx8/1234/:param\", Params{Param{Key: \"param\", Value: \"ffa\"}}},\n\t\t{\"/get/abc/123abc/xxx8/1234/kka\", false, \"/get/abc/123abc/xxx8/1234/:param\", Params{Param{Key: \"param\", Value: \"kka\"}}},\n\t\t{\"/get/abc/123abc/xxx8/1234/ffas321\", false, \"/get/abc/123abc/xxx8/1234/:param\", Params{Param{Key: \"param\", Value: \"ffas321\"}}},\n\t\t{\"/get/abc/123abc/xxx8/1234/kkdd/12c\", false, \"/get/abc/123abc/xxx8/1234/kkdd/12c\", nil},\n\t\t{\"/get/abc/123abc/xxx8/1234/kkdd/1\", false, \"/get/abc/123abc/xxx8/1234/kkdd/:param\", Params{Param{Key: \"param\", Value: \"1\"}}},\n\t\t{\"/get/abc/123abc/xxx8/1234/kkdd/12\", false, \"/get/abc/123abc/xxx8/1234/kkdd/:param\", Params{Param{Key: \"param\", Value: \"12\"}}},\n\t\t{\"/get/abc/123abc/xxx8/1234/kkdd/12b\", false, \"/get/abc/123abc/xxx8/1234/kkdd/:param\", Params{Param{Key: \"param\", Value: \"12b\"}}},\n\t\t{\"/get/abc/123abc/xxx8/1234/kkdd/34\", false, \"/get/abc/123abc/xxx8/1234/kkdd/:param\", Params{Param{Key: \"param\", Value: \"34\"}}},\n\t\t{\"/get/abc/123abc/xxx8/1234/kkdd/12c2e3\", false, \"/get/abc/123abc/xxx8/1234/kkdd/:param\", Params{Param{Key: \"param\", Value: \"12c2e3\"}}},\n\t\t{\"/get/abc/12/test\", false, \"/get/abc/:param/test\", Params{Param{Key: \"param\", Value: \"12\"}}},\n\t\t{\"/get/abc/123abdd/test\", false, \"/get/abc/:param/test\", Params{Param{Key: \"param\", Value: \"123abdd\"}}},\n\t\t{\"/get/abc/123abdddf/test\", false, \"/get/abc/:param/test\", Params{Param{Key: \"param\", Value: \"123abdddf\"}}},\n\t\t{\"/get/abc/123ab/test\", false, \"/get/abc/:param/test\", Params{Param{Key: \"param\", Value: \"123ab\"}}},\n\t\t{\"/get/abc/123abgg/test\", false, \"/get/abc/:param/test\", Params{Param{Key: \"param\", Value: \"123abgg\"}}},\n\t\t{\"/get/abc/123abff/test\", false, \"/get/abc/:param/test\", Params{Param{Key: \"param\", Value: \"123abff\"}}},\n\t\t{\"/get/abc/123abffff/test\", false, \"/get/abc/:param/test\", Params{Param{Key: \"param\", Value: \"123abffff\"}}},\n\t\t{\"/get/abc/123abd/test\", false, \"/get/abc/123abd/:param\", Params{Param{Key: \"param\", Value: \"test\"}}},\n\t\t{\"/get/abc/123abddd/test\", false, \"/get/abc/123abddd/:param\", Params{Param{Key: \"param\", Value: \"test\"}}},\n\t\t{\"/get/abc/123/test22\", false, \"/get/abc/123/:param\", Params{Param{Key: \"param\", Value: \"test22\"}}},\n\t\t{\"/get/abc/123abg/test\", false, \"/get/abc/123abg/:param\", Params{Param{Key: \"param\", Value: \"test\"}}},\n\t\t{\"/get/abc/123abf/testss\", false, \"/get/abc/123abf/:param\", Params{Param{Key: \"param\", Value: \"testss\"}}},\n\t\t{\"/get/abc/123abfff/te\", false, \"/get/abc/123abfff/:param\", Params{Param{Key: \"param\", Value: \"te\"}}},\n\t\t{\"/get/abc/escaped_colon/test\\\\:param\", false, \"/get/abc/escaped_colon/test\\\\:param\", nil},\n\t})\n\n\tcheckPriorities(t, tree)\n}\n\nfunc TestUnescapeParameters(t *testing.T) {\n\ttree := &node{}\n\n\troutes := [...]string{\n\t\t\"/\",\n\t\t\"/cmd/:tool/:sub\",\n\t\t\"/cmd/:tool/\",\n\t\t\"/src/*filepath\",\n\t\t\"/search/:query\",\n\t\t\"/files/:dir/*filepath\",\n\t\t\"/info/:user/project/:project\",\n\t\t\"/info/:user\",\n\t}\n\tfor _, route := range routes {\n\t\ttree.addRoute(route, fakeHandler(route))\n\t}\n\n\tunescape := true\n\tcheckRequests(t, tree, testRequests{\n\t\t{\"/\", false, \"/\", nil},\n\t\t{\"/cmd/test/\", false, \"/cmd/:tool/\", Params{Param{Key: \"tool\", Value: \"test\"}}},\n\t\t{\"/cmd/test\", true, \"\", Params{Param{Key: \"tool\", Value: \"test\"}}},\n\t\t{\"/src/some/file.png\", false, \"/src/*filepath\", Params{Param{Key: \"filepath\", Value: \"/some/file.png\"}}},\n\t\t{\"/src/some/file+test.png\", false, \"/src/*filepath\", Params{Param{Key: \"filepath\", Value: \"/some/file test.png\"}}},\n\t\t{\"/src/some/file++++%%%%test.png\", false, \"/src/*filepath\", Params{Param{Key: \"filepath\", Value: \"/some/file++++%%%%test.png\"}}},\n\t\t{\"/src/some/file%2Ftest.png\", false, \"/src/*filepath\", Params{Param{Key: \"filepath\", Value: \"/some/file/test.png\"}}},\n\t\t{\"/search/someth!ng+in+ünìcodé\", false, \"/search/:query\", Params{Param{Key: \"query\", Value: \"someth!ng in ünìcodé\"}}},\n\t\t{\"/info/gordon/project/go\", false, \"/info/:user/project/:project\", Params{Param{Key: \"user\", Value: \"gordon\"}, Param{Key: \"project\", Value: \"go\"}}},\n\t\t{\"/info/slash%2Fgordon\", false, \"/info/:user\", Params{Param{Key: \"user\", Value: \"slash/gordon\"}}},\n\t\t{\"/info/slash%2Fgordon/project/Project%20%231\", false, \"/info/:user/project/:project\", Params{Param{Key: \"user\", Value: \"slash/gordon\"}, Param{Key: \"project\", Value: \"Project #1\"}}},\n\t\t{\"/info/slash%%%%\", false, \"/info/:user\", Params{Param{Key: \"user\", Value: \"slash%%%%\"}}},\n\t\t{\"/info/slash%%%%2Fgordon/project/Project%%%%20%231\", false, \"/info/:user/project/:project\", Params{Param{Key: \"user\", Value: \"slash%%%%2Fgordon\"}, Param{Key: \"project\", Value: \"Project%%%%20%231\"}}},\n\t}, unescape)\n\n\tcheckPriorities(t, tree)\n}\n\nfunc catchPanic(testFunc func()) (recv any) {\n\tdefer func() {\n\t\trecv = recover()\n\t}()\n\n\ttestFunc()\n\treturn\n}\n\ntype testRoute struct {\n\tpath     string\n\tconflict bool\n}\n\nfunc testRoutes(t *testing.T, routes []testRoute) {\n\ttree := &node{}\n\n\tfor _, route := range routes {\n\t\trecv := catchPanic(func() {\n\t\t\ttree.addRoute(route.path, nil)\n\t\t})\n\n\t\tif route.conflict {\n\t\t\tif recv == nil {\n\t\t\t\tt.Errorf(\"no panic for conflicting route '%s'\", route.path)\n\t\t\t}\n\t\t} else if recv != nil {\n\t\t\tt.Errorf(\"unexpected panic for route '%s': %v\", route.path, recv)\n\t\t}\n\t}\n}\n\nfunc TestTreeWildcardConflict(t *testing.T) {\n\troutes := []testRoute{\n\t\t{\"/cmd/:tool/:sub\", false},\n\t\t{\"/cmd/vet\", false},\n\t\t{\"/foo/bar\", false},\n\t\t{\"/foo/:name\", false},\n\t\t{\"/foo/:names\", true},\n\t\t{\"/cmd/*path\", true},\n\t\t{\"/cmd/:badvar\", true},\n\t\t{\"/cmd/:tool/names\", false},\n\t\t{\"/cmd/:tool/:badsub/details\", true},\n\t\t{\"/src/*filepath\", false},\n\t\t{\"/src/:file\", true},\n\t\t{\"/src/static.json\", true},\n\t\t{\"/src/*filepathx\", true},\n\t\t{\"/src/\", true},\n\t\t{\"/src/foo/bar\", true},\n\t\t{\"/src1/\", false},\n\t\t{\"/src1/*filepath\", true},\n\t\t{\"/src2*filepath\", true},\n\t\t{\"/src2/*filepath\", false},\n\t\t{\"/search/:query\", false},\n\t\t{\"/search/valid\", false},\n\t\t{\"/user_:name\", false},\n\t\t{\"/user_x\", false},\n\t\t{\"/user_:name\", false},\n\t\t{\"/id:id\", false},\n\t\t{\"/id/:id\", false},\n\t\t{\"/static/*file\", false},\n\t\t{\"/static/\", true},\n\t\t{\"/escape/test\\\\:d1\", false},\n\t\t{\"/escape/test\\\\:d2\", false},\n\t\t{\"/escape/test:param\", false},\n\t}\n\ttestRoutes(t, routes)\n}\n\nfunc TestCatchAllAfterSlash(t *testing.T) {\n\troutes := []testRoute{\n\t\t{\"/non-leading-*catchall\", true},\n\t}\n\ttestRoutes(t, routes)\n}\n\nfunc TestTreeChildConflict(t *testing.T) {\n\troutes := []testRoute{\n\t\t{\"/cmd/vet\", false},\n\t\t{\"/cmd/:tool\", false},\n\t\t{\"/cmd/:tool/:sub\", false},\n\t\t{\"/cmd/:tool/misc\", false},\n\t\t{\"/cmd/:tool/:othersub\", true},\n\t\t{\"/src/AUTHORS\", false},\n\t\t{\"/src/*filepath\", true},\n\t\t{\"/user_x\", false},\n\t\t{\"/user_:name\", false},\n\t\t{\"/id/:id\", false},\n\t\t{\"/id:id\", false},\n\t\t{\"/:id\", false},\n\t\t{\"/*filepath\", true},\n\t}\n\ttestRoutes(t, routes)\n}\n\nfunc TestTreeDuplicatePath(t *testing.T) {\n\ttree := &node{}\n\n\troutes := [...]string{\n\t\t\"/\",\n\t\t\"/doc/\",\n\t\t\"/src/*filepath\",\n\t\t\"/search/:query\",\n\t\t\"/user_:name\",\n\t}\n\tfor _, route := range routes {\n\t\trecv := catchPanic(func() {\n\t\t\ttree.addRoute(route, fakeHandler(route))\n\t\t})\n\t\tif recv != nil {\n\t\t\tt.Fatalf(\"panic inserting route '%s': %v\", route, recv)\n\t\t}\n\n\t\t// Add again\n\t\trecv = catchPanic(func() {\n\t\t\ttree.addRoute(route, nil)\n\t\t})\n\t\tif recv == nil {\n\t\t\tt.Fatalf(\"no panic while inserting duplicate route '%s\", route)\n\t\t}\n\t}\n\n\t// printChildren(tree, \"\")\n\n\tcheckRequests(t, tree, testRequests{\n\t\t{\"/\", false, \"/\", nil},\n\t\t{\"/doc/\", false, \"/doc/\", nil},\n\t\t{\"/src/some/file.png\", false, \"/src/*filepath\", Params{Param{\"filepath\", \"/some/file.png\"}}},\n\t\t{\"/search/someth!ng+in+ünìcodé\", false, \"/search/:query\", Params{Param{\"query\", \"someth!ng+in+ünìcodé\"}}},\n\t\t{\"/user_gopher\", false, \"/user_:name\", Params{Param{\"name\", \"gopher\"}}},\n\t})\n}\n\nfunc TestEmptyWildcardName(t *testing.T) {\n\ttree := &node{}\n\n\troutes := [...]string{\n\t\t\"/user:\",\n\t\t\"/user:/\",\n\t\t\"/cmd/:/\",\n\t\t\"/src/*\",\n\t}\n\tfor _, route := range routes {\n\t\trecv := catchPanic(func() {\n\t\t\ttree.addRoute(route, nil)\n\t\t})\n\t\tif recv == nil {\n\t\t\tt.Fatalf(\"no panic while inserting route with empty wildcard name '%s\", route)\n\t\t}\n\t}\n}\n\nfunc TestTreeCatchAllConflict(t *testing.T) {\n\troutes := []testRoute{\n\t\t{\"/src/*filepath/x\", true},\n\t\t{\"/src2/\", false},\n\t\t{\"/src2/*filepath/x\", true},\n\t\t{\"/src3/*filepath\", false},\n\t\t{\"/src3/*filepath/x\", true},\n\t}\n\ttestRoutes(t, routes)\n}\n\nfunc TestTreeCatchAllConflictRoot(t *testing.T) {\n\troutes := []testRoute{\n\t\t{\"/\", false},\n\t\t{\"/*filepath\", true},\n\t}\n\ttestRoutes(t, routes)\n}\n\nfunc TestTreeCatchMaxParams(t *testing.T) {\n\ttree := &node{}\n\troute := \"/cmd/*filepath\"\n\ttree.addRoute(route, fakeHandler(route))\n}\n\nfunc TestTreeDoubleWildcard(t *testing.T) {\n\tconst panicMsg = \"only one wildcard per path segment is allowed\"\n\n\troutes := [...]string{\n\t\t\"/:foo:bar\",\n\t\t\"/:foo:bar/\",\n\t\t\"/:foo*bar\",\n\t}\n\n\tfor _, route := range routes {\n\t\ttree := &node{}\n\t\trecv := catchPanic(func() {\n\t\t\ttree.addRoute(route, nil)\n\t\t})\n\n\t\tif rs, ok := recv.(string); !ok || !strings.HasPrefix(rs, panicMsg) {\n\t\t\tt.Fatalf(`\"Expected panic \"%s\" for route '%s', got \"%v\"`, panicMsg, route, recv)\n\t\t}\n\t}\n}\n\n/*func TestTreeDuplicateWildcard(t *testing.T) {\n\ttree := &node{}\n\troutes := [...]string{\n\t\t\"/:id/:name/:id\",\n\t}\n\tfor _, route := range routes {\n\t\t...\n\t}\n}*/\n\nfunc TestTreeTrailingSlashRedirect(t *testing.T) {\n\ttree := &node{}\n\n\troutes := [...]string{\n\t\t\"/hi\",\n\t\t\"/b/\",\n\t\t\"/search/:query\",\n\t\t\"/cmd/:tool/\",\n\t\t\"/src/*filepath\",\n\t\t\"/x\",\n\t\t\"/x/y\",\n\t\t\"/y/\",\n\t\t\"/y/z\",\n\t\t\"/0/:id\",\n\t\t\"/0/:id/1\",\n\t\t\"/1/:id/\",\n\t\t\"/1/:id/2\",\n\t\t\"/aa\",\n\t\t\"/a/\",\n\t\t\"/admin\",\n\t\t\"/admin/:category\",\n\t\t\"/admin/:category/:page\",\n\t\t\"/doc\",\n\t\t\"/doc/go_faq.html\",\n\t\t\"/doc/go1.html\",\n\t\t\"/no/a\",\n\t\t\"/no/b\",\n\t\t\"/api/:page/:name\",\n\t\t\"/api/hello/:name/bar/\",\n\t\t\"/api/bar/:name\",\n\t\t\"/api/baz/foo\",\n\t\t\"/api/baz/foo/bar\",\n\t\t\"/blog/:p\",\n\t\t\"/posts/:b/:c\",\n\t\t\"/posts/b/:c/d/\",\n\t\t\"/vendor/:x/*y\",\n\t}\n\tfor _, route := range routes {\n\t\trecv := catchPanic(func() {\n\t\t\ttree.addRoute(route, fakeHandler(route))\n\t\t})\n\t\tif recv != nil {\n\t\t\tt.Fatalf(\"panic inserting route '%s': %v\", route, recv)\n\t\t}\n\t}\n\n\ttsrRoutes := [...]string{\n\t\t\"/hi/\",\n\t\t\"/b\",\n\t\t\"/search/gopher/\",\n\t\t\"/cmd/vet\",\n\t\t\"/src\",\n\t\t\"/x/\",\n\t\t\"/y\",\n\t\t\"/0/go/\",\n\t\t\"/1/go\",\n\t\t\"/a\",\n\t\t\"/admin/\",\n\t\t\"/admin/config/\",\n\t\t\"/admin/config/permissions/\",\n\t\t\"/doc/\",\n\t\t\"/admin/static/\",\n\t\t\"/admin/cfg/\",\n\t\t\"/admin/cfg/users/\",\n\t\t\"/api/hello/x/bar\",\n\t\t\"/api/baz/foo/\",\n\t\t\"/api/baz/bax/\",\n\t\t\"/api/bar/huh/\",\n\t\t\"/api/baz/foo/bar/\",\n\t\t\"/api/world/abc/\",\n\t\t\"/blog/pp/\",\n\t\t\"/posts/b/c/d\",\n\t\t\"/vendor/x\",\n\t}\n\n\tfor _, route := range tsrRoutes {\n\t\tvalue := tree.getValue(route, nil, getSkippedNodes(), false)\n\t\tif value.handlers != nil {\n\t\t\tt.Fatalf(\"non-nil handler for TSR route '%s\", route)\n\t\t} else if !value.tsr {\n\t\t\tt.Errorf(\"expected TSR recommendation for route '%s'\", route)\n\t\t}\n\t}\n\n\tnoTsrRoutes := [...]string{\n\t\t\"/\",\n\t\t\"/no\",\n\t\t\"/no/\",\n\t\t\"/_\",\n\t\t\"/_/\",\n\t\t\"/api\",\n\t\t\"/api/\",\n\t\t\"/api/hello/x/foo\",\n\t\t\"/api/baz/foo/bad\",\n\t\t\"/foo/p/p\",\n\t}\n\tfor _, route := range noTsrRoutes {\n\t\tvalue := tree.getValue(route, nil, getSkippedNodes(), false)\n\t\tif value.handlers != nil {\n\t\t\tt.Fatalf(\"non-nil handler for No-TSR route '%s\", route)\n\t\t} else if value.tsr {\n\t\t\tt.Errorf(\"expected no TSR recommendation for route '%s'\", route)\n\t\t}\n\t}\n}\n\nfunc TestTreeRootTrailingSlashRedirect(t *testing.T) {\n\ttree := &node{}\n\n\trecv := catchPanic(func() {\n\t\ttree.addRoute(\"/:test\", fakeHandler(\"/:test\"))\n\t})\n\tif recv != nil {\n\t\tt.Fatalf(\"panic inserting test route: %v\", recv)\n\t}\n\n\tvalue := tree.getValue(\"/\", nil, getSkippedNodes(), false)\n\tif value.handlers != nil {\n\t\tt.Fatalf(\"non-nil handler\")\n\t} else if value.tsr {\n\t\tt.Errorf(\"expected no TSR recommendation\")\n\t}\n}\n\nfunc TestRedirectTrailingSlash(t *testing.T) {\n\tdata := []struct {\n\t\tpath string\n\t}{\n\t\t{\"/hello/:name\"},\n\t\t{\"/hello/:name/123\"},\n\t\t{\"/hello/:name/234\"},\n\t}\n\n\tnode := &node{}\n\tfor _, item := range data {\n\t\tnode.addRoute(item.path, fakeHandler(\"test\"))\n\t}\n\n\tvalue := node.getValue(\"/hello/abx/\", nil, getSkippedNodes(), false)\n\tif value.tsr != true {\n\t\tt.Fatalf(\"want true, is false\")\n\t}\n}\n\nfunc TestTreeFindCaseInsensitivePath(t *testing.T) {\n\ttree := &node{}\n\n\tlongPath := \"/l\" + strings.Repeat(\"o\", 128) + \"ng\"\n\tlOngPath := \"/l\" + strings.Repeat(\"O\", 128) + \"ng/\"\n\n\troutes := [...]string{\n\t\t\"/hi\",\n\t\t\"/b/\",\n\t\t\"/ABC/\",\n\t\t\"/search/:query\",\n\t\t\"/cmd/:tool/\",\n\t\t\"/src/*filepath\",\n\t\t\"/x\",\n\t\t\"/x/y\",\n\t\t\"/y/\",\n\t\t\"/y/z\",\n\t\t\"/0/:id\",\n\t\t\"/0/:id/1\",\n\t\t\"/1/:id/\",\n\t\t\"/1/:id/2\",\n\t\t\"/aa\",\n\t\t\"/a/\",\n\t\t\"/doc\",\n\t\t\"/doc/go_faq.html\",\n\t\t\"/doc/go1.html\",\n\t\t\"/doc/go/away\",\n\t\t\"/no/a\",\n\t\t\"/no/b\",\n\t\t\"/Π\",\n\t\t\"/u/apfêl/\",\n\t\t\"/u/äpfêl/\",\n\t\t\"/u/öpfêl\",\n\t\t\"/v/Äpfêl/\",\n\t\t\"/v/Öpfêl\",\n\t\t\"/w/♬\",  // 3 byte\n\t\t\"/w/♭/\", // 3 byte, last byte differs\n\t\t\"/w/𠜎\",  // 4 byte\n\t\t\"/w/𠜏/\", // 4 byte\n\t\tlongPath,\n\t}\n\n\tfor _, route := range routes {\n\t\trecv := catchPanic(func() {\n\t\t\ttree.addRoute(route, fakeHandler(route))\n\t\t})\n\t\tif recv != nil {\n\t\t\tt.Fatalf(\"panic inserting route '%s': %v\", route, recv)\n\t\t}\n\t}\n\n\t// Check out == in for all registered routes\n\t// With fixTrailingSlash = true\n\tfor _, route := range routes {\n\t\tout, found := tree.findCaseInsensitivePath(route, true)\n\t\tif !found {\n\t\t\tt.Errorf(\"Route '%s' not found!\", route)\n\t\t} else if string(out) != route {\n\t\t\tt.Errorf(\"Wrong result for route '%s': %s\", route, string(out))\n\t\t}\n\t}\n\t// With fixTrailingSlash = false\n\tfor _, route := range routes {\n\t\tout, found := tree.findCaseInsensitivePath(route, false)\n\t\tif !found {\n\t\t\tt.Errorf(\"Route '%s' not found!\", route)\n\t\t} else if string(out) != route {\n\t\t\tt.Errorf(\"Wrong result for route '%s': %s\", route, string(out))\n\t\t}\n\t}\n\n\ttests := []struct {\n\t\tin    string\n\t\tout   string\n\t\tfound bool\n\t\tslash bool\n\t}{\n\t\t{\"/HI\", \"/hi\", true, false},\n\t\t{\"/HI/\", \"/hi\", true, true},\n\t\t{\"/B\", \"/b/\", true, true},\n\t\t{\"/B/\", \"/b/\", true, false},\n\t\t{\"/abc\", \"/ABC/\", true, true},\n\t\t{\"/abc/\", \"/ABC/\", true, false},\n\t\t{\"/aBc\", \"/ABC/\", true, true},\n\t\t{\"/aBc/\", \"/ABC/\", true, false},\n\t\t{\"/abC\", \"/ABC/\", true, true},\n\t\t{\"/abC/\", \"/ABC/\", true, false},\n\t\t{\"/SEARCH/QUERY\", \"/search/QUERY\", true, false},\n\t\t{\"/SEARCH/QUERY/\", \"/search/QUERY\", true, true},\n\t\t{\"/CMD/TOOL/\", \"/cmd/TOOL/\", true, false},\n\t\t{\"/CMD/TOOL\", \"/cmd/TOOL/\", true, true},\n\t\t{\"/SRC/FILE/PATH\", \"/src/FILE/PATH\", true, false},\n\t\t{\"/x/Y\", \"/x/y\", true, false},\n\t\t{\"/x/Y/\", \"/x/y\", true, true},\n\t\t{\"/X/y\", \"/x/y\", true, false},\n\t\t{\"/X/y/\", \"/x/y\", true, true},\n\t\t{\"/X/Y\", \"/x/y\", true, false},\n\t\t{\"/X/Y/\", \"/x/y\", true, true},\n\t\t{\"/Y/\", \"/y/\", true, false},\n\t\t{\"/Y\", \"/y/\", true, true},\n\t\t{\"/Y/z\", \"/y/z\", true, false},\n\t\t{\"/Y/z/\", \"/y/z\", true, true},\n\t\t{\"/Y/Z\", \"/y/z\", true, false},\n\t\t{\"/Y/Z/\", \"/y/z\", true, true},\n\t\t{\"/y/Z\", \"/y/z\", true, false},\n\t\t{\"/y/Z/\", \"/y/z\", true, true},\n\t\t{\"/Aa\", \"/aa\", true, false},\n\t\t{\"/Aa/\", \"/aa\", true, true},\n\t\t{\"/AA\", \"/aa\", true, false},\n\t\t{\"/AA/\", \"/aa\", true, true},\n\t\t{\"/aA\", \"/aa\", true, false},\n\t\t{\"/aA/\", \"/aa\", true, true},\n\t\t{\"/A/\", \"/a/\", true, false},\n\t\t{\"/A\", \"/a/\", true, true},\n\t\t{\"/DOC\", \"/doc\", true, false},\n\t\t{\"/DOC/\", \"/doc\", true, true},\n\t\t{\"/NO\", \"\", false, true},\n\t\t{\"/DOC/GO\", \"\", false, true},\n\t\t{\"/π\", \"/Π\", true, false},\n\t\t{\"/π/\", \"/Π\", true, true},\n\t\t{\"/u/ÄPFÊL/\", \"/u/äpfêl/\", true, false},\n\t\t{\"/u/ÄPFÊL\", \"/u/äpfêl/\", true, true},\n\t\t{\"/u/ÖPFÊL/\", \"/u/öpfêl\", true, true},\n\t\t{\"/u/ÖPFÊL\", \"/u/öpfêl\", true, false},\n\t\t{\"/v/äpfêL/\", \"/v/Äpfêl/\", true, false},\n\t\t{\"/v/äpfêL\", \"/v/Äpfêl/\", true, true},\n\t\t{\"/v/öpfêL/\", \"/v/Öpfêl\", true, true},\n\t\t{\"/v/öpfêL\", \"/v/Öpfêl\", true, false},\n\t\t{\"/w/♬/\", \"/w/♬\", true, true},\n\t\t{\"/w/♭\", \"/w/♭/\", true, true},\n\t\t{\"/w/𠜎/\", \"/w/𠜎\", true, true},\n\t\t{\"/w/𠜏\", \"/w/𠜏/\", true, true},\n\t\t{lOngPath, longPath, true, true},\n\t}\n\t// With fixTrailingSlash = true\n\tfor _, test := range tests {\n\t\tout, found := tree.findCaseInsensitivePath(test.in, true)\n\t\tif found != test.found || (found && (string(out) != test.out)) {\n\t\t\tt.Errorf(\"Wrong result for '%s': got %s, %t; want %s, %t\",\n\t\t\t\ttest.in, string(out), found, test.out, test.found)\n\t\t\treturn\n\t\t}\n\t}\n\t// With fixTrailingSlash = false\n\tfor _, test := range tests {\n\t\tout, found := tree.findCaseInsensitivePath(test.in, false)\n\t\tif test.slash {\n\t\t\tif found { // test needs a trailingSlash fix. It must not be found!\n\t\t\t\tt.Errorf(\"Found without fixTrailingSlash: %s; got %s\", test.in, string(out))\n\t\t\t}\n\t\t} else {\n\t\t\tif found != test.found || (found && (string(out) != test.out)) {\n\t\t\t\tt.Errorf(\"Wrong result for '%s': got %s, %t; want %s, %t\",\n\t\t\t\t\ttest.in, string(out), found, test.out, test.found)\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunc TestTreeInvalidNodeType(t *testing.T) {\n\tconst panicMsg = \"invalid node type\"\n\n\ttree := &node{}\n\ttree.addRoute(\"/\", fakeHandler(\"/\"))\n\ttree.addRoute(\"/:page\", fakeHandler(\"/:page\"))\n\n\t// set invalid node type\n\ttree.children[0].nType = 42\n\n\t// normal lookup\n\trecv := catchPanic(func() {\n\t\ttree.getValue(\"/test\", nil, getSkippedNodes(), false)\n\t})\n\tif rs, ok := recv.(string); !ok || rs != panicMsg {\n\t\tt.Fatalf(\"Expected panic '\"+panicMsg+\"', got '%v'\", recv)\n\t}\n\n\t// case-insensitive lookup\n\trecv = catchPanic(func() {\n\t\ttree.findCaseInsensitivePath(\"/test\", true)\n\t})\n\tif rs, ok := recv.(string); !ok || rs != panicMsg {\n\t\tt.Fatalf(\"Expected panic '\"+panicMsg+\"', got '%v'\", recv)\n\t}\n}\n\nfunc TestTreeInvalidParamsType(t *testing.T) {\n\ttree := &node{}\n\t// add a child with wildcard\n\troute := \"/:path\"\n\ttree.addRoute(route, fakeHandler(route))\n\n\t// set invalid Params type\n\tparams := make(Params, 0)\n\n\t// try to trigger slice bounds out of range with capacity 0\n\ttree.getValue(\"/test\", &params, getSkippedNodes(), false)\n}\n\nfunc TestTreeExpandParamsCapacity(t *testing.T) {\n\tdata := []struct {\n\t\tpath string\n\t}{\n\t\t{\"/:path\"},\n\t\t{\"/*path\"},\n\t}\n\n\tfor _, item := range data {\n\t\ttree := &node{}\n\t\ttree.addRoute(item.path, fakeHandler(item.path))\n\t\tparams := make(Params, 0)\n\n\t\tvalue := tree.getValue(\"/test\", &params, getSkippedNodes(), false)\n\n\t\tif value.params == nil {\n\t\t\tt.Errorf(\"Expected %s params to be set, but they weren't\", item.path)\n\t\t\tcontinue\n\t\t}\n\n\t\tif len(*value.params) != 1 {\n\t\t\tt.Errorf(\"Wrong number of %s params: got %d, want %d\",\n\t\t\t\titem.path, len(*value.params), 1)\n\t\t\tcontinue\n\t\t}\n\t}\n}\n\nfunc TestTreeWildcardConflictEx(t *testing.T) {\n\tconflicts := [...]struct {\n\t\troute        string\n\t\tsegPath      string\n\t\texistPath    string\n\t\texistSegPath string\n\t}{\n\t\t{\"/who/are/foo\", \"/foo\", `/who/are/\\*you`, `/\\*you`},\n\t\t{\"/who/are/foo/\", \"/foo/\", `/who/are/\\*you`, `/\\*you`},\n\t\t{\"/who/are/foo/bar\", \"/foo/bar\", `/who/are/\\*you`, `/\\*you`},\n\t\t{\"/con:nection\", \":nection\", `/con:tact`, `:tact`},\n\t}\n\n\tfor _, conflict := range conflicts {\n\t\t// I have to re-create a 'tree', because the 'tree' will be\n\t\t// in an inconsistent state when the loop recovers from the\n\t\t// panic which threw by 'addRoute' function.\n\t\ttree := &node{}\n\t\troutes := [...]string{\n\t\t\t\"/con:tact\",\n\t\t\t\"/who/are/*you\",\n\t\t\t\"/who/foo/hello\",\n\t\t}\n\n\t\tfor _, route := range routes {\n\t\t\ttree.addRoute(route, fakeHandler(route))\n\t\t}\n\n\t\trecv := catchPanic(func() {\n\t\t\ttree.addRoute(conflict.route, fakeHandler(conflict.route))\n\t\t})\n\n\t\tif !regexp.MustCompile(fmt.Sprintf(\"'%s' in new path .* conflicts with existing wildcard '%s' in existing prefix '%s'\", conflict.segPath, conflict.existSegPath, conflict.existPath)).MatchString(fmt.Sprint(recv)) {\n\t\t\tt.Fatalf(\"invalid wildcard conflict error (%v)\", recv)\n\t\t}\n\t}\n}\n\nfunc TestTreeInvalidEscape(t *testing.T) {\n\troutes := map[string]bool{\n\t\t\"/r1/r\":    true,\n\t\t\"/r2/:r\":   true,\n\t\t\"/r3/\\\\:r\": true,\n\t}\n\ttree := &node{}\n\tfor route, valid := range routes {\n\t\trecv := catchPanic(func() {\n\t\t\ttree.addRoute(route, fakeHandler(route))\n\t\t})\n\t\tif recv == nil != valid {\n\t\t\tt.Fatalf(\"%s should be %t but got %v\", route, valid, recv)\n\t\t}\n\t}\n}\n\nfunc TestWildcardInvalidSlash(t *testing.T) {\n\tconst panicMsgPrefix = \"no / before catch-all in path\"\n\n\troutes := map[string]bool{\n\t\t\"/foo/bar\":  true,\n\t\t\"/foo/x*zy\": false,\n\t\t\"/foo/b*r\":  false,\n\t}\n\n\tfor route, valid := range routes {\n\t\ttree := &node{}\n\t\trecv := catchPanic(func() {\n\t\t\ttree.addRoute(route, nil)\n\t\t})\n\n\t\tif recv == nil != valid {\n\t\t\tt.Fatalf(\"%s should be %t but got %v\", route, valid, recv)\n\t\t}\n\n\t\tif rs, ok := recv.(string); recv != nil && (!ok || !strings.HasPrefix(rs, panicMsgPrefix)) {\n\t\t\tt.Fatalf(`\"Expected panic \"%s\" for route '%s', got \"%v\"`, panicMsgPrefix, route, recv)\n\t\t}\n\t}\n}\n\nfunc TestTreeFindCaseInsensitivePathWithMultipleChildrenAndWildcard(t *testing.T) {\n\ttree := &node{}\n\n\t// Setup routes that create a node with both static children and a wildcard child.\n\t// This configuration previously caused a panic (\"invalid node type\") in\n\t// findCaseInsensitivePathRec because it accessed children[0] instead of the\n\t// wildcard child (which is always at the end of the children array).\n\t// See: https://github.com/gin-gonic/gin/issues/2959\n\troutes := [...]string{\n\t\t\"/aa/aa\",\n\t\t\"/:bb/aa\",\n\t}\n\n\tfor _, route := range routes {\n\t\trecv := catchPanic(func() {\n\t\t\ttree.addRoute(route, fakeHandler(route))\n\t\t})\n\t\tif recv != nil {\n\t\t\tt.Fatalf(\"panic inserting route '%s': %v\", route, recv)\n\t\t}\n\t}\n\n\t// These lookups previously panicked with \"invalid node type\" because\n\t// findCaseInsensitivePathRec picked children[0] (a static node) instead\n\t// of the wildcard child at the end of the array.\n\tout, found := tree.findCaseInsensitivePath(\"/aa\", true)\n\tif found {\n\t\tt.Errorf(\"Expected no match for '/aa', but got: %s\", string(out))\n\t}\n\n\tout, found = tree.findCaseInsensitivePath(\"/aa/aa/aa/aa\", true)\n\tif found {\n\t\tt.Errorf(\"Expected no match for '/aa/aa/aa/aa', but got: %s\", string(out))\n\t}\n\n\t// Case-insensitive lookup should match the static route /aa/aa\n\tout, found = tree.findCaseInsensitivePath(\"/AA/AA\", true)\n\tif !found {\n\t\tt.Error(\"Route '/AA/AA' not found via case-insensitive lookup\")\n\t} else if string(out) != \"/aa/aa\" {\n\t\tt.Errorf(\"Wrong result for '/AA/AA': expected '/aa/aa', got: %s\", string(out))\n\t}\n}\n\nfunc TestTreeFindCaseInsensitivePathWildcardParamAndStaticChild(t *testing.T) {\n\ttree := &node{}\n\n\t// Another variant: param route + static route under same prefix\n\troutes := [...]string{\n\t\t\"/prefix/:id\",\n\t\t\"/prefix/xxx\",\n\t}\n\n\tfor _, route := range routes {\n\t\trecv := catchPanic(func() {\n\t\t\ttree.addRoute(route, fakeHandler(route))\n\t\t})\n\t\tif recv != nil {\n\t\t\tt.Fatalf(\"panic inserting route '%s': %v\", route, recv)\n\t\t}\n\t}\n\n\t// Should NOT panic even for paths that don't match any route\n\tout, found := tree.findCaseInsensitivePath(\"/prefix/a/b/c\", true)\n\tif found {\n\t\tt.Errorf(\"Expected no match for '/prefix/a/b/c', but got: %s\", string(out))\n\t}\n\n\t// Exact match should still work\n\tout, found = tree.findCaseInsensitivePath(\"/prefix/xxx\", true)\n\tif !found {\n\t\tt.Error(\"Route '/prefix/xxx' not found\")\n\t} else if string(out) != \"/prefix/xxx\" {\n\t\tt.Errorf(\"Wrong result for '/prefix/xxx': %s\", string(out))\n\t}\n\n\t// Case-insensitive match should work\n\tout, found = tree.findCaseInsensitivePath(\"/PREFIX/XXX\", true)\n\tif !found {\n\t\tt.Error(\"Route '/PREFIX/XXX' not found via case-insensitive lookup\")\n\t} else if string(out) != \"/prefix/xxx\" {\n\t\tt.Errorf(\"Wrong result for '/PREFIX/XXX': expected '/prefix/xxx', got: %s\", string(out))\n\t}\n\n\t// Param route should still match\n\tout, found = tree.findCaseInsensitivePath(\"/prefix/something\", true)\n\tif !found {\n\t\tt.Error(\"Route '/prefix/something' not found via param match\")\n\t} else if string(out) != \"/prefix/something\" {\n\t\tt.Errorf(\"Wrong result for '/prefix/something': %s\", string(out))\n\t}\n}\n"
  },
  {
    "path": "utils.go",
    "content": "// Copyright 2014 Manu Martinez-Almeida. All rights reserved.\n// Use of this source code is governed by a MIT style\n// license that can be found in the LICENSE file.\n\npackage gin\n\nimport (\n\t\"encoding/xml\"\n\t\"math\"\n\t\"net/http\"\n\t\"os\"\n\t\"path\"\n\t\"reflect\"\n\t\"runtime\"\n\t\"strings\"\n\t\"unicode\"\n)\n\n// BindKey indicates a default bind key.\nconst BindKey = \"_gin-gonic/gin/bindkey\"\n\n// localhostIP indicates the default localhost IP address.\nconst localhostIP = \"127.0.0.1\"\n\n// localhostIPv6 indicates the default localhost IPv6 address.\nconst localhostIPv6 = \"::1\"\n\n// Bind is a helper function for given interface object and returns a Gin middleware.\nfunc Bind(val any) HandlerFunc {\n\tvalue := reflect.ValueOf(val)\n\tif value.Kind() == reflect.Ptr {\n\t\tpanic(`Bind struct can not be a pointer. Example:\n\tUse: gin.Bind(Struct{}) instead of gin.Bind(&Struct{})\n`)\n\t}\n\ttyp := value.Type()\n\n\treturn func(c *Context) {\n\t\tobj := reflect.New(typ).Interface()\n\t\tif c.Bind(obj) == nil {\n\t\t\tc.Set(BindKey, obj)\n\t\t}\n\t}\n}\n\n// WrapF is a helper function for wrapping http.HandlerFunc and returns a Gin middleware.\nfunc WrapF(f http.HandlerFunc) HandlerFunc {\n\treturn func(c *Context) {\n\t\tf(c.Writer, c.Request)\n\t}\n}\n\n// WrapH is a helper function for wrapping http.Handler and returns a Gin middleware.\nfunc WrapH(h http.Handler) HandlerFunc {\n\treturn func(c *Context) {\n\t\th.ServeHTTP(c.Writer, c.Request)\n\t}\n}\n\n// H is a shortcut for map[string]any\ntype H map[string]any\n\n// MarshalXML allows type H to be used with xml.Marshal.\nfunc (h H) MarshalXML(e *xml.Encoder, start xml.StartElement) error {\n\tstart.Name = xml.Name{\n\t\tSpace: \"\",\n\t\tLocal: \"map\",\n\t}\n\tif err := e.EncodeToken(start); err != nil {\n\t\treturn err\n\t}\n\tfor key, value := range h {\n\t\telem := xml.StartElement{\n\t\t\tName: xml.Name{Space: \"\", Local: key},\n\t\t\tAttr: []xml.Attr{},\n\t\t}\n\t\tif err := e.EncodeElement(value, elem); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\treturn e.EncodeToken(xml.EndElement{Name: start.Name})\n}\n\nfunc assert1(guard bool, text string) {\n\tif !guard {\n\t\tpanic(text)\n\t}\n}\n\nfunc filterFlags(content string) string {\n\tfor i, char := range content {\n\t\tif char == ' ' || char == ';' {\n\t\t\treturn content[:i]\n\t\t}\n\t}\n\treturn content\n}\n\nfunc chooseData(custom, wildcard any) any {\n\tif custom != nil {\n\t\treturn custom\n\t}\n\tif wildcard != nil {\n\t\treturn wildcard\n\t}\n\tpanic(\"negotiation config is invalid\")\n}\n\nfunc parseAccept(acceptHeader string) []string {\n\tparts := strings.Split(acceptHeader, \",\")\n\tout := make([]string, 0, len(parts))\n\tfor _, part := range parts {\n\t\tif i := strings.IndexByte(part, ';'); i > 0 {\n\t\t\tpart = part[:i]\n\t\t}\n\t\tif part = strings.TrimSpace(part); part != \"\" {\n\t\t\tout = append(out, part)\n\t\t}\n\t}\n\treturn out\n}\n\nfunc lastChar(str string) uint8 {\n\tif str == \"\" {\n\t\tpanic(\"The length of the string can't be 0\")\n\t}\n\treturn str[len(str)-1]\n}\n\nfunc nameOfFunction(f any) string {\n\treturn runtime.FuncForPC(reflect.ValueOf(f).Pointer()).Name()\n}\n\nfunc joinPaths(absolutePath, relativePath string) string {\n\tif relativePath == \"\" {\n\t\treturn absolutePath\n\t}\n\n\tfinalPath := path.Join(absolutePath, relativePath)\n\tif lastChar(relativePath) == '/' && lastChar(finalPath) != '/' {\n\t\treturn finalPath + \"/\"\n\t}\n\treturn finalPath\n}\n\nfunc resolveAddress(addr []string) string {\n\tswitch len(addr) {\n\tcase 0:\n\t\tif port := os.Getenv(\"PORT\"); port != \"\" {\n\t\t\tdebugPrint(\"Environment variable PORT=\\\"%s\\\"\", port)\n\t\t\treturn \":\" + port\n\t\t}\n\t\tdebugPrint(\"Environment variable PORT is undefined. Using port :8080 by default\")\n\t\treturn \":8080\"\n\tcase 1:\n\t\treturn addr[0]\n\tdefault:\n\t\tpanic(\"too many parameters\")\n\t}\n}\n\n// https://stackoverflow.com/questions/53069040/checking-a-string-contains-only-ascii-characters\nfunc isASCII(s string) bool {\n\tfor i := range len(s) {\n\t\tif s[i] > unicode.MaxASCII {\n\t\t\treturn false\n\t\t}\n\t}\n\treturn true\n}\n\n// safeInt8 converts int to int8 safely, capping at math.MaxInt8\nfunc safeInt8(n int) int8 {\n\tif n > math.MaxInt8 {\n\t\treturn math.MaxInt8\n\t}\n\treturn int8(n)\n}\n\n// safeUint16 converts int to uint16 safely, capping at math.MaxUint16\nfunc safeUint16(n int) uint16 {\n\tif n > math.MaxUint16 {\n\t\treturn math.MaxUint16\n\t}\n\treturn uint16(n)\n}\n"
  },
  {
    "path": "utils_test.go",
    "content": "// Copyright 2014 Manu Martinez-Almeida. All rights reserved.\n// Use of this source code is governed by a MIT style\n// license that can be found in the LICENSE file.\n\npackage gin\n\nimport (\n\t\"bytes\"\n\t\"encoding/xml\"\n\t\"fmt\"\n\t\"math\"\n\t\"net/http\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc init() {\n\tSetMode(TestMode)\n}\n\nfunc BenchmarkParseAccept(b *testing.B) {\n\tfor b.Loop() {\n\t\tparseAccept(\"text/html , application/xhtml+xml,application/xml;q=0.9,  */* ;q=0.8\")\n\t}\n}\n\ntype testStruct struct {\n\tT *testing.T\n}\n\nfunc (t *testStruct) ServeHTTP(w http.ResponseWriter, req *http.Request) {\n\tassert.Equal(t.T, http.MethodPost, req.Method)\n\tassert.Equal(t.T, \"/path\", req.URL.Path)\n\tw.WriteHeader(http.StatusInternalServerError)\n\tfmt.Fprint(w, \"hello\")\n}\n\nfunc TestWrap(t *testing.T) {\n\trouter := New()\n\trouter.POST(\"/path\", WrapH(&testStruct{t}))\n\trouter.GET(\"/path2\", WrapF(func(w http.ResponseWriter, req *http.Request) {\n\t\tassert.Equal(t, http.MethodGet, req.Method)\n\t\tassert.Equal(t, \"/path2\", req.URL.Path)\n\t\tw.WriteHeader(http.StatusBadRequest)\n\t\tfmt.Fprint(w, \"hola!\")\n\t}))\n\n\tw := PerformRequest(router, http.MethodPost, \"/path\")\n\tassert.Equal(t, http.StatusInternalServerError, w.Code)\n\tassert.Equal(t, \"hello\", w.Body.String())\n\n\tw = PerformRequest(router, http.MethodGet, \"/path2\")\n\tassert.Equal(t, http.StatusBadRequest, w.Code)\n\tassert.Equal(t, \"hola!\", w.Body.String())\n}\n\nfunc TestLastChar(t *testing.T) {\n\tassert.Equal(t, uint8('a'), lastChar(\"hola\"))\n\tassert.Equal(t, uint8('s'), lastChar(\"adios\"))\n\tassert.Panics(t, func() { lastChar(\"\") })\n}\n\nfunc TestParseAccept(t *testing.T) {\n\tparts := parseAccept(\"text/html , application/xhtml+xml,application/xml;q=0.9,  */* ;q=0.8\")\n\tassert.Len(t, parts, 4)\n\tassert.Equal(t, \"text/html\", parts[0])\n\tassert.Equal(t, \"application/xhtml+xml\", parts[1])\n\tassert.Equal(t, \"application/xml\", parts[2])\n\tassert.Equal(t, \"*/*\", parts[3])\n}\n\nfunc TestChooseData(t *testing.T) {\n\tA := \"a\"\n\tB := \"b\"\n\tassert.Equal(t, A, chooseData(A, B))\n\tassert.Equal(t, B, chooseData(nil, B))\n\tassert.Panics(t, func() { chooseData(nil, nil) })\n}\n\nfunc TestFilterFlags(t *testing.T) {\n\tresult := filterFlags(\"text/html \")\n\tassert.Equal(t, \"text/html\", result)\n\n\tresult = filterFlags(\"text/html;\")\n\tassert.Equal(t, \"text/html\", result)\n}\n\nfunc TestFunctionName(t *testing.T) {\n\tassert.Regexp(t, `^(.*/vendor/)?github.com/gin-gonic/gin.somefunction$`, nameOfFunction(somefunction))\n}\n\nfunc somefunction() {\n\t// this empty function is used by TestFunctionName()\n}\n\nfunc TestJoinPaths(t *testing.T) {\n\tassert.Empty(t, joinPaths(\"\", \"\"))\n\tassert.Equal(t, \"/\", joinPaths(\"\", \"/\"))\n\tassert.Equal(t, \"/a\", joinPaths(\"/a\", \"\"))\n\tassert.Equal(t, \"/a/\", joinPaths(\"/a/\", \"\"))\n\tassert.Equal(t, \"/a/\", joinPaths(\"/a/\", \"/\"))\n\tassert.Equal(t, \"/a/\", joinPaths(\"/a\", \"/\"))\n\tassert.Equal(t, \"/a/hola\", joinPaths(\"/a\", \"/hola\"))\n\tassert.Equal(t, \"/a/hola\", joinPaths(\"/a/\", \"/hola\"))\n\tassert.Equal(t, \"/a/hola/\", joinPaths(\"/a/\", \"/hola/\"))\n\tassert.Equal(t, \"/a/hola/\", joinPaths(\"/a/\", \"/hola//\"))\n}\n\ntype bindTestStruct struct {\n\tFoo string `form:\"foo\" binding:\"required\"`\n\tBar int    `form:\"bar\" binding:\"min=4\"`\n}\n\nfunc TestBindMiddleware(t *testing.T) {\n\tvar value *bindTestStruct\n\tvar called bool\n\trouter := New()\n\trouter.GET(\"/\", Bind(bindTestStruct{}), func(c *Context) {\n\t\tcalled = true\n\t\tvalue = c.MustGet(BindKey).(*bindTestStruct)\n\t})\n\tPerformRequest(router, http.MethodGet, \"/?foo=hola&bar=10\")\n\tassert.True(t, called)\n\tassert.Equal(t, \"hola\", value.Foo)\n\tassert.Equal(t, 10, value.Bar)\n\n\tcalled = false\n\tPerformRequest(router, http.MethodGet, \"/?foo=hola&bar=1\")\n\tassert.False(t, called)\n\n\tassert.Panics(t, func() {\n\t\tBind(&bindTestStruct{})\n\t})\n}\n\nfunc TestMarshalXMLforH(t *testing.T) {\n\th := H{\n\t\t\"\": \"test\",\n\t}\n\tvar b bytes.Buffer\n\tenc := xml.NewEncoder(&b)\n\tvar x xml.StartElement\n\te := h.MarshalXML(enc, x)\n\tassert.Error(t, e)\n}\n\nfunc TestMarshalXMLforHSuccess(t *testing.T) {\n\th := H{\n\t\t\"key1\": \"value1\",\n\t\t\"key2\": 123,\n\t}\n\tdata, err := xml.Marshal(h)\n\trequire.NoError(t, err)\n\tassert.Contains(t, string(data), \"<key1>value1</key1>\")\n\tassert.Contains(t, string(data), \"<key2>123</key2>\")\n}\n\nfunc TestIsASCII(t *testing.T) {\n\tassert.True(t, isASCII(\"test\"))\n\tassert.False(t, isASCII(\"🧡💛💚💙💜\"))\n}\n\nfunc TestSafeInt8(t *testing.T) {\n\tassert.Equal(t, int8(100), safeInt8(100))\n\tassert.Equal(t, int8(math.MaxInt8), safeInt8(int(math.MaxInt8)+123))\n}\n\nfunc TestSafeUint16(t *testing.T) {\n\tassert.Equal(t, uint16(100), safeUint16(100))\n\tassert.Equal(t, uint16(math.MaxUint16), safeUint16(int(math.MaxUint16)+123))\n}\n"
  },
  {
    "path": "version.go",
    "content": "// Copyright 2018 Gin Core Team. All rights reserved.\n// Use of this source code is governed by a MIT style\n// license that can be found in the LICENSE file.\n\npackage gin\n\n// Version is the current gin framework's version.\nconst Version = \"v1.12.0\"\n"
  }
]